はじめてのArchUnit

今回は、2018/11に発表された[Technology Radar Vol.19](https://www.thoughtworks.com/radar/tools)の中で自分が特に気になったArchUnitというライブラリについて書いていきたいと思います。
# ゴール
* ArchUnitとはなにかを知る
* ArchUnitで出来ることを知る

# はじめに
この記事は、公式の情報を参考にしながら実際に手を動かした結果を載せていますが、ArchUnitをつかって実装する場合はこちらの記事ではなく、公式の[ArchUnit User Guide](https://www.archunit.org/userguide/html/000_Index.html)を参考にするようにしてください。
また、記事の中でなにか間違っている箇所がありましたらコメントお願いします。

# ArchUnitとは
[ArchUnit](https://www.archunit.org/)は、Java/Kotlinで書かれたアプリケーションを対象とした、アーキテクチャのテストを行うライブラリです。ArchUnitのソースコードはGitHubに公開されています。
https://github.com/TNG/ArchUnit

ArchUnitでは、パッケージやクラス間の依存関係、クラスのパッケージングのチェック、循環参照のチェックなどアーキテクチャに関する様々なテストを行うことができます。
また、ArchUnitは、[進化的アーキテクチャ](https://www.oreilly.co.jp/books/9784873118567/)で述べられている「適応度関数」を簡単に実装できるライブラリとして[Technology Radar](https://www.thoughtworks.com/radar/tools/archunit)で紹介されています。適応度関数とは、アーキテクチャの特性を測るため指標です。ArchUnitを使ったテストをCI/CDに組み込むことで適応度関数を実現することができます。適応度関数について詳しく知りたい方は、[進化的アーキテクチャ](https://www.oreilly.co.jp/books/9784873118567/)や[Technology Radar Vol.18](https://www.thoughtworks.com/radar/techniques/architectural-fitness-function)を参照してください。

# ArchUnitではどんなことができるのか
ここからは実際にコードベースでArchUnitを使ったテストを紹介していきたいと思います。
[ArchUnit User Guide](https://www.archunit.org/userguide/html/000_Index.html)を参考にしているので、詳細を知りたい場合はこちらを必ず参照してください。
動作環境は、Maven 3.5.4, Spring Boot 2.1.0, Kotlin 1.3, JUnit 4を想定しています。

## 導入
JUnit4/Mavenの場合は、pom.xmlに以下を追加すればOKです。

```pom.xml
<dependency>
    <groupId>com.tngtech.archunit</groupId>
    <artifactId>archunit-junit4</artifactId>
    <version>0.9.3</version>
    <scope>test</scope>
</dependency>
```

実際にテストクラスを作成するときは、以下のように@RunWithアノテーションを使ってArchUnitRunnerを読み込んでください。また、テスト対象のアプリケーションのベースパッケージを@AnalyzeClassesアノテーションで指定し、テストメソッドには`@ArchTest`を付与します。

```Sample.kt
@RunWith(ArchUnitRunner::class)
@AnalyzeClasses(packages = ["com.dais39.sample.app"])
class TestApplicationRules {

    @ArchTest
    fun test(classes: JavaClasses) {
        // テスト処理
    }
}
```

## パッケージ間の依存関係のテスト
パッケージの依存関係をテストする場合は、以下のようなコードを書きます。
ArchUnitでは、基本的なテストの流れとして、`rule`をメソッドチェーンで記述していき、最後に`check()`を呼びだすことでテストを行います。メソッドを繋げることで表現力のあるコードを書くことができるのがArchUnitの特徴です。
下の例では、applicationパッケージに配置されたクラスを参照するクラスは全て、presentationもしくはapplicationパッケージに配置されていることを保証します。別のパッケージから参照されている場合はテストが失敗します。

```Sample.kt
@RunWith(ArchUnitRunner::class)
@AnalyzeClasses(packages = ["com.dais39.sample.app"])
class TestApplicationRules {

    @ArchTest
    fun applicationレイヤのクラスはpresentationレイヤ以外のクラスから依存されない(classes: JavaClasses) {

        val rules = ArchRuleDefinition.classes().that().resideInAPackage("..application..")
            .should().onlyBeAccessed().byClassesThat().resideInAPackage("..presentation..")

        rules.check(classes)
    }
}
```
## クラス間の依存関係のテスト
ServiceクラスがControllerクラスからのみアクセスされることを保証したい場合のテストは以下です。`haveNameMatching()`を使うと正規表現でパターンマッチができますが、`haveSimpleName()`を使うことでクラス名を直接指定することができます。これ以外にも様々なパターンマッチを行うメソッドが用意されています。

```Sample.kt
@ArchTest
fun ServiceクラスはControllerクラスからのみアクセスされる(classes: JavaClasses){
        
    val rules = ArchRuleDefinition.classes().that().haveNameMatching(".*Service")
            .should().onlyBeAccessed().byClassesThat().haveNameMatching(".*Controller")

    rules.check(classes)
}
```
## クラス配置に関するテスト
あるクラスが特定のパッケージに配置されていることを保証するテストは以下のように記述します。

```Sample.kt
@ArchTest
fun ToDoServiceで始まる名前のクラスはapplicationパッケージに配置される(classes: JavaClasses){

    val rules = ArchRuleDefinition.classes().that().haveSimpleNameStartingWith("ToDoService")
            .should().resideInAPackage("..application..")

    rules.check(classes)
}
```
## 継承,実装関係のテスト
あるインターフェースが決まった条件のクラスからのみ実装されていることを保証するテストは以下のように記述します。下の例では`haveSimpleNameEndingWith()`を使って指定した文字列で終わるクラスを条件としています。

```Sample.kt
@ArchTest
    fun ToDoServiceImplはToDoServiceインターフェースを実装する(classes: JavaClasses){

        val rules = ArchRuleDefinition.classes().that().implement(ToDoService::class.java)
            .should().haveSimpleNameEndingWith("ToDoServiceImpl")

        rules.check(classes)
    }
```

## アノテーションが付与されたクラスの依存関係のテスト
ToDoServiceクラスが`@RestController`が付与されたクラスからのみアクセスされることを保証するテストが以下です。

```Sample.kt
@ArchTest
fun ToDoServiceクラスはRestControllerアノテーションが付与されたクラスからのみアクセスされる(classes: JavaClasses){

    val rules = ArchRuleDefinition.classes().that().areAssignableTo(ToDoService::class.java)
            .should().onlyBeAccessed().byClassesThat().areAnnotatedWith(RestController::class.java)

    rules.check(classes)
}
```
## レイヤアーキテクチャに則ったパッケージ構成のテスト
ArchUnitではレイヤアーキテクチャに則ったパッケージ構成を保証するテストを書くことができます。現状ではレイヤアーキテクチャのみに対応していますが、今後はヘキサゴナルアーキテクチャやクリーンアーキテクチャなどにも対応していくそうです。
レイヤアーキテクチャにDIP(依存関係逆転の原則)を適用したアーキテクチャを保証するテストが以下です。

```Sample.kt
@ArchTest
fun アプリケーションの構造がレイヤアーキテクチャに則っている(classes: JavaClasses){
    val rules = Architectures.layeredArchitecture()
            .layer("Presentation").definedBy("..presentation..")
            .layer("Application").definedBy("..application..")
            .layer("Domain").definedBy("..domain..")
            .layer("Infrastructure").definedBy("..infra..")

            .whereLayer("Presentation").mayNotBeAccessedByAnyLayer()
            .whereLayer("Application").mayOnlyBeAccessedByLayers("Presentation")
            .whereLayer("Domain").mayOnlyBeAccessedByLayers("Application", "Infrastructure")
            .whereLayer("Infrastructure").mayNotBeAccessedByAnyLayer()

    rules.check(classes)
}
```
## 循環参照のテスト
アプリケーションに循環参照が存在していないかをチェックするテストを作る場合は、以下のように記述します。`matching()`で対象のパッケージを指定します。

```Sample.kt
@ArchTest
fun 循環参照が存在しない(classes: JavaClasses){

    val rules  = SlicesRuleDefinition.slices().matching("com.dais39.sample.app.(*)..").should().beFreeOfCycles()

    rules.check(classes)
}
```
# おわりに
今回は、ArchUnitとはなにか？ArchUnitを使ってどんなことができるのか？ということを紹介しました。アーキテクチャのテストができる点とKotlinをサポートしている点で、個人的にとても推しているライブラリなので、この記事を見て興味を持つ方が増えるとうれしいです。
# 参考資料
- ArchUnit https://www.archunit.org/
- TNG/ArchUnit https://github.com/TNG/ArchUnit
- ArchUnit User Guide https://www.archunit.org/userguide/html/000_Index.html
- Technology Radar https://www.thoughtworks.com/radar
- 進化的アーキテクチャ https://www.oreilly.co.jp/books/9784873118567/