チュートリアルの第 6 章で作成した署名コンポーネントの Sling Model の動作を検証する、単体テストの実装について説明します。

前提条件

これは、複数のパートで構成されているチュートリアルの第 8 章です。第 7 章はこちら概要はこちらから確認できます

GitHub で完成済みコードを参照するか、ソリューションパッケージをダウンロードできます。

ダウンロード

単体テスト

この章では、アドビの署名コンポーネントで Sling Model第 6 章で作成したもの)向け単体テストの作成方法について説明します。単体テストは、Java コードの期待される動作を検証するために Java で記述されるビルド時間テストです。各単体テストは一般的に小さく、期待される結果に対するメソッドの成果(または作業のユニット)を検証します。

ここでは、AEM ベストプラクティスおよび以下を使用します。


単体テストおよび AEM 向け Cloud Manager

AEM 向け Cloud Manager は、AEM コードの単体テストのベストプラクティスを推奨および促進するために、単体テストの実施とコード有効範囲ポートを CI/CD パイプラインに統合します。

コードの単体テストはあらゆるコードベースで有益ですが、Cloud Manager を使用している場合は、Cloud Manager で実行できる単体テストを提供して、コード品質のテストや報告機能を活用することが重要です。

テスト Maven 依存関係の追加

最初に、テストの記述と実行をサポートする Maven 依存関係を追加します。必要な依存関係は 4 つです。

  1.  JUnit4
  2. Mockito テストフレームワーク
  3. Apache Sling Mocks
  4. テストフレームワークの wcm.io

JUnit4、Mockito および Sling Mocks の依存関係は、AEM Maven archetype を使用してセットアップする際、プロジェクトに自動追加されます(以下のように、Sling Mocks 依存関係バージョンは更新する必要があります)。

io.wcm テストフレームワークの依存関係を、プロジェクトのpom.xmlsに追加する必要があります。

 

  1. これらの依存関係を追加するには、aem-guides-wknd/pom.xml を開き、<dependencies>..</dependencies> へ移動して、次の依存関係が定義されていることを確認します。io.wcm 依存性を相互に追加する必要があります。JUnit および Mockito の依存関係は、Adobe AEM Maven Archetype によって以前に追加されています。

    <dependencies>
        ...
        <dependency>
            <groupId>io.wcm</groupId>
            <artifactId>io.wcm.testing.aem-mock.junit4</artifactId>
            <version>2.3.2</version>
            <scope>test</scope>
        </dependency>             
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-core</artifactId>
            <version>2.7.22</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>junit-addons</groupId>
            <artifactId>junit-addons</artifactId>
            <version>1.4</version>
            <scope>test</scope>
        </dependency>
        ...
    </dependencies>
  2. リアクター pom.xml で、org.apache.sling.testing.sling-mock 依存関係が 2.3.4 以上であることを確認します。

    <dependencies>
    ...
        <dependency>
            <groupId>org.apache.sling</groupId>
            <artifactId>org.apache.sling.testing.sling-mock</artifactId>
            <version>2.3.4</version>
            <scope>test</scope>
        </dependency>
    ...
    </dependencies>

    org.apache.sling.testing.sling-mock 依存関係のバージョンが低すぎる場合、AemContex の io.wcmは最後のメソッド currentResource(...) をオーバーライドしようとする例外をスローします。

  3. aem-guides-wknd/core/pom.xml を開き、必要に応じて対応するエントリを追加します。

    core-pom-dependencies
    core/pom.xml

    core プロジェクトの並列ソースフォルダーには、単体テストと、サポートするテストファイルが含まれます。この test フォルダーはテストクラスをソースコードから分離しますが、ソースコードと同じパッケージ内にあるようにテストを動作させることができます。

JUnit テストの作成

core-src-test-folder
コアプロジェクトのテストフォルダー(core/src/test)

