單元測試 unit-testing

本教學課程涵蓋單元測試的實作,該測試會驗證自訂元件教學課程中建立的Byline元件的Sling模型的行為。

先決條件 prerequisites

檢閱設定本機開發環境所需的工具和指示。

如果系統上同時安裝了Java™ 8和Java™ 11,則VS Code測試執行程式會在執行測試時挑選較低的Java™執行階段,導致測試失敗。 如果發生這種狀況,請解除安裝Java™ 8.

入門專案

NOTE
如果您成功完成上一章,您可以重複使用專案,並略過出庫入門專案的步驟。

檢視教學課程建置的基底程式碼:

  1. GitHub檢視tutorial/unit-testing-start分支

    code language-shell
    $ cd aem-guides-wknd
    $ git checkout tutorial/unit-testing-start
    
  2. 使用您的Maven技能將程式碼庫部署到本機AEM執行個體:

    code language-shell
    $ mvn clean install -PautoInstallSinglePackage
    
    note note
    NOTE
    如果使用AEM 6.5或6.4,請將classic設定檔附加至任何Maven命令。
    code language-shell
    $ mvn clean install -PautoInstallSinglePackage -Pclassic
    

您一律可以在GitHub上檢視完成的程式碼,或切換至分支tutorial/unit-testing-start在本機簽出程式碼。

目標

  1. 瞭解單元測試的基本概念。
  2. 瞭解測試AEM程式碼常用的架構和工具。
  3. 瞭解在編寫單元測試時模擬或模擬AEM資源的選項。

背景 unit-testing-background

在本教學課程中,我們將探索如何為署名元件的Sling模型 (建立於建立自訂AEM元件)寫入單元測試。 單元測試是以Java™撰寫的建置時間測試,可驗證Java™程式碼的預期行為。 每個單元測試通常都很小,並且會根據預期結果來驗證方法(或工作單位)的輸出。

我們採用AEM最佳實務,並採用:

單元測試與AdobeCloud Manager unit-testing-and-adobe-cloud-manager

AdobeCloud Manager將單元測試執行和程式碼涵蓋範圍報告整合到其CI/CD管線中,以協助鼓勵和推廣單元測試AEM程式碼的最佳作法。

雖然單元測試程式碼是任何程式碼庫的良好做法,但在使用Cloud Manager時,請務必提供單元測試供Cloud Manager執行,以便利用其程式碼品質測試和報告功能。

更新測試Maven依賴項 inspect-the-test-maven-dependencies

第一步是檢查Maven依賴項以支援寫入和執行測試。 需要四個相依性:

  1. JUnit5
  2. Mockito測試架構
  3. Apache Sling Mocks
  4. AEM Mocks Test Framework (由io.wcm)

使用AEM Maven原型安裝期間,JUnit5Mockito和​ ​AEM Mocks**​測試相依性會自動新增到專案。

  1. 若要檢視這些相依性,請開啟位於​ aem-guides-wknd/pom.xml ​的Parent Reactor POM,瀏覽至<dependencies>..</dependencies>並檢視<!-- Testing -->下io.wcm的JUnit、Mockito、Apache Sling Mocks和AEM Mock Tests相依性。

  2. 請確定io.wcm.testing.aem-mock.junit5已設為​ 4.1.0

    code language-xml
    <dependency>
        <groupId>io.wcm</groupId>
        <artifactId>io.wcm.testing.aem-mock.junit5</artifactId>
        <version>4.1.0</version>
        <scope>test</scope>
    </dependency>
    
    note caution
    CAUTION
    原型​ 35 ​產生io.wcm.testing.aem-mock.junit5版本​ 4.1.8 ​的專案。 請降級為​ 4.1.0 ​以遵循本章其餘部分。
  3. 開啟​ aem-guides-wknd/core/pom.xml ​並檢視對應的測試相依性是否可用。

    核心 ​專案中的平行來源資料夾將包含單元測試和任何支援的測試檔案。 此​ test ​資料夾提供測試類別與原始程式碼的分離,但允許測試就像它們存在於與原始程式碼相同的套件中一樣。

