单元测试 unit-testing

本教程介绍单元测试的实施,该单元测试将验证在自定义组件教程中创建的Byline组件的Sling模型的行为。

先决条件 prerequisites

查看设置本地开发环境所需的工具和说明。

如果系统上同时安装了Java™ 8和Java™ 11,则VS代码测试运行程序在执行测试时可能会选择较低的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 ​的父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 ​生成版本为​ 4.1.8 ​的io.wcm.testing.aem-mock.junit5项目。 请降级到​ 4.1.0 ​以遵循本章的其余部分。
  3. 打开​ aem-guides-wknd/core/pom.xml ​并查看相应的测试依赖项是否可用。

    core ​项目中的并行源文件夹将包含单元测试和任何支持的测试文件。 此​ 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 > Run

查看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对象。

    由于甚至要实例化署名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}) ​将测试用例类标记为使用Mockito JUnit Jupiter扩展运行,该扩展允许使用@Mock注释在类级别定义模拟对象。
    • @Mock private Image ​创建类型为com.adobe.cq.wcm.core.components.models.Image的模拟对象。 这是在类级别上定义的,这样@Test方法就可以根据需要更改其行为。
    • @Mock private ModelFactory ​创建ModelFactory类型的模拟对象。 这是一个纯粹的Mockito模拟,没有实现任何方法。 这是在类级别上定义的,这样@Test方法就可以根据需要更改其行为。
    • when(modelFactory.getModelFromWrappedRequest(..) ​在模拟ModelFactory对象上调用getModelFromWrappedRequest(..)时为其注册模拟行为。 在thenReturn (..)中定义的结果是返回模拟图像对象。 仅在以下情况下调用此行为:第一个参数等于ctx的请求对象,第二个参数是任何Resource对象,第三个参数必须是核心组件Image类。 我们接受任何资源,因为在整个测试中,我们将ctx.currentResource(...)设置为​ BylineImplTest.json ​中定义的各种模拟资源。 请注意,我们添加了​ lenient() ​严格性,因为稍后我们将要覆盖ModelFactory的此行为。
    • ctx.registerService(..) ​将模拟ModelFactory对象注册到AemContext中,具有最高的服务等级。 这是必需的,因为BylineImpl的init()中使用的ModelFactory是通过@OSGiService ModelFactory model字段注入的。 为了使AemContext注入​ 我们的 ​模拟对象(该对象处理对getModelFromWrappedRequest(..)的调用),我们必须将其注册为该类型的最高级别服务(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()

请记住,此方法必须返回按字母顺序排序的占有列表(降序)存储在occutions属性中。

  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
  • 当占用为null或空时返回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。

    对于使用离散模拟资源定义的每个测试,使用不带 — name ​和​ 不带 — occupations ​的其他资源定义更新​ BylineImplTest.json

    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() ​针对具有名称和占用情况的模拟资源定义进行测试,但将模拟图像设置为返回空值。 请注意,我们要覆盖setUp()中定义的modelFactory.getModelFromWrappedRequest(..)行为,以确保此调用返回的图像对象为null。 Mockito桩模块功能非常严格,并且不需要重复的代码。 因此,我们将模拟设置为​ lenient ​设置,以明确说明我们正在覆盖setUp()方法中的行为。

    testIsEmpty_WithoutImageSrc() ​针对具有名称和占用情况的模拟资源定义进行测试,但设置模拟图像以在调用getSrc()时返回空白字符串。

  4. 最后,编写测试以确保​ isEmpty() ​在正确配置组件时返回false。 对于这种情况,我们可以重用​ /content/byline,它表示完全配置的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