単体テストは一般的に、Java クラスと 1 対 1 でマッピングします。この章では、署名コンポーネントを支える Sling Model である BylineImpl.java 用に JUnit テストを記述します。

  1. テストする Java クラスを右クリックし、新規/その他/Java/JUnit/JUnit テストケースを選択して Eclipse でこれを実行できます。

    現在位置が core プロジェクトのコンテキスト内(親 aem-guides-wknd リアクタープロジェクトではなく)であることを確認します。

    junit-test-case-1
    新しい JUnit テストケース

    最初のウィザード画面で以下を検証します。

    1. JUnit テストタイプが New JUnit 4 Test (pom.xml で設定された JUnit Maven 依存関係)となっていること。
    2. package がテスト対象のクラスの java パッケージ(BylineImpl.java)となっていること。
    3. ソースフォルダーが core プロジェクトを示し、Eclipse に単体テストファイルの保存先を指定すること。
    4. setUp() メソッドスタブは手動で作成されること。方法については後で説明します。
    5. テストの下のクラスが BylineImpl.java であること。これが、テストしたい Java クラスです。
    junit-test-case-2
    JUnit テストケースウィザード
  2. ウィザード下部にある「次へ」ボタンをクリックします。

    次のステップは、テストメソッドの自動生成に役立ちます。一般的に、Java クラスの各パブリックメソッドには、動作を検証する、対応するテストメソッドが 1 つ以上あります。単体テストには 1 つのパブリックメソッドをテストする複数のテストメソッドがあり、それぞれが異なる入力または状態を表すということがよくあります。

    ウィザードで、BylineImpl の下のすべてのメソッドを選択します。ただし、Sling Model によって既に内部で使用されている(@PostConstruct 経由)メソッドである init() を除きます。他のメソッドは正常に実行される init() に依存しているので、その他すべてのメソッドをテストすることで、init() を効果的にテストします。

    新しいテストメソッドはいつでも JUnit テストクラスに追加できます。ウィザードのこのページは便宜上のものです。

    junit-test-case-3
    JUnit テストケースウィザード(続き)
  3. JUnit4 テストファイルを生成するには、ウィザードの下部にある「終了」ボタンをクリックします。

  4. JUnit4 テストファイルが、aem-guides-wknd.core/src/test/java 上で、BylineImplTest.java という名前のファイルとして、対応するパッケージ構造で作成されたことを確認します。

BylineImplTest.java のレビュー

テストファイルには多数の自動生成メソッドがあります。この時点で、この JUnit テストファイルには AEM 固有のものはありません。

最初のメソッドは public void setUp() { ..}で、注釈 @Before が付けられています。

@Before 注釈は、JUnit テスト実行に対し、このクラスで各テストメソッドを実行する前にこのメソッドを実施するよう指示する JUnit 注釈です。

それ以降のメソッドはテストメソッドであり、@Test 注釈でそのようにマークされます。デフォルトでは、すべてのテストが失敗するように設定されています。

この JUnit (または JUniit テストケース)が実行している場合、@Test とマークされた各メソッドは、成功/失敗する可能性があるテストとして実行されます。

bylineimpltest-new
core/src/test/java/com/adobe/aem/guides/wknd/core/components/impl/BylineImplTest.java
  1. クラス名を右クリックし、実行ユーザー/JUnit テストを選択して、JUnit テストケースを実行します。

    run-as-junit-test
    BylineImplTests.java /実行ユーザー/JUnit テストを右クリックします。
  2. 想定どおり、すべてのテストは失敗します。

    all-tests-fail
    Eclipse/Window/ビューを表示/Java/JUnit の JUnit ビュー

BylineImpl.java のレビュー

単体テストを作成する際の主なアプローチは次の 2 つです。

  • TDD またはテスト駆動開発。実装を開発する直前に単体テストの増分を記述、テストを記述、実装を記述してテストを合格します。
  • 最初に実装をおこなう開発。動作するコードを最初に開発してから、そのコードを検証するテストを記述します。

このチュートリアルでは、後者のアプローチを使用します(前の章で動作する BylineImpl.java を作成済みのため)。このため、パブリックメソッドの動作だけでなく、いくつかの実装の詳細についても確認および理解しておく必要があります。優れたテストは入力と出力のみを重視する必要があるので、理屈に合わないと思われるかもしれません。AEM で作業する際には、動作するテストを構築するために、実装に関する様々な考慮事項を理解しておく必要があります。

AEM における TDD には高度な専門知識が必要です。AEM 開発や AEM コードの単体テストを熟知した AEM 開発者が使用することで最大限の効果を発揮できます。