建立JUnit測試 creating-the-junit-test

單元測試通常使用Java™類別對應1對1。 在本章中,我們將為​ BylineImpl.java ​撰寫JUnit測試,這是支援Byline元件的Sling模型。

單元測試src資料夾

儲存單元測試的位置。

  1. 在Java™封裝資料夾結構中的src/test/java下建立新的Java™類別,以反映BylineImpl.java要測試的Java™類別的位置,藉此建立的單元測試。

    建立新的BylineImplTest.java檔案

    由於我們正在測試

    • src/main/java/com/adobe/aem/guides/wknd/core/models/impl/BylineImpl.java

    在下列位置建立對應的單元測試Java™類別

    • src/test/java/com/adobe/aem/guides/wknd/core/models/impl/BylineImplTest.java

    單位測試檔案BylineImplTest.java上的Test尾碼是慣例,允許我們

    1. 輕鬆識別為​ BylineImpl.java的測試檔案
    2. 但是,也要區分測試檔案​ ​所測試的類別,BylineImpl.java

檢閱BylineImplTest.java reviewing-bylineimpltest-java

此時,JUnit測試檔案是空的Java™類別。

  1. 使用以下程式碼更新檔案:

    code language-java
    package com.adobe.aem.guides.wknd.core.models.impl;
    
    import static org.junit.jupiter.api.Assertions.*;
    
    import org.junit.jupiter.api.BeforeEach;
    import org.junit.jupiter.api.Test;
    
    public class BylineImplTest {
    
        @BeforeEach
        void setUp() throws Exception {
    
        }
    
        @Test
        void testGetName() {
            fail("Not yet implemented");
        }
    
        @Test
        void testGetOccupations() {
            fail("Not yet implemented");
        }
    
        @Test
        void testIsEmpty() {
            fail("Not yet implemented");
        }
    }
    
  2. 第一個方法public void setUp() { .. }是以JUnit的@BeforeEach加上註解,它會指示JUnit測試執行程式先執行此方法,然後再執行此類別中的每個測試方法。 這為初始化所有測試所需的通用測試狀態提供了一個方便的位置。

  3. 後續方法是測試方法,其名稱依照慣例以test為前置詞,並以@Test註解標籤。 請注意,根據預設,我們所有的測試都會設為失敗,因為我們尚未實作這些測試。

    首先,我們先對我們測試的類別上的每個公用方法使用單一測試方法,因此:

    table 0-row-3 1-row-3 2-row-3 3-row-3
    BylineImpl.java BylineImplTest.java
    getName() 測試者 testGetName()
    getOccupations() 測試者 testGetOccupations()
    isEmpty() 測試者 testIsEmpty()

    您可以視需要展開這些方法,我們將在本章的稍後章節中看到。

    執行此JUnit測試類別(也稱為JUnit測試案例)時,每個標示@Test的方法都會當作測試執行,測試可能會通過或失敗。

產生的BylineImplTest

core/src/test/java/com/adobe/aem/guides/wknd/core/models/impl/BylineImplTest.java

  1. BylineImplTest.java檔案上按一下滑鼠右鍵,並點選​ 執行,以執行JUnit測試案例。
    如預期,所有測試都會失敗,因為它們尚未實作。

    以junit測試執行

    在BylineImplTests.java >執行 ​上按一下滑鼠右鍵

檢閱BylineImpl.java reviewing-bylineimpl-java

編寫單元測試時,有兩個主要方法:

  • TDD或測試驅動開發,涉及在開發實作之前以增量方式寫入單元測試;請撰寫測試,然後寫入實作以通過測試。
  • 實作優先開發,包括先開發工作程式碼,然後撰寫測試以驗證該程式碼。

在本教學課程中,會使用後一種方法(因為我們在先前的章節中已建立有效的​ BylineImpl.java)。 因此,我們必須檢閱並瞭解其公開方法的行為,但也要瞭解其部分實作細節。 這聽起來可能恰恰相反,因為良好的測試應該只關心輸入和輸出,但是當在AEM中工作時,為了建構有效的測試,需要理解各種實施考量。

