ArchUnit 実践:同一パッケージからのみ依存されるクラスの可視性をパッケージプライベートに強制する


// 実行環境
* AdoptOpenJDK 11.0.9.1+1
* JUnit 5.7.0
* ArchUnit 0.14.1

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

  • パッケージ内に閉じた関心事を、パッケージ外に対して隠蔽したい
  • パッケージ外からの想定外の依存(結合)が生まれることを言語仕様レベルで防止したい
  • 公開範囲を限定することで、保守性(理解容易性、変更容易性)を向上したい

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

package com.example;
 
import com.tngtech.archunit.base.DescribedPredicate;
import com.tngtech.archunit.core.domain.Dependency;
import com.tngtech.archunit.core.domain.JavaClass;
import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import com.tngtech.archunit.core.importer.ImportOption;
import org.junit.jupiter.api.Test;

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

class ArchitectureTest {

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

    @Test
    void 同一パッケージからのみ依存されるクラスはパッケージプライベートにする() {
        classes()
            .that()
            .arePublic()
            .and(new DescribedPredicate<>("only have dependent classes that reside in same package") {
                @Override
                public boolean apply(final JavaClass clazz) {
                    return clazz.getDirectDependenciesToSelf()
                        .stream()
                        .map(Dependency::getOriginClass)
                        .allMatch(dependentClass
                            -> dependentClass.getPackageName().equals(clazz.getPackageName()));
                }
            })
            .should()
            .notBePublic()
            .check(CLASSES);
    }
}