AEM テストコンテキストの設定

AEM で記述されるコードの大部分は JCR、Sling または AEM API に依存しているので、正常に実行するためには実行中の AEM のコンテキストが必要となります。 

単体テストは実行中の AEM インスタンスのコンテキスト外にあるビルドで実施されるので、このようなリソースはありません。これを促すために、io.wcm の AEMContext は、これらの API が、ほぼ AEM 内で実行しているかのように動作できるモックコンテキストを作成します。

  1. BylineImplTest.java 内で io.wcm の AemContext を使用し、@Rule で修飾されるクラス変数として追加することで、AEM コンテキストを作成します。

    import org.junit.Rule;
    import io.wcm.testing.mock.aem.junit.AemContext;
    ...
    @Rule
    public final AemContext ctx = new AemContext();

    この変数「ctx」は、多数の AEM および Sling 抽象を提供するモック AEM コンテキストを提供します。

    • BylineImpl Sling Model はこのコンテキストに登録されます。
    • モック JCR コンテンツ構造はこのコンテキストで作成されます。
    • カスタム OSGi サービスはこのコンテキスト内で登録できます。
    • 一般的に必要となる様々なモックオブジェクトおよびヘルパー(SlingHttpServletRequest オブジェクトなど)、様々なモック Sling および AEM OSGi サービス(ModelFactory、PageManager、ページ、テンプレート、ComponentManager、コンポーネント、TagManager、タグなど)を提供します。
      • これらのオブジェクトのすべてのメソッドが実装されるわけではありません。
    • その他

    ctx オブジェクトは、ほとんどのモックコンテキストのエントリポイントとして機能します。

  2. setUp(..) メソッド(各 @Test メソッドの前に実行される)で、一般的なモックテスト状態を定義します。

    @Before
    public void setUp() throws Exception {
        ctx.addModelsForClasses(BylineImpl.class);
                
        ctx.load().json("/com/adobe/aem/guides/wknd/core/components/impl/BylineImplTest.json", "/content");
    }

    3 行目はテスト対象の Sling Model をモック AEM コンテキストに登録して、@Test メソッドでインスタンス化できるようにします。
    5 行目はリソース構造をモックコンテキストに読み込み、コードはこれらのリソースを、実際のリポジトリで提供されたかのように操作できるようになります。ファイル BylineImplTest.json のリソース定義は、/content の下でモック JCR コンテキストに読み込まれます。

    BylineImplTest.json がまだないので、作成してテストに必要な JCR リソース構造を定義しましょう。

  3. 新しい core/src/test/resources フォルダーを作成し、モックリソース構造を表す JSON ファイルを含めます。慣例により、どの JSON ファイルがどのテストをサポートするかを明確にするために、リソース構造は Java のパッケージ構造に従います。

    new-resources-folder
    core/Src/テストを右クリックし、新規/フォルダーを右クリックします。
  4. モックリソース構造を表す JSON ファイルは、JUnit Java テストファイルと同じパッケージパスに従い、core/src/test/resources 配下に保存されます。

    JSON ファイルの命名(BylineImplTest.java)は任意ですが、どの単体テストをサポートしているかが明確になるように名前を付けると良いでしょう。

    core/test/resources/com/adobe/aem/guides/wknd/core/components/impl/BylineImplTest.json に新しい JSON ファイルを作成し、次のコンテンツを含めます。

    {
      "byline": {
        "jcr:primaryType": "nt:unstructured",
        "sling:resourceType": "wknd/components/content/byline"
      }
    }

    この JSON は、署名コンポーネント単体テストのモックリソースを定義します。この時点で、JSON には、署名コンポーネントコンテンツリソースを表す最小限のプロパティのセットである jcr:primaryType および sling:resourceType のみが含まれます。

    単体テストで作業する際の一般的なルールは、各テストに必要な最小限のモックコンテキスト、コンテンツ、およびコードのセットを作成することです。テストを記述する前に、完全なモックコンテキストを構築したくなりますが、不要なアーティファクトが生成される結果になることが多いので、避けてください。

    BylineImplTest.json の存在により、ctx.json("/com/adobe/aem/guides/wknd/core/components/impl/BylineImplTest.json", "/content") を実行すると、モックリソース定義はパス /content
    にあるコンテキストに読み込まれます。