在AEM的情境下,TDD需要一定程度的專業知識,且最能被精通AEM開發和AEM程式碼的單元測試的AEM開發人員採用。

設定AEM測試內容 setting-up-aem-test-context

大部分針對AEM撰寫的程式碼都仰賴JCR、Sling或AEM API,而這又需要執行AEM的內容才能正確執行。

由於單元測試是在建置時執行,因此在正在執行的AEM執行個體的上下文之外,沒有這樣的上下文。 為方便起見,wcm.io的AEM Mocks會建立模擬內容,讓這些API 大部分 ​就像在AEM中執行一樣。

  1. 在​ BylineImplTest.java ​中使用​ wcm.io的 AemContext建立AEM內容,方法是將其新增為裝飾有@ExtendWith的JUnit延伸模組,並新增至​ BylineImplTest.java ​檔案。 擴充功能會處理所有必要的初始化和清理工作。 為AemContext建立類別變數,該變數可用於所有測試方法。

    code language-java
    import org.junit.jupiter.api.extension.ExtendWith;
    import io.wcm.testing.mock.aem.junit5.AemContext;
    import io.wcm.testing.mock.aem.junit5.AemContextExtension;
    ...
    
    @ExtendWith(AemContextExtension.class)
    class BylineImplTest {
    
        private final AemContext ctx = new AemContext();
    

    此變數ctx會公開提供一些AEM和Sling抽象概念的模擬AEM內容:

    • BylineImpl Sling模型已登入至此內容
    • 模擬JCR內容結構會在此內容中建立
    • 可在此內容中註冊自訂OSGi服務
    • 提供各種常見的必要模擬物件和協助程式,例如SlingHttpServletRequest物件、各種模擬Sling和AEM OSGi服務,例如ModelFactory、PageManager、Page、Template、ComponentManager、Component、TagManager、Tag等。
      • 並非這些物件的所有方法都已實作!
    • 以及更多

    ctx ​物件將做為大部分模擬內容的進入點。

  2. 在每個@Test方法之前執行的setUp(..)方法中,定義共同的模擬測試狀態:

    code language-java
    @BeforeEach
    public void setUp() throws Exception {
        ctx.addModelsForClasses(BylineImpl.class);
        ctx.load().json("/com/adobe/aem/guides/wknd/core/models/impl/BylineImplTest.json", "/content");
    }
    
    • addModelsForClasses ​將要測試的Sling模型註冊到模擬AEM Context中,以便它可以在@Test方法中具現化。
    • load().json ​將資源結構載入模擬內容中,讓程式碼與這些資源互動,就像它們是由真正的存放庫提供一樣。 檔案​ BylineImplTest.json ​中的資源定義已載入到​ /content ​下的模擬JCR內容中。
    • BylineImplTest.json ​尚不存在,所以讓我們建立它並定義測試所需的JCR資源結構。
  3. 代表模擬資源結構的JSON檔案儲存在​ core/src/test/resources ​下,其路徑與JUnit Java™測試檔案相同。

    core/test/resources/com/adobe/aem/guides/wknd/core/models/impl建立一個名為​ BylineImplTest.json ​的JSON檔案,其內容如下:

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

    BylineImplTest.json

    此JSON定義了Byline元件單元測試的模擬資源(JCR節點)。 此時,JSON具有代表Byline元件內容資源所需的最小屬性集jcr:primaryTypesling:resourceType

    使用單元測試時,一般規則是建立滿足每個測試所需的最小模擬內容、前後關聯和程式碼集合。 避免在撰寫測試之前建置完整模擬內容的誘惑,因為這通常會導致不需要的成品。

    現在有​ BylineImplTest.json ​存在,執行ctx.json("/com/adobe/aem/guides/wknd/core/models/impl/BylineImplTest.json", "/content")時,模擬資源定義會載入到路徑​ /content. ​的內容中

測試getName() testing-get-name

現在我們已有基本的模擬內容設定,讓我們來撰寫​ BylineImpl的getName() ​的第一個測試。 此測試必須確定方法​ getName() ​傳回儲存在資源「name」 ​屬性中的正確編寫名稱。

  1. 更新​ BylineImplTest.java ​中的​ testGetName()方法,如下所示:

    code language-java
    import com.adobe.aem.guides.wknd.core.models.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);
    }
    
    • String expected ​設定預期的值。 我們會將此專案設為"Jane Done"。
    • ctx.currentResource ​設定模擬資源的內容以評估程式碼,因此這會設為​ /content/byline,因為這是載入模擬署名內容資源的位置。
    • Byline byline ​從模擬要求物件改寫並具現化署名Sling模型。
    • String actual ​在Byline Sling模型物件上叫用我們正在測試的方法getName()
    • assertEquals ​斷言預期值與署名Sling模型物件傳回的值相符。 如果這些值不相等,測試就會失敗。
  2. 執行測試……但失敗並出現NullPointerException

    此測試不會失敗,因為我們從未在模擬JSON中定義name屬性,這將導致測試失敗,但測試執行尚未到達該點! 此測試失敗,因為署名物件本身有NullPointerException

  3. BylineImpl.java中,如果@PostConstruct init()擲回例外狀況,這會阻止Sling模型具現化,並導致該Sling模型物件為空。

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

    雖然ModelFactory OSGi服務是透過AemContext (透過Apache Sling Context)提供,但並非所有方法皆已實作,包括在BylineImpl的init()方法中呼叫的getModelFromWrappedRequest(...)。 這會導致AbstractMethodError,字詞中會導致init()失敗,ctx.request().adaptTo(Byline.class)的調整結果為Null物件。

    由於提供的模擬無法容納我們的程式碼,我們必須自行實作模擬內容。為此,我們可以使用Mockito建立模擬ModelFactory物件,當對其呼叫getModelFromWrappedRequest(...)時,會傳回模擬Image物件。

    因為即使要例項化Byline Sling模型,這個模擬內容必須準備就緒,我們可以將其新增到@Before setUp()方法。 我們還需要將MockitoExtension.class新增至​ BylineImplTest ​類別上方的@ExtendWith註解。

    code language-java
    package com.adobe.aem.guides.wknd.core.models.impl;
    
    import org.mockito.junit.jupiter.MockitoExtension;
    import org.mockito.Mock;
    
    import com.adobe.aem.guides.wknd.core.models.Byline;
    import com.adobe.cq.wcm.core.components.models.Image;
    
    import io.wcm.testing.mock.aem.junit5.AemContext;
    import io.wcm.testing.mock.aem.junit5.AemContextExtension;
    
    import org.apache.sling.models.factory.ModelFactory;
    import org.junit.jupiter.api.BeforeEach;
    import org.junit.jupiter.api.Test;
    import org.junit.jupiter.api.extension.ExtendWith;
    
    import static org.junit.jupiter.api.Assertions.*;
    import static org.mockito.Mockito.*;
    import org.apache.sling.api.resource.Resource;
    
    @ExtendWith({ AemContextExtension.class, MockitoExtension.class })
    public class BylineImplTest {
    
        private final AemContext ctx = new AemContext();
    
        @Mock
        private Image image;
    
        @Mock
        private ModelFactory modelFactory;
    
        @BeforeEach
        public void setUp() throws Exception {
            ctx.addModelsForClasses(BylineImpl.class);
    
            ctx.load().json("/com/adobe/aem/guides/wknd/core/models/impl/BylineImplTest.json", "/content");
    
            lenient().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);
        }
    
        @Test
        void testGetName() { ...
    }
    
    • @ExtendWith({AemContextExtension.class, MockitoExtension.class}) ​會將Test Case類別標示為使用Mockito JUnit Jupiter延伸模組執行,允許使用@Mock註解在類別層級定義模擬物件。
    • @Mock private Image ​建立型別com.adobe.cq.wcm.core.components.models.Image的模擬物件。 這是在類別層級定義,因此@Test方法可以根據需要變更其行為。
    • @Mock private ModelFactory ​會建立ModelFactory型別的模擬物件。 這是純粹的Mockito模擬,而且沒有實作任何方法。 這是在類別層級定義,因此@Test方法可以根據需要變更其行為。
    • 在模擬ModelFactory物件上呼叫getModelFromWrappedRequest(..)時,when(modelFactory.getModelFromWrappedRequest(..) ​會登入的模擬行為。 在thenReturn (..)中定義的結果是傳回模擬影像物件。 唯有在下列情況下才會叫用此行為:第一個引數等於ctx的要求物件、第二個引數是任何Resource物件,而第三個引數必須是核心元件Image類別。 我們接受任何資源,因為在整個測試中,我們將ctx.currentResource(...)設定為​ BylineImplTest.json ​中定義的各種模擬資源。 請注意,我們新增​ lenient() ​嚴格,因為我們稍後會想要覆寫ModelFactory的這個行為。
    • ctx.registerService(..) ​將模擬ModelFactory物件註冊到AemContext中,具有最高的服務排名。 這是必要的,因為BylineImpl的init()中使用的ModelFactory是透過@OSGiService ModelFactory model欄位插入。 為了讓AemContext插入​ 我們的 ​模擬物件(處理對getModelFromWrappedRequest(..)的呼叫),我們必須將它註冊為該型別的最高等級Service (ModelFactory)。
  4. 重新執行測試,再次失敗,但這次訊息已清楚說明失敗的原因。

    測試名稱失敗宣告

    由於宣告​ testGetName()失敗

    我們收到​ AssertionError,表示測試中的判斷提示條件失敗,它告訴我們​ 預期值為「Jane Doe」,但​ 實際值為null。 這是有道理的,因為「name」 ​屬性尚未新增至​ BylineImplTest.json ​中的模擬​ /content/byline ​資源定義,所以讓我們新增它:

  5. 更新​ BylineImplTest.json ​以定義"name": "Jane Doe".

    code language-json
    {
        "byline": {
        "jcr:primaryType": "nt:unstructured",
        "sling:resourceType": "wknd/components/content/byline",
        "name": "Jane Doe"
        }
    }
    
  6. 重新執行測試,testGetName() ​現在通過!

    測試名稱通過

測試getOccupations() testing-get-occupations

很好! 已通過第一個測試! 讓我們繼續並測試getOccupations()。 因為模擬內容的初始化已在@Before setUp()方法中完成,所以這個測試案例中的所有@Test方法都可以使用,包括getOccupations()

請記住,此方法必須傳回儲存在occupations屬性中的按字母順序排序的職業清單(降序)。

  1. 更新​ testGetOccupations(),如下所示:

    code language-java
    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);
    }
    
    • List<String> expected ​定義預期的結果。
    • ctx.currentResource ​設定目前資源,以針對/content/byline處的模擬資源定義評估內容。 這可確保在模擬資源的內容中執行​ BylineImpl.java
    • ctx.request().adaptTo(Byline.class) ​從模擬要求物件改寫並具現化署名Sling模型。
    • byline.getOccupations() ​在Byline Sling模型物件上叫用我們正在測試的方法getOccupations()
    • assertEquals(expected, actual) ​宣告預期的清單與實際清單相同。
  2. 請記住,如同上述​ getName()BylineImplTest.json ​未定義職業,因此如果執行該測試,將會失敗,因為byline.getOccupations()將傳回空白清單。

    更新​ BylineImplTest.json ​以包含職業清單,且這些職業是以非字母順序設定的,以確保我們的測試可驗證這些職業是否按​ getOccupations() ​的字母順序排序。

    code language-json
    {
        "byline": {
        "jcr:primaryType": "nt:unstructured",
        "sling:resourceType": "wknd/components/content/byline",
        "name": "Jane Doe",
        "occupations": ["Photographer", "Blogger", "YouTuber"]
        }
    }
    
  3. 執行測試,再次通過測試! 取得已排序的職業似乎可以運作!

    取得佔用階段

    testGetOccupations()通過

