1. 概要

単体テストは、ソフトウェアの設計と実装における重要なステップです。

これにより、コードの効率と有効性が向上するだけでなく、コードがより堅牢になり、将来の開発と保守におけるリグレッションが減少します。

このチュートリアルでは、Javaでの単体テストのいくつかのベストプラクティスについて説明します。

2. ユニットテストとは何ですか?

単体テストは、本番環境での使用に適したソースコードをテストする方法です。

ソースコードの個々のユニットの動作を検証するためのさまざまなテストケースを作成することから、単体テストの作成を開始します。

次に、完全なテストスイートが実行され、実装フェーズで、またはステージングや本番環境などの展開のさまざまな段階のパッケージを構築しているときに、リグレッションをキャッチします。

簡単なシナリオを見てみましょう。 まず、 Circle クラスを作成し、その中にcalculateAreaメソッドを実装しましょう。

public class Circle {

    public static double calculateArea(double radius) {
        return Math.PI * radius * radius;
    }
}

次に、 Circle クラスの単体テストを作成して、calculateAreaメソッドが期待どおりに機能することを確認します。

src / main /testディレクトリにCalculatorTestクラスを作成しましょう。

public class CircleTest {

    @Test
    public void testCalculateArea() {
        //...
    }
}

この場合、テストを実行するために、JUnitの@TestアノテーションMavenGradleなどのビルドツールを使用しています。

3. ベストプラクティス

3.1. ソースコード

テストクラスをメインのソースコードから分離しておくことをお勧めします。 したがって、は、製品コードとは別に開発、実行、および保守されます。

また、本番環境でテストコードを実行する可能性を回避します。

テスト実装のsrc/main/testディレクトリを探すMavenやGradleなどのビルドツールの手順に従うことができます。

3.2. パッケージ命名規則

テストクラスのsrc/ main /testディレクトリに同様のパッケージ構造を作成する必要があります。 したがって、テストコードの読みやすさと保守性が向上します。

簡単に言うと、テストクラスのパッケージは、ソースコードの単位がテストされるソースクラスのパッケージと一致する必要があります。

たとえば、Circleクラスがcom.baeldung.mathパッケージに存在する場合、CircleTestクラスもcom.baeldungに存在する必要があります。 src / main /testディレクトリ構造の下のmathパッケージ。

3.3. テストケースの命名規則

テスト名は洞察に満ちたである必要があり、ユーザーは名前自体を一瞥するだけでテストの動作と期待を理解する必要があります。

たとえば、前述したように、単体テストの名前は testCalculateArea でした。これは、テストシナリオと期待に関する意味のある情報を漠然と提供します。

したがって、 testCalculateAreaWithGeneralDoubleValueRadiusThatReturnsAreaInDouble、testCalculateAreaWithLargeDoubleValueRadiusThatReturnsAreaAsInfinityなどのアクションと期待値を使用してテストに名前を付ける必要があります。

ただし、読みやすくするために名前を改善することはできます。 多くの場合、ユニットテストの目的を詳しく説明するために、given_when_thenでテストケースに名前を付けると便利です。 例えば:

public class CircleTest {

    //...

    @Test
    public void givenRadius_whenCalculateArea_thenReturnArea() {
        //...
    }

    @Test
    public void givenDoubleMaxValueAsRadius_whenCalculateArea_thenReturnAreaAsInfinity() {
        //...
    }
}

単体テストの名前だけでなく、は、Given、When、Then形式でコードブロックを記述する必要があります。 さらに、は、テストを入力、アクション、および出力の3つの部分に区別するのに役立ちます。

まず、 give セクションに対応するコードブロックがテストオブジェクトを作成し、データをモックし、入力を調整します。

次に、whenセクションのコードブロックが特定のアクションまたはテストシナリオを表します。 同様に、 then セクションは、アサーションを使用して期待される結果に対して検証されるコードの出力を示します。

3.4. 期待される対。 実際

テストケースには、期待値と実際の値の間にアサーションが必要です。 期待される対の考えを裏付けるために。 実際の値については、JUnitのAssertクラスassertEqualsメソッドの定義を確認できます。

public static void assertEquals(Object expected, Object actual)

すでに説明したテストケースの1つでアサーションを使用してみましょう。

@Test 
public void givenRadius_whenCalculateArea_thenReturnArea() {
    double actualArea = Circle.calculateArea(1d);
    double expectedArea = 3.141592653589793;
    Assert.assertEquals(expectedArea, actualArea); 
}

テストコードの読みやすさを向上させるために、変数名の前に実際のキーワードと期待されるキーワードを付けることをお勧めします。

3.5. シンプルなテストケースを好む

前のテストケースでは、期待値がハードコーディングされていることがわかります。 これは、テストケースで実際のコード実装を書き直したり再利用したりして、期待値を取得しないようにするために行われます。

calculateAreaメソッドの戻り値と一致するように円の面積を計算することはお勧めしません。