getName() のテスト

基本的なモックコンテキストの設定が完了したところで、BylineImpl's getName() の最初のテストを作成しましょう。このテストでは、メソッド getName() がリソースの "name" プロパティに保存されている、作成された正しい名前を返すことを確認する必要があります。

  1. 次のように、BylineImplTest.javatestGetName() メソッドを更新します。

    import com.adobe.aem.guides.wknd.core.components.Byline;
    ...
    @Test
    public void testGetName() {
        final String expected = "Jane Doe";
    
        ctx.currentResource("/content/byline");
        Byline byline = ctx.request().adaptTo(Byline.class);
        
        String actual = byline.getName();
    
        assertEquals(expected, actual);
    }

    3 行目は期待値を設定します。  ここでは、これを "Jane Done" に設定します。

    5 行目はコードの評価をおこなうモックリソースのコンテキストを設定します。そのため、これはモック署名コンテンツリソースの読み込み先となる /content/byline に設定されます。

    6 行目は、モックリクエストオブジェクトから適応させて署名 Sling Model をインスタンス化します。

    8 行目は、署名 Sling Model オブジェクト上で、テストするオブジェクトである getName() を呼び出します。

    10 行目は、期待される値が、署名 Sling Model オブジェクトで返される値と一致することをアサートします。これらの値が等しくない場合、テストは失敗します。

  2. テストを実行しますが、失敗します。

    testgetname-failure-npe
    NullPointedException による testGetName() の失敗

    このテストは、テスト失敗の原因となるモック JSON で "name" プロパティを定義していないから失敗したのではありません。しかし、テストの実施ではそこまではわかりません。このテストは、署名オブジェクト自体の上にある NullPointerException が原因で失敗します。

  3. 上記のBylineImpl.java のレビュービデオでは、@PostConstruct init() が例外をスローして Sling Model のインスタンス化を防ぐ方法について説明しました。ここではこれが発生しています。

    @PostConstruct
    private void init() {
        image = modelFactory.getModelFromWrappedRequest(request, request.getResource(), Image.class);
    }

    ModelFactory OSGi サービスは AemContext 経由で(Apache Sling コンテキストで)提供されますが、BylineImpl の init() メソッドで呼び出される getModelFromWrappedRequest(...) を含み、すべてのメソッドが実装されるわけではないということがわかります。この結果 AbstractMethodError が返されます。つまり、init() が失敗し、結果として適応した ctx.request().adaptTo(Byline.class) は null オブジェクトとなります。

    提供されたモックはコードに対応できないので、自分たちでモックコンテキストを実装する必要があります。この場合、Mockito を使用して、getModelFromWrappedRequest(...) が呼び出されたときにモック Image オブジェクトを返すモック ModelFactory オブジェクトを作成できます。

    署名 Sling Model を均等にインスタンス化するようこのモックコンテキストを配置する必要があるので、@Before setUp() メソッドに追加できます。また、BylineImpleTest クラスの上に @RunWith(MockitoJUnitRunner.class) 注釈を追加する必要があります。

    import org.junit.runner.RunWith;
    import org.mockito.junit.MockitoJUnitRunner;
    import org.mockito.Mock;
    import com.adobe.cq.wcm.core.components.models.Image;
    import org.apache.sling.models.factory.ModelFactory;
    import static org.mockito.Mockito.*;
    ...
    @RunWith(MockitoJUnitRunner.class)
    public class BylineImplTest {
    
    	@Rule
    	public final AemContext ctx = new AemContext();
    	
    	@Mock
    	private Image image;
    	
    	@Mock 
    	private ModelFactory modelFactory;
    	
    	@Before
    	public void setUp() throws Exception {
    		ctx.addModelsForClasses(BylineImpl.class);
              
    		ctx.load().json("/com/adobe/aem/guides/wknd/core/components/impl/BylineImplTest.json", "/content");
    		             
    		when(modelFactory.getModelFromWrappedRequest(eq(ctx.request()), 
    				any(Resource.class),
    				eq(Image.class))).thenReturn(image);
    
    		ctx.registerService(ModelFactory.class, modelFactory, 
    				org.osgi.framework.Constants.SERVICE_RANKING, Integer.MAX_VALUE);
    	}
            ...

    8 行目は、テストケースクラスを MockitoJUnitRunner とともに実行するようマークし、クラスレベルでモックオブジェクトを定義する @Mock 注釈の使用を許可します。

    14~15 行目は type com.adobe.cq.wcm.core.components.models.Image のモックオブジェクトを作成します。これはクラスレベルで定義され、必要に応じて @Test メソッドの動作を変更できます。
    11 行目は ModelFactory のモックオブジェクトを作成します。これは純粋な Mockito モックであり、メソッドは実装されません。これはクラスレベルで定義され、必要にに応じて @Test メソッドの動作を変更できます。
    26~28 行目は、getModelFromWrappedRequest(..) がモック ModelFactory オブジェクトで呼び出されたときの動作を登録します。thenReturn(..) では、モック Image オブジェクトを返すよう結果が定義されています。この動作は、最初のパラメーターが ctxリクエストオブジェクトと等しく、2 番目のパラメーターがリソースオブジェクトで、かつ 3 番目のパラメーターがコアコンポーネントの Image クラスである場合にのみ呼び出されます。テストを通じて、ctx.currentResource(...) を、BylineImplTest.json で定義した様々なモックリソースに設定することになるので、どのリソースでも使用できます。

    30~31 行目は、モック ModelFactory オブジェクトを最高のサービスランキングで AemContext に登録します。BylineImpl の init() で使用される ModelFactory は @OSGiService ModelFactory model フィールド経由で挿入されるので、これが必要になります。AemContext が getModelFromWrappedRequest(..) への呼び出しを処理する モックオブジェクトを挿入するには、そのタイプ(ModelFactory)で最高ランクのサービスとして登録する必要があります。

  4. テストを再実行すると再び失敗しますが、今回は失敗の理由が明白です。

    testgetname-failure-assertion
    アサーションによる testGetName() の失敗

    AssertionError が返されます。これはテストでのアサート条件が失敗し、期待値は "Jane Doe" で、実際の値が null なことを示します。BylineImplTest.json のモック /content/byline リソース定義に "name" プロパティが追加されていないので、この結果は当然です。そこで、これを追加します。

  5. BylineImplTest.json を更新し、"name": "Jane Doe" を定義します。

    {
      "byline": {
        "jcr:primaryType": "nt:unstructured",
        "sling:resourceType": "wknd/components/content/byline",
        "name": "Jane Doe"
      }
    }
  6. テストを再実行すると、今回は testGetName() が成功します。

    testgetname-success
    testGetName() は成功する

getOccupations() のテスト

成功です。最初のテストはうまくいきました。先へ進み、getOccupations() をテストします。モックコンテキストの初期化は @Before setUp() メソッドでおこなわれたので、このテストケースのすべての @Test メソッド(getOccupations() を含む)で利用できるようになります。

このメソッドは、職業プロパティに保存されている職業のリストをアルファベット順(降順)に並べ替えて返します。

  1. testGetOccupations() を次のように更新します。

    import java.util.List;
    import com.google.common.collect.ImmutableList;
    ...
    @Test
    public void testGetOccupations() {
        List<String> expected = new ImmutableList.Builder<String>()
                                .add("Blogger")
                                .add("Photographer")
                                .add("YouTuber")
                                .build();
    
        ctx.currentResource("/content/byline");
        Byline byline = ctx.request().adaptTo(Byline.class);
        
        List<String> actual = byline.getOccupations();
    	    
        assertEquals(expected, actual);
    }

    6~10 行目は期待される結果を定義します。

    13 行目は現在のリソースを設定し、コンテキストを /content/byline でモックリソース定義に対して評価します。これにより、モックリソースのコンテキストで BylineImpl.java が実行されるようにします。

    14 行目は、モックリクエストオブジェクトから適応させて署名 Sling Model をインスタンス化します。

    15 行目は、署名 Sling Model オブジェクト上で、テスト対象オブジェクトである getOccupations() を呼び出します。

    17 行目は、期待されているリストが実際のリストと同じであるとアサートします。

  2. 上記の getName() と同様に、BylineImplTest.json は職業を定義しません。そのため、このテストを実行すると、byline.getOccupations() は空のリストを返し、テストが失敗します。

    BylineImplTest.json を更新して職業のリストを含めます。すると、getOccupations() によって職業が並べ替えられていることをテストで検証できるよう、このリストはアルファベット順以外に設定されます。

    {
      "byline": {
        "jcr:primaryType": "nt:unstructured",
        "sling:resourceType": "wknd/components/content/byline",
        "name": "Jane Doe",
        "occupations": ["Photographer", "Blogger", "YouTuber"]
      }
    }
  3. テストを実行すると、再び成功します。職業を並べ替えたことが良かったようです。

    testgetoccupations-success
    testGetOccupations() は成功

isEmpty() のテスト

最後に isEmpty() メソッドをテストします。

isEmpty() のテストは、様々な条件をテストする必要があり、興味深いものです。BylineImpl.javaisEmpty() メソッドをレビューするには、次の条件をテストする必要があります。

  1. 名前が空のときに true を返す。
  2. 職業が null または空のときに true を返す。
  3. 画像が空または src URL がない場合 true を返す。
  4. 名前、職業、および Image(srcURL 付き)が存在する場合は false を返す。

これにより、BylineImplTest.json で特定の条件や新しいモックリソース構造をテストする新しいテストメソッドを作成して、これらのテストを駆動させる必要があります。

getName()getOccupations() および getImage() が空の場合、その状態の期待される動作は isEmpty() 経由でテストされるので、このチェックによってテストをスキップすることができます。

 

  1. 最初のテストは、プロパティが設定されていない、まったく新しいコンポーネントの条件をテストします。

    BylineImplTest.json に新しいリソース定義を追加し、意味のある名前「empty」を付けます。

    {
      "byline": {
        "jcr:primaryType": "nt:unstructured",
        "sling:resourceType": "wknd/components/content/byline",
        "name": "Jane Doe",
        "occupations": ["Photographer", "Blogger", "YouTuber"]
      },
      "empty": {
        "jcr:primaryType": "nt:unstructured",
        "sling:resourceType": "wknd/components/content/byline"
      }
    }
    

    8~11 行目は、jcr:primaryType および sling:resourceType のみを持つ、「empty」という名前の新しいリソース定義をおこないます。

    BylineImplTest.json は、ctx@setUp の各テストメソッドの実施前に読み込みます。そのため、新しいリソース定義は、/content/empty でテスト時すぐに利用できるようになります。

  2. testIsEmpty() を次のように更新し、現在のリソースを新しい「empty」というモックリソース定義に設定します。

    @Test
    public void testIsEmpty() {
        ctx.currentResource("/content/empty");
        Byline byline = ctx.request().adaptTo(Byline.class);
        
        assertTrue(byline.isEmpty());
    }

    テストを実行し、成功することを確認します。

  3. 次に、必要なデータポイント(名前、職業、または画像)が空となっている場合、isEmpty() が true を返すメソッドのセットを作成します。

    各テストで、個別モックリソース定義が使用され、without-name および without-occupations の追加リソース定義を使用して BylineImplTest.json を更新します。

    {
      "byline": {
        "jcr:primaryType": "nt:unstructured",
        "sling:resourceType": "wknd/components/content/byline",
        "name": "Jane Doe",
        "occupations": ["Photographer", "Blogger", "YouTuber"]
      },
      "empty": {
        "jcr:primaryType": "nt:unstructured",
        "sling:resourceType": "wknd/components/content/byline"
      },
      "without-name": {
        "jcr:primaryType": "nt:unstructured",
        "sling:resourceType": "wknd/components/content/byline",
        "occupations": "[Photographer, Blogger, YouTuber]"
      },
      "without-occupations": {
        "jcr:primaryType": "nt:unstructured",
        "sling:resourceType": "wknd/components/content/byline",
        "name": "Jane Doe"
      }  
    }

    Mockito 経由で完全にモック化されているので、Image のモックリソース定義には何もありません。

    次のテストメソッドを作成し、これらの状態をそれぞれテストします。

    @Test
    public void testIsEmpty() {
        ctx.currentResource("/content/empty");
    
        Byline byline = ctx.request().adaptTo(Byline.class);
        
        assertTrue(byline.isEmpty());
    }
    
    @Test
    public void testIsEmpty_WithoutName() {
        ctx.currentResource("/content/without-name");
    
        Byline byline = ctx.request().adaptTo(Byline.class);
        
        assertTrue(byline.isEmpty());
    }
    
    @Test
    public void testIsEmpty_WithoutOccupations() {
        ctx.currentResource("/content/without-occupations");
    
        Byline byline = ctx.request().adaptTo(Byline.class);
        
        assertTrue(byline.isEmpty());
    }
    
    @Test
    public void testIsEmpty_WithoutImage() {
        ctx.currentResource("/content/byline");
        
        when(modelFactory.getModelFromWrappedRequest(eq(ctx.request()), 
            any(Resource.class),
            eq(Image.class))).thenReturn(null);    
    
        Byline byline = ctx.request().adaptTo(Byline.class);
        
        assertTrue(byline.isEmpty());
    }
    
    @Test
    public void testIsEmpty_WithoutImageSrc() {
        ctx.currentResource("/content/byline");
        
        when(image.getSrc()).thenReturn("");
        
        Byline byline = ctx.request().adaptTo(Byline.class);
        
        assertTrue(byline.isEmpty());
    }

    1~8 行目は、空のモックリソース定義に対してテストし、isEmpty() が true であることをアサートする testIsEmpty() を定義します。

    10~17 行目は、職業があるけれども名前がないモックリソース定義をテストする、testIsEmpty_WithoutName() を定義します。

    19~26 行目は、名前があるけれども職業がないモックリソース定義をテストする、testIsEmpty_WithoutOccupations() を定義します。

    28~39 行目は、名前と職業でモックリソース定義をテストし、モック Image が null を返すよう設定する testIsEmpty_WithoutImage() を定義します。アドビでは、この呼び出しで返される Image が null となるよう、modelFactory.getModelFromWrappedRequest(..)動作(setUp() で定義したもの)をオーバーライドします。

    41~50 行目は、名前と職業でモックリソース定義をテストし、getSrc() を呼び出すと空白の文字列を返すようモック Image を設定する
    testIsEmpty_WithoutImageSrc() を定義します。

  4. 最後に、コンポーネントが正しく設定されている場合、isEmpty() が false を返すようテストを記述します。この条件の場合は、完全に設定された署名コンポーネントを表す /content/byline を再使用できます。

    @Test
    public void testIsNotEmpty() {
    	ctx.currentResource("/content/byline");
    	when(image.getSrc()).thenReturn("/content/bio.png");
    
    	Byline byline = ctx.request().adaptTo(Byline.class);
    	
    	assertFalse(byline.isEmpty());
    }

    技術的には、このケースは他の testIsEmpty_* メソッドでカバーされますが、このテストは非常に簡単に記述できるので、この場合はこれを検証します。

コードの有効範囲

コードの有効範囲とは、単体テストの対象となるソースコードの量のことです。最近の IDE は、単体テストでどのソースコードが実行されたかを自動的にチェックするツールを提供します。コードの有効範囲自体はコード品質を表すものではありませんが、単体テストの対象とならない、ソースコードの重要な領域があるかどうかを理解するのに役立ちます。

  1. Eclipse のプロジェクトエクスプローラーで BylineImplTest.java を右クリックし、有効範囲の設定/JUnit テストを選択します。

    有効範囲概要ビューが開いていることを確認します(Window/ビューを表示/その他/Java/有効範囲)。

    これにより、このファイル内で単体テストを実行し、コードの有効範囲を示すレポートを提供します。クラスやメソッドを掘り下げると、ファイルのどの部分がテストされ、どの部分がテストされていないかが明確にわかります。

    code-coverage-1
    コードの有効範囲の概要

    Eclipse では、単体テストでカバーされる各クラスとメソッドの範囲をすばやく確認できます。Eclipse はコード行をカラーコード化します。

    • は、1 つ以上のテストで実行されたコードです。
    • 黄色は、どのテストでも検証されていないブランチを示します。
    • は、どのテストでの実行されていないコードを示します。
  2. 有効範囲レポートでは、職業フィールドが null で空のリストを返したブランチが、一度も評価されていないことがわかります。黄色で表示されている 59 行目は if/else のブランチが実行されておらず、赤で表示されている 63 行目はコードが一度も実行されていないことを示します。

    coverage-color-coding
  3. これはリソースに職業の値がない場合、空のリストを返すようアサートする getOccupations() にテストを追加することで修正できます。次の新しいテストメソッドを BylineImplTests.java に追加します。

    @Test
    public void testGetOccupations_WithoutOccupations() {
        List<String> expected = Collections.emptyList();
    
        ctx.currentResource("/content/empty");
        Byline byline = ctx.request().adaptTo(Byline.class);
        
        List<String> actual = byline.getOccupations();
        
        assertEquals(expected, actual);
    }

    3 行目は、期待値を空のリストに設定します。

    5 行目は、現在のリソースを職業プロパティが定義されていない /content/empty に設定します。

  4. 「有効範囲の設定」を再度実行すると、BylineImpl.java は 100 %の有効範囲となっていると報告されますが、isEmpty() に未評価のブランチがまだ 1 つあるので、もう一度職業を操作する必要があります。この場合、occupations == null は評価されますが、"occupations": [] を定義するモックリソース定義がないので、occupations.isEmpty() は評価されません。

    getoccupations-withoutoccupations
    testGetOccupations_WithoutOccupations() の有効範囲
  5. この問題は、職業を空のアレイに設定するモックリソース定義を使用する別のテストメソッドを作成することで簡単に解決できます。

    BylineImplTest.jsonに新しいモックリソース定義("without-occupations" のコピー)空のアレイに設定された職業プロパティを追加し、"without-occupations-empty-array"
    と名付けます。

    "without-occupations-empty-array": {
        "jcr:primaryType": "nt:unstructured",
        "sling:resourceType": "wknd/components/content/byline",
        "name": "Jane Doe",
        "occupations": []
      }  

    この新しいモックリソースを使用する BylineImplTest.java で新しい @Test メソッドを作成すると、アサート isEmpty() は true を返します。

    @Test
    public void testIsEmpty_WithEmptyArrayOfOccupations() {
        ctx.currentResource("/content/without-occupations-empty-array");
        
        Byline byline = ctx.request().adaptTo(Byline.class);
        
        assertTrue(byline.isEmpty());
    }
    testIsEmpty_WithEmptyArrayOfOccupations
    testIsEmpty_WithEmptyArrayOfOccupations() の有効範囲
  6. 最後に追加した内容により、BylineImpl.java は条件パスをすべて評価して、100 %のコード有効範囲を利用できます。

    テストは BylineImpl の期待される動作を検証すると同時に、最小限の実装情報に依存します。

ビルドの一部として単体テストを実行する

単体テストは、Maven ビルドの一部として実行し、成功する必要があります。これにより、アプリケーションをデプロイする前にすべてのテストが成功することを確認します。  パッケージやインストールなどの Maven 目標を実行すると、テストが自動的に呼び出され、プロジェクトのすべての単体テストで成功する必要があります。

$ mvn package
mvn-package-success
$ mvn パッケージ

同様に、テストメソッドが失敗するような変更を加えると、ビルドは失敗し、どのテストが失敗したのか、その理由は何かが報告されます。

mvn-package-fail
$ mvn パッケージ

レビュー


ヘルプ

行き詰まったり、追加の質問がある場合は、AEM 用 Experience League フォーラムを確認するか、既存の GitHub の問題を参照してください。

探していた情報が見つからなかった場合やエラーが見つかった場合は、WKND プロジェクトの問題として GitHub で報告してください。

次の手順

パフォーマンスのベストプラクティスがまもなく公開されます。

完成したソリューションパッケージをダウンロード:

ダウンロード

本作品は Creative Commons Attribution-Noncommercial-Share Alike 3.0 Unported License によってライセンス許可を受けています。  Twitter™ および Facebook の投稿には、Creative Commons の規約内容は適用されません。

リーガルノーティス   |   プライバシーポリシー