測試isEmpty() testing-is-empty

最後一個測試​ isEmpty() ​的方法。

測試isEmpty()很有趣,因為它需要測試各種條件。 檢閱​ BylineImpl.java ​的isEmpty()方法必須測試下列條件:

  • 當名稱為空時傳回true
  • 當職業為空值或空白時傳回true
  • 當影像為null或沒有src URL時傳回true
  • 出現名稱、佔用和影像(具有src URL)時,傳回false

為此,我們需要建立測試方法,每個方法都會在BylineImplTest.json中測試特定條件和新的模擬資源結構,以推動這些測試。

此檢查可讓我們略過當getName()getOccupations()getImage()為空時的測試,因為該狀態的預期行為是透過isEmpty()測試的。

  1. 第一個測試將會測試全新元件的狀態,該元件沒有設定屬性。

    新增資源定義至BylineImplTest.json,賦予其語意名稱「empty

    code language-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"
        }
    }
    

    "empty": {...} ​定義名稱為「empty」的新資源定義,其中只有jcr:primaryTypesling:resourceType

    請記住,在@setUp中執行每個測試方法之前,我們先將BylineImplTest.json載入ctx,所以這個新的資源定義在​ /content/empty. ​的測試中立即可供我們使用。

  2. 更新testIsEmpty(),如下所示,將目前資源設定為新的"empty"模擬資源定義。

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

    執行測試並確保測試通過。

  3. 接下來,建立一組方法,以確保如果任何必要的資料點(名稱、職務或影像)是空的,isEmpty()會傳回true。

    每個測試都使用分散式模擬資源定義,請更新​ BylineImplTest.json ​以包含​ 不含name ​和​ 不含occupations ​的其他資源定義。

    code language-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"
        }
    }
    

    建立下列測試方法來測試每種狀態。

    code language-java
    @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");
    
        lenient().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());
    }
    

    針對空白模擬資源定義進行​ testIsEmpty() ​測試,並斷定isEmpty()為true。

    testIsEmpty_WithoutName() ​針對模擬資源定義進行測試,該定義具有佔用專案但沒有名稱。

    testIsEmpty_WithoutOccupations() ​針對名稱為但無佔用位置的模擬資源定義進行測試。

    testIsEmpty_WithoutImage() ​針對名稱與佔用情況的模擬資源定義進行測試,但將模擬影像設定為傳回null。 請注意,我們要覆寫setUp()中定義的modelFactory.getModelFromWrappedRequest(..)行為,以確保此呼叫傳回的影像物件為Null。 Mockito stubs功能非常嚴格,並且不想要重複的程式碼。 因此,我們將模型設定為​ lenient ​設定,以明確指出我們正在覆寫setUp()方法中的行為。

    testIsEmpty_WithoutImageSrc() ​針對具有名稱與佔用情況的模擬資源定義進行測試,但設定模擬影像以在叫用getSrc()時傳回空白字串。

  4. 最後,撰寫測試以確保​ isEmpty() ​在正確設定元件時傳回false。 針對此情況,我們可以重複使用代表完整設定的Byline元件的​ /content/byline

    code language-java
    @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());
    }
    
  5. 現在執行BylineImplTest.java檔案中的所有單元測試,並檢閱Java™測試報告輸出。

所有測試均通過

在建置過程中執行單元測試 running-unit-tests-as-part-of-the-build

單元測試會執行,並需要作為Maven組建的一部分通過。 這可確保在部署應用程式之前成功通過所有測試。 執行Maven目標(例如封裝或安裝)會自動叫用,並需要傳遞專案中的所有單元測試。

$ mvn package

mvn封裝成功

$ mvn package

同樣地,如果我們將測試方法變更為失敗,建置將失敗並報告哪些測試失敗及原因。

mvn封裝失敗

檢閱程式碼 review-the-code

GitHub上檢視完成的程式碼,或在Git分支tutorial/unit-testing-solution上檢閱並部署本機的程式碼。

recommendation-more-help
b2a561c1-47c0-4182-b8c1-757a197484f9