JUnitは、JavaおよびKotlinエコシステムにおける単体テストの標準フレームワークであり、Android開発において独立したコンポーネントレベルでビジネスロジックを検証するために使用されます。このフレームワークは、アノテーション、アサーションメソッド、Test Runnerのセットを提供し、テストの自動検出と実行を行います。JUnit.orgによると、このライブラリはJVMエコシステムにおける単体テストの最も人気のあるソリューションであり続けており、70%以上のJavaプロジェクトが何らかのバージョンのJUnitを使用しています。
主要ポイント
JUnitは、JavaおよびKotlinで反復可能な単体テストを作成および実行するためのオープンソースフレームワークです。これはxUnitエコシステムの一部であり、Kent BeckとErich Gammaのアーキテクチャに基づくフレームワークファミリーで、Android StudioおよびIntelliJ IDEAの標準テストツールです。
JUnitの主な役割は、コードの小さな断片(メソッド、クラス)を分離し、制御された条件下でその動作を検証することです。テストはアノテーション付きの通常のJava/Kotlinクラスとして記述され、フレームワークがテストの検出、ライフサイクル管理、実行統計の収集を処理します。
JUnitの最初のバージョンは1997年に登場し、テスト駆動開発(TDD)のプラクティスを普及させることで開発アプローチを根本的に変えました。現在、JUnit 5(Jupiter)が最新バージョンであり、Java 8+と拡張機能をサポートするモジュラーアーキテクチャで完全に再設計されています。
JUnitの実行メカニズムはTest Runnerパターンに基づいており、classpathをスキャンし、@Testアノテーションが付いたメソッドを見つけ、テストクラスのインスタンスを作成し、特定の順序でメソッドを実行します。Test Runnerはライフサイクルを管理します:BeforeAll → BeforeEach → Test → AfterEach → AfterAll。
JUnitは各テストメソッドを個別のクラスインスタンスにラップし、テスト間の分離を保証します。つまり、クラスのフィールドは異なる@Testメソッド間で状態を保持せず、各テストはクリーンなオブジェクトから開始されます。
JUnitの最も単純なテストは、@Testアノテーションが付いたメソッドで、テスト対象のコードを呼び出し、アサーションを使用して結果を検証します。アサーションが失敗した場合、テストは失敗と見なされ、JUnitがエラーを報告します。
@Test
void additionShouldReturnCorrectSum() {
var calculator = new Calculator();
var result = calculator.add(2, 3);
assertEquals(5, result);
}
Test Runnerは自動的にそのようなメソッドを見つけ、実行し、結果を報告します。メソッドが例外をスローしなければテストは成功(green)、アサーションが失敗するとテストは失敗(red)します。
JUnitのアノテーションは、テストコードをいつどのように実行するかを定義します。JUnit 5では、アノテーションはorg.junit.jupiter.apiパッケージにあり、データ準備、テスト実行、リソースクリーンアップのすべての段階をカバーしています。
| アノテーション | 目的 | 実行タイミング |
|---|---|---|
| @Test | テストメソッドをマーク | 呼び出しごとに1回 |
| @BeforeEach | 各テスト前のセットアップ | 各@Testの前 |
| @BeforeAll | クラスの1回限りの初期化 | 全テストの前に1回 |
| @AfterEach | 各テスト後のクリーンアップ | 各@Testの後 |
| @AfterAll | クラスの1回限りの終了処理 | 全テストの後に1回 |
| @DisplayName | 人間が読めるテスト名 | 装飾 |
適切なライフサイクルを持つ完全なテストクラスを考えてみましょう。setUpメソッドは各テストの前に新しいCalculatorインスタンスを作成し、tearDownはリソースを解放します(例:ファイル記述子やデータベース接続のクローズ)。
class CalculatorTest {
private Calculator calculator;
@BeforeEach
void setUp() {
calculator = new Calculator();
}
@Test
void subtractionShouldReturnCorrectResult() {
int result = calculator.subtract(10, 4);
assertEquals(6, result);
}
@AfterEach
void tearDown() {
calculator.reset();
}
}
アサーションは、実際の結果を期待される結果と比較し、一致しない場合に例外をスローする静的メソッドです。JUnit 5はorg.junit.jupiter.api.Assertionsクラスを提供し、単純な値の比較からタイムアウトチェックやグループ化されたアサーションまで、さまざまな検証シナリオのための25以上のメソッドがあります。
assertThrowsメソッドは特に注目に値します。コードがゼロ除算、nullの受け渡し、制限超過などのエラー状況を正しく処理するかどうかを検証できます。
@Test
void divisionByZeroShouldThrowException() {
Calculator calc = new Calculator();
ArithmeticException exception = assertThrows(
ArithmeticException.class,
() -> calc.divide(10, 0)
);
assertEquals("Cannot divide by zero", exception.getMessage());
}
パラメータ化テストを使用すると、同じテストメソッドを異なる引数セットで実行できます。JUnit 5では、@ParameterizedTestアノテーションをデータソース(@ValueSource、@CsvSource、@MethodSource、@EnumSource)と組み合わせて使用します。
このアプローチはコードの重複を大幅に削減します。異なる値に対して10個の同一テストを書く代わりに、1つのパラメータ化されたメソッドを記述します。Google Testing Blogによると、パラメータ化によりカバレッジを損なうことなくテストコード量が40~60%削減されます。
@CsvSourceアノテーションは、カンマ区切りの複数行をテストに渡します。各行が1回のテスト実行に対応します。JUnitは自動的に文字列値を必要な型(int、long、Stringなど)に変換します。
@ParameterizedTest
@CsvSource({
"1, 1, 2",
"2, 3, 5",
"10, 20, 30",
"-1, 1, 0"
})
void additionWithMultipleInputs(int a, int b, int expected) {
assertEquals(expected, a + b);
}
入力データが単純な数値や文字列より複雑な場合は、@MethodSourceを使用します。これは引数のStreamを返す静的メソッドを参照します。例えば、DAO層をテストするためのArgumentsのStreamやUserオブジェクトのStreamなどです。
Android開発では、JUnitはAndroidX Testと併用され、Activity、Content Provider、その他のAndroidコンポーネントをテストするための拡張機能を提供します。JUnitはエミュレータなしでJVM上で実行される単体テストを担当し、AndroidX Testはデバイス上のインストゥルメントテストを担当します。
AndroidプロジェクトにJUnit 5を追加するには、モジュールレベルのbuild.gradleにorg.junit.jupiter:junit-jupiter依存関係を含めるだけで十分です。AGP Gradleプラグインは、標準のTest Runnerを介してJVM上でJUnit 5テストを実行することをサポートしています。
// build.gradle.kts
android {
testOptions {
unitTests.isIncludeAndroidResources = true
}
}
dependencies {
testImplementation("org.junit.jupiter:junit-jupiter:5.11.0")
testImplementation("androidx.test:core-ktx:1.6.1")
}
実際には、JUnitはAndroidコンテキストやエミュレータを必要としないViewModelやリポジトリのテストに最もよく使用されます。このようなテストはミリ秒で実行され、時間を無駄にすることなく何百回も実行できます。
JUnit 5(Jupiter)は単なる新しいバージョンではなく、3つのモジュールに分割された完全に再設計されたプラットフォームです:JUnit Platform(JVM上でのテスト実行)、JUnit Jupiter(テスト作成のためのAPI)、JUnit Vintage(JUnit 4との後方互換性)。このモジュール性により、さまざまなTest Engine(例:Kotlin用のSpekやTestNG)を接続できます。
| 特徴 | JUnit 4 | JUnit 5 |
|---|---|---|
| パッケージ | org.junit | org.junit.jupiter |
| 基本アノテーション | @Test(junit.framework) | @Test(org.junit.jupiter.api) |
| Before/After | @Before、@After、@BeforeClass | @BeforeEach、@AfterEach、@BeforeAll |
| パラメータ化 | @RunWith(Parameterized.class) | @ParameterizedTest + @ValueSource |
| 拡張 | @Rule、@ClassRule | @ExtendWith、より柔軟なAPI |
| Java最小 | Java 5 | Java 8+ |
JUnit 4からJUnit 5への移行は、すべてのテストを書き換える必要はありません。JUnit Vintage Engineを追加するだけで、古いテストは引き続き動作します。新しいテストはJUnit 5で記述することをお勧めします。拡張機能、ラムダアサーション、組み込みのパラメータ化サポートを活用できます。
よくある質問
JUnitはテストを作成して実行するためのフレームワークであり、Mockitoはモックオブジェクトを作成するためのライブラリです。これらは一緒に使用されます。JUnitがテスト実行を管理し、Mockitoがテスト対象クラスの依存関係を置き換えます。
はい、JUnit 5はAndroidプロジェクトと完全に互換性があります。単体テストの場合は、build.gradleにjunit-jupiter依存関係を追加するだけです。インストゥルメントテストは引き続きAndroidX Test Runnerを介して動作します。
最低限、メソッドの前に1つの@Testアノテーションだけで十分です。初期化とクリーンアップには@BeforeEachと@AfterEachが推奨されますが、必須ではありません。
Test Runnerは、classpathをスキャンし、@Testアノテーションが付いたメソッドを見つけ、テストクラスのインスタンスを作成してテストを実行するJUnitのコンポーネントです。JUnit 5では、この役割は接続されたTest EngineとともにJUnit Platformが担います。
assertThrows(Class, Executable)を使用します。これは期待される例外タイプとメソッド呼び出しを含むラムダを受け取ります。JUnitは例外が実際にスローされたことを検証し、さらに検査するためにそれを返します。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。