1
1

More than 3 years have passed since last update.

ArchUnit 実践:特定のメソッド呼び出しを禁止する

Last updated at Posted at 2020-12-11
// 実行環境
* AdoptOpenJDK 11.0.9.1+1
* JUnit 5.7.0
* ArchUnit 0.14.1

アーキテクチャテストのモチベーション

タイムゾーンを考慮する必要があるアプリケーション1で、以下の実装ルールに準拠していることを担保したい。

  • 原則として ZonedDateTime, OffsetDateTime の使用を強制したい
  • ただし、実装の都合で LocalDateTime インスタンスを扱いたい場合があり、LocalDateTime 型への依存自体は許容したい
  • LocalDateTime インスタンスをアプリコード内で生成する必要はなく、LocalDateTime インスタンスの生成方法のうち、最低限、LocalDateTime.now(), LocalDateTime.of(...), LocalDateTime.parse(...) が使用されないことを担保したい

アーキテクチャテストの実装

package com.example;
 
import com.tngtech.archunit.core.domain.AccessTarget.MethodCallTarget;
import com.tngtech.archunit.core.domain.JavaClass;
import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.domain.JavaMethod;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import com.tngtech.archunit.core.importer.ImportOption;
import com.tngtech.archunit.lang.ArchCondition;
import com.tngtech.archunit.lang.ConditionEvents;
import com.tngtech.archunit.lang.SimpleConditionEvent;
import org.junit.jupiter.api.Test;

import java.time.LocalDateTime;

import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.*;

class ArchitectureTest {

    // 検査対象のクラス
    private static final JavaClasses CLASSES =
            new ClassFileImporter()
                    .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS)
                    .importPackages("com.example");

    @Test
    void doNotInstantiateLocalDateTime() {
        methods().should(new ArchCondition<>("not instantiate LocalDateTime") {
            @Override
            public void check(final JavaMethod method, final ConditionEvents events) {
                method.getMethodCallsFromSelf().forEach(javaMethodCall -> {
                    // 呼び出したメソッド
                    MethodCallTarget calledMethod = javaMethodCall.getTarget();
                    // 呼び出したメソッドが定義されたクラス
                    JavaClass calledClass = calledMethod.getOwner();

                    // LocalDateTime の生成か
                    boolean isLocalDateTimeInstantiated =
                        calledClass.isAssignableTo(LocalDateTime.class)
                            && (
                                calledMethod.getName().equals("now")
                                    || calledMethod.getName().equals("of")
                                    || calledMethod.getName().equals("parse")
                            );

                    if (isLocalDateTimeInstantiated) {
                        // 実装ルール違反を通知
                        events.add(SimpleConditionEvent.violated(
                            method,
                            String.format("`%s` calls `%s`.", method.getFullName(), calledMethod.getFullName()))
                        );
                    }
                });
            }
        })
        .check(CLASSES);
    }
}

アーキテクチャテストの失敗例

ArchitectureTest > doNotInstantiateLocalDateTime() FAILED
    java.lang.AssertionError: Architecture Violation [Priority: MEDIUM] - Rule 'methods should not instantiate LocalDateTime' was violated (4 times):
    `com.example.domain.DomainService.hoge1()` calls `java.time.LocalDateTime.now()`.
    `com.example.domain.DomainService.hoge2()` calls `java.time.LocalDateTime.of(java.time.LocalDate, java.time.LocalTime)`.
    `com.example.domain.DomainService.hoge3()` calls `java.time.LocalDateTime.of(int, int, int, int, int, int)`.
    `com.example.domain.DomainService.hoge4()` calls `java.time.LocalDateTime.parse(java.lang.CharSequence)`.
1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1