アーキテクチャの欠陥をビルド時に発見
著者:Jonas Havers
2019年8月20日
開発組織がよく直面する問題に、コードの実装がもともとの設計やアーキテクチャからたびたびずれてしまうことがあるというものがあります。この問題は、大規模なプロジェクトでは特にですが、よく起きるものであるため、コードの実装と最初に定義されたアーキテクチャとの整合性をテストする新しいツールが登場しています。ArchUnitは、小さくシンプルで拡張可能な、オープンソースのJavaテスト・ライブラリです。このライブラリは、事前に定義したアプリケーション・アーキテクチャの特徴やアーキテクチャの制約を検証するためのものです。ArchUnitのテストは、単体テストとして記述して実行します。そのため、開発者やアプリケーション・アーキテクトが、作業に対するフィードバックを迅速に得ることができます。アーキテクチャの違反が含まれていた場合、このテストにより、ソフトウェアのビルドが確実に中断されます。
本記事では、アーキテクチャのテストを行うべき理由と、ArchUnitを使って実際にそのテストを開始する方法について説明します。本記事で紹介しているコード・スニペットや、さらなるサンプルは、筆者のGitHubリポジトリで公開しています。
アーキテクチャのテストを行うべき理由
ソフトウェアのアーキテクチャは、わかりやすく変更しやすいコードベースを実現するためや、ソフトウェアの品質目標に準拠するために重要な前提条件です。コードベースについて言えば、ソフトウェアのアーキテクチャの主な目標は、保守性、置換性、拡張性の3つです。それらの目標を達成する能力を最大化することにより、チームの反復作業の速度を高めることができます。すなわち、アプリケーションが成長したときに、機能追加やバグの修正を短時間で行うことができます。ソフトウェア・システムの保守性、置換性、拡張性を維持するためには、そのシステムがモジュール式になっており、相互依存ができる限り少なくかつ正確であることを保証する必要があります。そうすれば、凝集度が高くかつ疎結合であるシステムが実現します。
こういった目標は、一定のパターンやコード表記規則の導入により達成できます。その場合、該当するパターンや規則を完全に文書化したうえで、それに賛同している開発チーム全体に伝達する必要があります。
既存のソフトウェア・システムの保守が難しくなった場合、自動アーキテクチャ検証を事後導入することも適切な選択肢です。テストによって、技術的負債を段階的に減らしていくことができます。こうすることで、目標としたプロジェクト・アーキテクチャの実現に向けた進捗の監視や検証を行うことができます。
アーキテクチャのテストを記述する準備
通常、アプリケーション・アーキテクチャのコンセプトや構造をテストするためには、アーキテクチャ・モデルをコードにマッピングする作業か、既存のアプリケーションからアーキテクチャ・モデルを導出する作業が最初に必要になります。これを行う場合、あらかじめコード構造について考えておくだけでなく、アプリケーションのコードでどのようにすればドメイン・コンセプトや技術的コンセプトを特定できるか(または、特定できるようになるか)を考えておくことが必要です。これには、「コントローラ」や「サービス」などの具体的なアプリケーション・コンポーネントの技術用語や、「プロダクト」や「顧客」などのドメイン固有用語、そしてそれらの間の関係を特定することが含まれます。
用語、関係、表記規則に賛同したら、静的アーキテクチャのルール体系を作成できます。この体系により、開発者はそのルールに準拠したコードを書くことや変更することができます。つまり、レイヤーやパッケージ、クラスのネーミング方式、アノテーションなどを使用してコンセプトを実装することにより、メンタル・モデルをマッピングすることができます。
ArchUnitを選ぶ理由
最初のArchUnitライブラリは、Peter Gafert氏が2017年に作成しました。ArchUnitにより、アプリケーションのクラスを特別なJavaコード構造にインポートし、Javaの任意の単体テスト・フレームワークを使ってコードとその構造をテストすることができます。ArchUnitでは、実行可能テスト形式でアプリケーション・アーキテクチャの静的プロパティのルールを実装できます。たとえば、次のようなものです。
- パッケージ依存性チェック
- クラス依存性チェック
- クラスとパッケージの包含性チェック
- 継承チェック
- アノテーション・チェック
- レイヤー・チェック
- サイクル・チェック
さらに、コンストラクタ、メソッド、フィールド、メンバー、および「コード・ユニット」(すなわち、他のコードにアクセスできるメソッド、コンストラクタ、静的クラス初期化機能)のカスタム・テストを作成できます。これらのチェックを使用して、固有のアーキテクチャ制約に加え、ネーミング規則などのコーディング・ルールも検証することができます。
レイヤー間やパッケージ間の依存性をテストするツールにはさまざまなものがあります。これはアーキテクチャの検証において、とりわけレイヤー型アーキテクチャの場合、重要な懸念事項です。コードのlintツールにより、多くのコーディング・ガイドライン(クラスやメンバーのネーミングなど)に対処できますが、こういったツールはスコープが限られており、ルールがさらに複雑になった場合、部分的にしか使えなくなる可能性があります。その結果、複数のツールの組合せによってしか強制できないルールが出てくる可能性もあります(ときには、ツールを組み合わせても強制できないこともあります)。カスタム・テスト・スイートでさまざまなツールやAPI(Java Reflection APIやAspectJなど)、および追加言語(Groovyなど)を使用すると、初心者の場合は特に、結局のところ習得がいたずらに難しくなります。
対照的に、ArchUnitでは、特別なインフラストラクチャや新しい言語は一切必要ありません。ルールを検証するテストは、プレーンJavaで記述できます。ArchUnitでは、Javaバイトコードに変換された他の言語(Kotlinなど)のテスト・コードやアプリケーション・コードを扱うこともできます。ルールは任意の単体テスト・ツールで評価できますが、JUnit 4およびJUnit 5でテストを記述する場合の拡張サポートが存在します。
アーキテクチャが進化している場合、実装ルールも時間とともに進化します。組込みの自動実行テストを使用すると、組織は事前定義ルールからの逸脱があることを意識的に認めざるを得なくなります。後ほどレビューで偶然発見することや、決して発見できないことはなくなります。ArchUnitでは、逸脱の警告が可能です。
筆者の経験では、いったんArchUnitでルールを書き始めれば、次々とユースケースに気づきます。たとえば、サードパーティ製ライブラリの正しくない使用を避けることができ、「コードの臭い」のパターンと比較してテストすることにより、そういった兆候をプロジェクトから排除することもできます。ArchUnitのAPIによって創造性が刺激され、コード全体の品質を向上させるために行う、新しいチェックの発案や既存ルールの改善が容易になります。
さらに、ArchUnitライブラリには、一般的な事前定義ルールも用意されています。ライブラリにおいてすべての使用方法を予測するのは難しいため、拡張可能であることがなおさら重要です。拡張性を実現するため、ArchUnitではカスタム・チェックを記述する便利な方法を提供しています。具体的には、いくつかの事前定義された組合せ可能なビルディング・ブロックやインタフェースを使います。
内部動作
ArchUnitでは、リフレクションとJavaバイトコード分析を使用しています。Java Reflection APIから取得できない情報は、バイトコード・レベルで取得します。たとえば、Reflection APIでは、クラスへのアクセスまたはクラスからのアクセスについての情報を取得する方法は提供されていません。しかし、この情報はバイトコードに含まれています。つまり、2つのクラス間の依存性は、Javaバイトコード分析によってのみ取得できます。バイトコードを分析するために、ArchUnitライブラリではASMを使用します。ASMは、Javaバイトコードの読取り、書込み、操作を行う万能フレームワークです(詳しくは、本誌で以前特集したASMの記事をご覧ください)。
ASMを使用して、単体テストによく似ているものの、アーキテクチャ制約を対象にしたテストを記述することができます。たとえば、ArchUnitでアーキテクチャ・ルールを記述すると、次のようになります。
ArchRule rule = ArchRuleDefinition.classes()
.that().resideInAPackage("..domain..")
.should().onlyBeAccessed()
.byAnyPackage("..domain..", "..application..");
別の例を示します。
ArchRule rule = ArchRuleDefinition.methods()
.that().arePublic()
.and().areDeclaredInClassesThat().resideInAPackage("..adapters.primary.web..")
.and().areDeclaredInClassesThat().haveSimpleNameEndingWith("Controller")
.and().areDeclaredInClassesThat().areAnnotatedWith(Controller.class)
.should().beAnnotatedWith(RequestMapping.class);
ArchUnitは、3つの主要なAPIレイヤーに分かれています。Core、Lang、Libraryという3つのレイヤーですが、次のセクションではこの点について詳しく説明します。
使用の開始
ArchUnitライブラリは、Maven Centralから入手できます。まず、MavenまたはGradleで宣言を行い、依存性を取得する必要があります。その後は、Javaの任意の単体テスト・フレームワークを使用し、テストを記述して実行することができます。次に示すのは、Mavenユーザー向けのPOMエントリです。
<dependency>
<groupId>com.tngtech.archunit</groupId>
<artifactId>archunit</artifactId>
<version>0.11.0</version>
<scope>test</scope>
</dependency>
Gradleユーザー向けのPOMは次のようになります。
dependencies {
testCompile 'com.tngtech.archunit:archunit:0.11.0'
}
ArchUnitでは、JUnit向けにArchUnitRunnerを提供しています。このランナーにより、コードの定型挿入文が減少します。また、複数のテストに使うクラスを毎回インポートしなくてもよいように、インポートしたクラスがURL別にキャッシュされます。JUnit 4でこのランナーを使う場合は、上記の依存性のarchunitアーティファクトをarchunit-junit4に置き換えます。JUnit 5では、archunit-junit5-apiおよびarchunit-junit5-engineアーティファクトを使用します。その他の情報は、インストール・ガイドに記載されています。
ArchUnitRunnerとJUnitを使う場合、ArchRulesは静的フィールド、または1つのJavaClasses引数を持つ静的メソッドのいずれかで定義できます。いずれの場合も、@ArchTestアノテーションを付加する必要があります。これにより、すでにインポートされてキャッシュされたクラスがランナーによって再利用され、ルールはキャッシュされたクラスを対象として評価されます。次に例を示します。
@RunWith(ArchUnitRunner.class)
@AnalyzeClasses(
packages = "com.company.app",
importOptions =
ImportOption.DoNotIncludeTests.class
)
public class ArchitectureRulesTest {
@ArchTest
public static final ArchRule ruleAsStaticField =
ArchRuleDefinition.classes()
.should()...
@ArchTest
public static void ruleAsStaticMethod(JavaClasses classes) {
ArchRuleDefinition.classes()
.should()...
}
}
JUnit 5では、ランナーを宣言する必要はありません。そのため、最初の行@RunWith(ArchUnitRunner.class)を削除できます。
前述のように、ArchUnitではチェックを単体テストとして記述します。その性質上、継続的インテグレーション(CI)パイプラインに特別な手順を導入する必要はありません。このテスト・スイートは、任意のCI環境やデプロイ・パイプラインに組み込むことができます。
既知のアーキテクチャ違反でテストが中断しないように、そういった違反を除外することができます。これを行うには、archunit_ignore_patterns.txtという名前のファイルをクラスパスのルート・ディレクトリに配置します。このファイルのそれぞれの行は正規表現と解釈され、報告された違反と照合されます。違反のメッセージがファイル内のパターンと一致した場合は、無視されます。1つのテストを無視するためには、テスト・クラスで@ArchIgnoreアノテーションを使います。バージョン0.11.0以降では、FreezingArchRuleという機能を使うこともできます。この機能は、ルールの違反が多すぎてすぐに修正できない場合に、現在の違反状態を保存するものです。とは言え一般論としては、テストを無視すべきではありません。ただし、これらのオプションを使うことで、アーキテクチャに関する既知の欠陥や侵害を含む、既存のソフトウェア・プロジェクトにArchUnitを組み込めるようになります。そうすれば、ルールを満たすことを目的として、修正やクリーンなアーキテクチャへの移行を繰り返すことができます。
また、異なるプロジェクト間で、共通のアーキテクチャ・ルールをコピーすることなく、簡単に共有して強制できるサードパーティ製Mavenプラグインもあります。
ArchUnitの中身
前述のように、ArchUnitは3つの主要APIであるCore、Lang、Libraryの各レイヤーに分かれています。
Core API:このレイヤーは、Reflection APIに似ています。さらに、バイトコードのインポート方法などの基本インフラストラクチャも扱います。コンパイル済みのJavaクラス・ファイルをインポートするためには、ClassFileImporterを使います。このインポーターでは、コンパイル済みのJavaクラスをインポートするさまざまな方法を提供しますが、もっとも便利な方法は、次に例を示すように、1つまたは複数のベース・パッケージを宣言することです。テスト・クラスやアーカイブ(JARなど)といった特定の場所を除外できます。
JavaClasses classes = new ClassFileImporter()
.withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS)
.withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_ARCHIVES)
.importPackages("com.company.app");
インポーターから返されるのは、JavaClassesのインスタンスです。このインスタンスは、JavaClass型の要素のコレクションを表します。JavaClassは、1つのインポート済みクラス・ファイルを示します。
Coreのオブジェクトは、Reflection APIの同等のオブジェクトから命名されていますが、Javaというプリフィックスが追加されています。さらに、Core APIには代表的なクラスとして、JavaPackage、JavaMethod、JavaFieldなどもあります。以前にReflection APIを使ったことがある開発者なら、getName()、getMethods()、getRawType()、getRawParameterTypes()といったメソッドはおなじみでしょう。ArchUnitのCore APIにも、これらと同等のメソッドがあります。
追加のアクセス情報は、JavaMethodCall、JavaConstructorCall、JavaFieldAccessを使ってバイトコードから取得できます。たとえば、JavaClassインスタンスに対してgetAccessesFromSelf()を呼び出すことにより、インポート済みJavaクラスと、他の複数のインポート済みJavaクラスとの間でアクセスを反復させ、そのアクセスを分析することができます。同じようなアクセス・チェックは、getAccessesToSelf()、getFieldAccessesFromSelf()、getFieldAccessesToSelf()の各オブジェクト・メソッドで行うことができます。
Lang API:Coreのクラスだけを使ってアーキテクチャのテストを行うことは可能ですが、表現力に欠けることになります。これに対処するため、ArchUnitではLang APIで高レベルの抽象化を提供しています。このAPIでは、次の3つの中核コンポーネントで構成された、流れるようなインタフェースを提供します。
- DescribedPredicate:関連クラスを選択する基準
- ArchCondition:選択されたクラスが満たさなければならない条件
- ArchRule:アーキテクチャのコンセプトを定義するルール
Lang APIの大部分も同様に、流れるようなAPIとして構成されているため、IDEでは、使うAPIについての有益な提案を行うことができます。
クラスArchRuleDefinitionは、ArchRuleを定義するエントリ・ポイントとして使われます。先ほど触れた3つのAPIコンポーネントを使用して、提供されている選択基準および条件にとどまらず、ニーズを満たす独自のものを作成できます。
次の例では、ドメイン・クラスが他のドメイン・クラスまたはアプリケーション・クラスからのみアクセスされることを検証しています。
ArchRule rule = ArchRuleDefinition.classes()
.that().resideInAPackage("..domain..")
.should().onlyBeAccessed()
.byAnyPackage("..domain..", "..application..");
❝この構文は、AspectJのポイントカットの影響を受けています。❞
パッケージ表記法の「..」は、任意の数のパッケージを表します。この構文は、AspectJのポイントカットの影響を受けています。つまり、この例のArchRuleは、com.company.app.domain.modelなどのパッケージ内にある任意のクラスに適用されます(DescribedPredicate)。また、そのクラスがdomainパッケージまたはapplicationパッケージ内のクラス、あるいはそれらのパッケージのサブパッケージ内にあるクラスによってのみアクセスされることが検証されます(ArchCondition)。
ArchUnitルールのチェックに失敗した場合、ルールのテキストとすべての違反を内包するjava.lang.AssertionErrorが報告されます。この報告には、クラスと行番号も含まれています。
また、ArchRuleDefinition.noClasses()で始めることにより、クラス・ルールを否定することもできます。クラスだけでなく、コンストラクタ、メソッド、フィールド、メンバー、コード・ユニットを直接テストすることもでき、それぞれを否定する方法も存在します。
次に示すのは、Lang APIを使って長い記述をした例です。複雑なチェックがどのようなものになるかをご覧ください。この例はSpringフレームワークを対象としたもので、Springのモデル・ビュー・コントローラ(MVC)クラスのメソッドをチェックするために使用できます。
ArchRule rule = ArchRuleDefinition.methods()
.that().arePublic()
.and().areDeclaredInClassesThat()
.resideInAPackage("..adapters.primary.web..")
.and().areDeclaredInClassesThat()
.haveSimpleNameEndingWith("Controller")
.and().areDeclaredInClassesThat()
.areAnnotatedWith(Controller.class)
.or().areDeclaredInClassesThat()
.areAnnotatedWith(RestController.class)
.should().beAnnotatedWith(RequestMapping.class)
.orShould().beAnnotatedWith(GetMapping.class)
.orShould().beAnnotatedWith(PostMapping.class)
.orShould().beAnnotatedWith(PatchMapping.class)
.orShould().beAnnotatedWith(DeleteMapping.class);
このルールでは、コントローラ内のパブリック・メソッドにSpring MVCのリクエスト・マッピング・アノテーションのいずれかが付加されていることを確認し、リクエストの処理に使われないパブリック・メソッドがコントローラに存在しないようにしています。選択されたクラスは、Controllerで終わる単純名を持ち、Spring MVCの@Controllerまたは@RestControllerアノテーションが付加され、..adapters.primary.web..パッケージまたはそのサブパッケージ内に存在しなければなりません。このようなルールにより、メソッド・レベルで強いコーディング規約を強制できます。
上記のようなルールを作成したら、そのルールをテスト結果に反映させるために、インポートしたJavaクラスに対してチェックを行う必要があります。
JavaClasses classes = new ClassFileImporter() ...
ArchRule rule = ...
rule.check(classes);
Library API:このレイヤーには、さらに抽象化された複雑な事前定義ルールが含まれています。たとえば、ArchUnitでは、Architectures.LayeredArchitectureのインスタンスを使ってレイヤー型アーキテクチャの定義を作成し、個々のレイヤーに対してチェックを行うことができます。レイヤー型アーキテクチャは、レイヤー、名前、パッケージを規定することで簡単に定義できます。レイヤー型アーキテクチャとして表現できるポート・アダプタ・アーキテクチャの定義は、次のようなものになります。
Architectures.LayeredArchitecture portsAndAdaptersArchitecture =
Architectures
.layeredArchitecture()
.layer("domain layer")
.definedBy("com.company.app.domain..")
.layer("application layer")
.definedBy("com.company.app.application..")
.layer("adapters layer")
.definedBy("com.company.app.adapters..");
ここで、このアーキテクチャ定義を使用し、レイヤーに対してルールを定義することができます。ルールは、1つのテストに直接追加することも、次の例に示すように、個々のテストに保存されているインスタンス変数に追加することもできます。いずれの場合でも、whereLayerメソッドでレイヤー条件を追加します。
ArchRule applicationLayerRule =
portsAndAdaptersArchitecture
.whereLayer("application layer")
.mayOnlyBeAccessedByLayers("adapters layer");
ArchUnitバージョン0.11.0では、Jeffrey Palermo氏が言うところのオニオン・アーキテクチャのセマンティックを検証するための新しい事前定義APIであるArchitectures.onionArchitecture()が追加されています。オニオン・アーキテクチャは、ポート・アダプタ・アーキテクチャに関連しています。このAPIも他と同じように使うことができますが、それぞれのアダプタを定義する必要があります。
Architectures.OnionArchitecture onionArchitecture =
Architectures.onionArchitecture()
.domainModels("com.company.app.domain.model..")
.domainServices("com.company.app.domain.service..")
.applicationServices("com.company.app.application..")
.adapter("cli", "com.company.app.adapters.cli..")
.adapter("web", "com.company.app.adapters.web..");
ArchUnitのLibrary APIには、スライスを扱うものがあります。スライスは、基本的にJavaクラスのサブセットのルール定義です。各サブセットは、パッケージの挿入辞のパターンに一致します。スライスの作成と、スライスに対するアサーションの実行には、SlicesRuleDefinitionを使用します。その結果、Slices APIのSliceRuleオブジェクトが生成されます。SlicesRuleDefinitionビルダーを使用してSliceRuleを作成し、循環依存を検出することができます。すなわち、スライス間に循環が発生していないことや、個々のスライスが相互に依存していないことを確認できます。たとえば、次のSliceRuleは推移的依存性の検出に役立ちます。
SliceRule layersShouldBeFreeOfCycles =
SlicesRuleDefinition.slices()
.matching("com.company.app.(*)..")
.should().beFreeOfCycles();
SliceRule adaptersShouldNotDependOnEachOther =
SlicesRuleDefinition.slices()
.matching("com.company.app.adapters.(**)..")
.should().notDependOnEachOther();
ここでも、照合の表記法はAspectJの構文の影響を受けたものになっています。最初のルールでは、appの1階層下位にあるパッケージのクラスを対象として、スライス間に循環が発生していないことを確認します。2つ目のルールでは、adaptersのすべてのサブパッケージ(たとえば、..adapters.primary.web..や..adapters.secondary.mongodb..)内にあるJavaクラスをグループ化し、それらすべてのスライス間の相互依存性をチェックします。
たとえば、Webコントローラ(プライマリ・アダプタ)からMongoDBリポジトリ(セカンダリ・アダプタ)に直接アクセスしている場合、ArchUnitでは次のエラーが生成されます。このエラーは、上記の2つ目のルールに対するものです。
java.lang.AssertionError:Architecture Violation [Priority:MEDIUM] - Rule 'slices matching 'com.company.app.adapters.(**)..' should not depend on each other' was violated (1 times):
<strong>Slice primary.web calls Slice secondary.mongodb</strong>
先ほどの例でアスタリスクを1つだけ使ったとすれば、クラスはプライマリとセカンダリの2つのスライスにのみグループ化されたでしょう。エラー・メッセージにもそれが反映されることになります。
Library APIには、GeneralCodingRulesクラスも含まれています。このクラスには、多くのJavaプロジェクトで一般的に使われている静的コーディング・ルールが含まれ、いずれも自己記述的な名前で事前に定義されています。たとえば次のようなものです。
- NO_CLASSES_SHOULD_ACCESS_STANDARD_STREAMS(クラスから標準ストリームへのアクセスを禁止します。標準ストリームとは、System.outやSystem.err、そしてprintStackTraceメソッドを指します。代わりにロギング・ライブラリを使います)
- NO_CLASSES_SHOULD_THROW_GENERIC_EXCEPTIONS(クラスから汎用例外をスローすることを禁止します。たとえば、RuntimeExceptionをスローする代わりに、IllegalArgumentExceptionのようなカスタム例外を使います)
- NO_CLASSES_SHOULD_USE_JAVA_UTIL_LOGGING(クラスでJavaユーティリティのロギングの使用を禁止します。SLF4JのバックエンドにLog4jやLogbackを使う場合に指定します)
- NO_CLASSES_SHOULD_USE_JODATIME(クラスでJoda-Timeの使用を禁止します。最新のjava.time APIを使う場合に指定します)
テストを記述する際の留意事項
コードベースが大きい場合は、テストの際にクラス・ファイルのインポートをキャッシュすべきです。この手順により、テストの実行時間を大幅に削減できます。インポートしたクラスのキャッシュは、たとえばArchUnitRunnerで実現できます(JUnitサポートを使っている場合)。コードベースが小さい場合、クラスの再インポートによるオーバーヘッドは無視できます。
テスト対象のコンセプトは、可能な限り正確に記述された、ルールの要点を明確に伝えるものであることが重要です。複雑なルールの場合は、生成されるルール・テキストに依存するのではなく、明確でわかりやすいルール・テキストを使う方が理にかなっているでしょう。場合によっては、長いルール定義を、短くわかりやすい複数のルールに分割できることもあります。
また、実装されたルールに、何も考えずに従うべきではありません。同様に、あらかじめ考えることなく、簡単にテストを通過させるためにルールを変えるべきでもありません。ときには、新しいコンポーネントが既存のコンセプトに適合しないため、既存のルールの変更や拡張が必要になります。アーキテクチャが進化している場合、このようなことはよく起こります。しかし、前述のように、ArchUnitで提供されるテストを無視するオプションは、過剰に使用すべきではありません。未成熟なプロジェクトでは、特にそう言えます。
ArchUnitの代替プロダクトとその使用タイミング
言語レベルでは、Java 9で導入されたProject Jigsawのモジュール・システムが、意図せずにレイヤー間の依存性が発生することを防ぐために非常に役立ちます。モジュール依存性を明示的に指定するからです。残念ながら、この機能は既存のアプリケーションにはあまり役立ちません。Java 8以前を必要とする場合は、特にそうです。さらに、Jigsawで保証されるのはモジュール性とモジュール間の依存性だけです。これは品質の1つの側面でしかなく、ArchUnitで実行できるチェックです。
ArchUnitの代わりに使えるものに、jQAssistantがあります。このツールは、プロジェクトを分析し、生成された情報をNeo4jグラフ・データベースに格納するものです。jQAssistantではAsciiDocとの統合が提供されているため、テストをドキュメントに埋め込むことができます。ターゲット・アーキテクチャに移行しようとしている場合、これは実用的なツールです。商用プロダクトも存在します。その一例であるStructure101 Studioは、システムやアーキテクチャの監査に使われることが多い製品です。さらに、いくつかの静的コード解析ツールでは、ArchUnitで行うことの一部を実行できます。たとえば、DegraphやDeptectiveなどがあります。こういったツールの中には、ビルド環境や、場合によってはIDEに組み込めるものもあります。
まとめ
ArchUnitは小さなライブラリですが、アプリケーションの規模を問わず、アーキテクチャや内部コードの品質の単体テストに使用できます。ArchUnitにより、短時間で容易かつ実践的に、コードの品質目標テストを始めることができます。ArchUnitによってビルド・プロセスの間に行われるテストで、Javaアプリケーションのアーキテクチャが、定義されたルールに準拠していることを確認できます。流れるようなAPIと細かく記述されたJavadocドキュメントに加え、公式ガイドでは、コードベースのチェックのためにArchUnitで提供されているさまざまなオプションについて説明されています。
Java Magazine December 2019の他の記事
プロパティベース・テストを習得する
Arquillian:簡単なJakarta EEテスト
新しいJava Magazine
作ってみよう:自分だけのテキスト・エディタ(パート1)
クイズに挑戦:Collectorsの使用(上級者向け)
クイズに挑戦:ループ構造の比較(中級者向け)
クイズに挑戦:スレッドとExecutor(上級者向け)
クイズに挑戦:ラッパー・クラス(中級者向け)
書評:Core Java, 11th Ed. Volumes 1 and 2
![]() |
Jonas HaversJonas Havers(@JonasHavers):ドイツでフリーランスのフルスタック・ソフトウェア・エンジニアとソフトウェア・エンジニアリングの講師として活躍。主にeコマース関係のプロジェクトで、Java、Kotlin、Groovy、TypeScript、JavaScriptを組み合わせたWebアプリケーションを開発。リモート・ワークの支持者であるとともに、ブログで頻繁に発信している。 |