@Test 
public void givenRadius_whenCalculateArea_thenReturnArea() {
    double actualArea = Circle.calculateArea(2d);
    double expectedArea = 3.141592653589793 * 2 * 2;
    Assert.assertEquals(expectedArea, actualArea); 
}

このアサーションでは、同様のロジックを使用して期待値と実際の値の両方を計算しているため、結果は永久に同じになります。 したがって、私たちのテストケースには、コードの単体テストに付加価値はありません。

したがって、実際の期待値に対してハードコードされた期待値をアサートする単純なテストケースを作成する必要があります。

テストケースでロジックを作成する必要がある場合もありますが、やりすぎないようにしてください。 また、よく見られるように、アサーションに合格するためにテストケースにプロダクションロジックを実装しないでください

3.6. 適切なアサーション

いつも適切なアサーションを使用して、期待される対を検証します。 実績JUnitAssertクラスまたはAssertJのような同様のフレームワークで利用可能なさまざまなメソッドを使用する必要があります。

たとえば、値のアサーションにはすでにAssert.assertEqualsメソッドを使用しています。 同様に、 assertNotEquals を使用して、期待値と実際の値が等しくないかどうかを確認できます。

assertNotNull assertTrue assertNotSame などの他のメソッドは、個別のアサーションで役立ちます。

3.7. 特定の単体テスト

同じ単体テストに複数のアサーションを追加する代わりに、個別のテストケースを作成する必要があります。

もちろん、同じテストで複数のシナリオを検証したくなることもありますが、それらを別々に保つことをお勧めします。 次に、テストが失敗した場合、どの特定のシナリオが失敗したかを判断するのが簡単になり、同様に、コードを修正するのも簡単になります。

したがって、常に単体テストを記述して、単一の特定のシナリオをテストします

単体テストは、理解するのに過度に複雑になることはありません。 さらに、後で単体テストをデバッグおよび保守する方が簡単です。

3.8. 本番シナリオのテスト

実際のシナリオを念頭に置いてテストを作成する場合、単体テストはよりやりがいがあります。

主に、単体テストの関連性を高めるのに役立ちます。 また、特定の実稼働ケースでのコードの動作を理解する上で不可欠であることがわかります。

3.9. 模擬外部サービス

単体テストは特定の小さなコードに集中しますが、コードが一部のロジックの外部サービスに依存している可能性があります。

したがって、外部サービスをモックし、さまざまなシナリオのコードのロジックと実行をテストするだけです。

Mockito EasyMock JMockit などのさまざまなフレームワークを使用して、外部サービスをモックすることができます。

3.10. コードの冗長性を回避する

ますます多くのヘルパー関数を作成して、一般的に使用されるオブジェクトを生成し、同様の単体テスト用にデータまたは外部サービスをモックします。

他の推奨事項と同様に、これにより、テストコードの読みやすさと保守性が向上します。

3.11. 注釈

多くの場合、テストフレームワークは、セットアップの実行、前のコードの実行、テストの実行後の破棄など、さまざまな目的でアノテーションを提供します。

JUnitの@Before、@ BeforeClass、@ After などのさまざまなアノテーションや、TestNGなどの他のテストフレームワークからのアノテーションを自由に使用できます。

アノテーションを活用して、テスト用にシステムを準備する必要があります。データを作成し、オブジェクトを配置し、テストごとにすべてを削除して、テストケースを互いに分離します。

3.12. 80%のテストカバレッジ

ソースコードテストカバレッジを増やすことは常に有益です。 ただし、達成する目標はそれだけではありません。 十分な情報に基づいた決定を下し、実装、期限、およびチームに適したより適切なトレードオフを選択する必要があります。

原則として、ユニットテストでコードの80% oをカバーするようにしてください。

さらに、JaCoCoCoberturaなどのツールをMavenまたはGradleと一緒に使用して、コードカバレッジレポートを生成できます。

3.13. TDDアプローチ

テスト駆動開発(TDD)は、実装前および実装中にテストケースを作成する方法論です。 このアプローチは、ソースコードの設計と実装のプロセスと結びついています。

利点には、最初からテスト可能な本番コード、簡単なリファクタリングによる堅牢な実装、およびより少ないリグレッションが含まれます。

3.14. オートメーション

新しいビルドを作成しながら、テストスイート全体の実行を自動化することで、コードの信頼性を向上させることができます。

主に、これはさまざまなリリース環境での不幸なリグレッションを回避するのに役立ちます。 また、壊れたコードがリリースされる前に迅速なフィードバックを保証します。

したがって、ユニットテストの実行はCI-CDパイプラインの一部であり、誤動作が発生した場合に関係者に警告する必要があります。

4. 結論

この記事では、Javaでの単体テストのいくつかのベストプラクティスについて説明しました。 ベストプラクティスに従うことは、ソフトウェア開発の多くの側面で役立ちます。