TestNG + PowerMock 单元测试

简介: TestNG + PowerMock 单元测试

单元测试(Unit Testing),是指对软件或项目中最小可测试单元进行正确性检验的测试工作。单元是人为规定最小可测试的功能模块,可以是一个模块,一个函数或者一个类。单元测试需要与模块开发进行隔离情况下进行测试。


在程序开发完成后,我们往往不能保证程序 100% 的正确,通过单元测试的编写,我们可以通过自动化的测试程序将我们的输入输出程序进行定义,通过断言来 Check 各个 Case 的结果,检测我们的程序。以提高程序的正确性,稳定性,可靠性,节省程序开发时间。我们在项目中主要用到的单元测试框架有 Spring-Boot-TestTestNGPowerMock 等。


TestNG,即 Testing, Next Generation,下一代测试技术,是一套根据 JUnit 和 NUnit 思想而构建的利用注释来强化测试功能的一个测试框架,即可以用来做单元测试,也可以用来做集成测试。


PowerMock 也是一个单元测试模拟框架,它是在其它单元测试模拟框架的基础上做出的扩展。通过提供定制的类加载器以及一些字节码篡改技巧的应用,PowerMock 现了对静态方法、构造方法、私有方法以及 Final 方法的模拟支持,对静态初始化过程的移除等强大的功能。


常用注解


1. TestNG  注解


  • @BeforeSuite 在该套件的所有测试都运行在注释的方法之前,仅运行一次
  • @AftereSuite  在该套件的所有测试都运行在注释方法之后,仅运行一次
  • @BeforeClass  在调用当前类的第一个测试方法之前运行,注释方法仅运行一次
  • @AftereClass  在调用当前类的第一个测试方法之后运行,注释方法仅运行一次
  • @BeforeMethod  注释方法将在每个测试方法之前运行
  • @AfterMethod   注释方法将在每个测试方法之后运行
  • @BeforeTest  注释的方法将在属于test标签内的类的所有测试方法运行之前运行
  • @AfterTest    注释的方法将在属于test标签内的类的所有测试方法运行之后运行
  • @DataProvider  标记一种方法来提供测试方法的数据。注释方法必须返回一个Object [] [],其中每个Object []可以被分配给测试方法的参数列表。要从该DataProvider接收数据的@Test方法需要使用与此注释名称相等的dataProvider名称
  • @Parameters   描述如何将参数传递给@Test方法 ;适用于 xml 方式的参数化方式传值
  • @Test    将类或方法标记为测试的一部分,此标记若放在类上,则该类所有公共方法都将被作为测试方法


2. PowerMock 注解


  • @Mock  注解实际上是 Mockito.mock() 方法的缩写,我们只在测试类中使用它;
  • @InjectMocks 主动将已存在的 mock 对象注入到 bean 中, 按名称注入, 但注入失败不会抛出异常;
  • @Spy 封装一个真实的对象,以便可以像其他 mock 的对象一样追踪、设置对象的行为;


示例代码


1. 添加 pom.xml 依赖


以 Spring-Boot 项目为例,首先我们需要添加 TestNG + ProwerMock 依赖依赖如下:


    <dependency>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-starter-test</artifactId>    <scope>test</scope></dependency><dependency>    <groupId>org.testng</groupId>    <artifactId>testng</artifactId>    <version>${testng.version}</version>    <scope>test</scope></dependency><dependency>    <groupId>org.powermock</groupId>    <artifactId>powermock-api-mockito2</artifactId>    <version>${powermock.version}</version>    <scope>test</scope></dependency><dependency>    <groupId>org.powermock</groupId>    <artifactId>powermock-module-junit4</artifactId>    <version>${powermock.version}</version>    <scope>test</scope></dependency><dependency>    <groupId>org.powermock</groupId>    <artifactId>powermock-module-testng</artifactId>    <version>${powermock.version}</version>    <scope>test</scope></dependency>


    2. 增加单元测试


    增加测试代码


      import com.test.testng.dto.OrderDto;import com.test.testng.dto.UserDto;import org.mockito.*;import org.powermock.modules.testng.PowerMockTestCase;import org.testng.annotations.BeforeMethod;import org.testng.annotations.Test;import static org.junit.jupiter.api.Assertions.*;import static org.mockito.Mockito.when;public class OrderServiceTest extends PowerMockTestCase {    @BeforeMethod    public void before() {        MockitoAnnotations.openMocks(this);    }    @InjectMocks    private OrderService orderService;    @Mock    private UserService userService;    // 正常测试    @Test    public void testCreateOrder() {        //1. mock method start        UserDto userDto = new UserDto();        userDto.setId(100);        when(userService.get()).thenReturn(userDto);        //2. call business method        OrderDto order = orderService.createOrder(new OrderDto());        //3. assert        assertEquals(order.getId(), 100);    }    // 异常测试    @Test    public void testCreateOrderEx() {        //1. mock method start        when(userService.get()).thenThrow(new RuntimeException());        Exception exception = null;        try {            //2. call business method            orderService.createOrder(new OrderDto());        } catch (RuntimeException e) {            exception = e;        }        //3. assert        assertNotNull(exception);    }}


      常用 Mock 方式


      1. Mock 静态方法


        //静态方法UserDto dto = new UserDto();dto.setId(100000);PowerMockito.mockStatic(UserService.class);PowerMockito.when(UserService.loginStatic()).thenReturn(dto);UserDto userDto = UserService.loginStatic();assertEquals(100000, userDto.getId().intValue());


        2. Mock 私有属性


          //字段赋值ReflectionTestUtils.setField(orderService, "rateLimit", 99);


          3. Mock 私有方法


            // 模拟私有方法MemberModifier.stub(MemberMatcher.method(UserService.class, "get1")).toReturn(new UserDto());// 测试私有方法Method method = PowerMockito.method(UserService.class, "get1", Integer.class);Object userDto = method.invoke(userService, 1);assertTrue(userDto instanceof UserDto);


            进阶使用


            1. 参数化批量测试


            在测试数据比较多的时候,我们可以通过 @DataProvider 生成数据源,通过 @Test(dataProvider = "xxx") 使用数据, 如下所示:


              import com.test.testng.BaseTest;import com.test.testng.dto.UserDto;import org.mockito.InjectMocks;import org.testng.annotations.DataProvider;import org.testng.annotations.Test;import static org.testng.Assert.assertFalse;import static org.testng.AssertJUnit.assertTrue;public class UserServiceTest2 extends BaseTest {    @InjectMocks    private UserService userService;    // 定义数据源    @DataProvider(name = "test")    public static Object[][] userList() {        UserDto dto1 = new UserDto();        UserDto dto2 = new UserDto();        dto2.setSex(1);        UserDto dto3 = new UserDto();        dto3.setSex(1);        dto3.setFlag(1);        UserDto dto4 = new UserDto();        dto4.setSex(1);        dto4.setFlag(1);        dto4.setAge(1);        return new Object[][] {{dto1, null}, {dto2, null}, {dto3, null}, {dto4, null}};    }    // 正确场景    @Test    public void testCheckEffectiveUser() {        UserDto dto = new UserDto();        dto.setSex(1);        dto.setFlag(1);        dto.setAge(18);        boolean result = userService.checkEffectiveUser(dto);        assertTrue(result);    }
                  // 错误场景    @Test(dataProvider = "test")    public void testCheckEffectiveUser(UserDto dto, Object object) {        boolean result = userService.checkEffectiveUser(dto);        assertFalse(result);    }
              }


              2. 复杂判断保证测试覆盖率


              案例:


              1. 判断有效用户: 年龄大于 18 并且 sex = 1 并且 flag = 1


                public boolean checkEffectiveUser(UserDto dto) {    // 判断有效用户: 年龄大于 18 并且 sex = 1 并且 flag = 1    return Objects.equals(dto.getSex(), 1) &&        Objects.equals(dto.getFlag(), 1) &&        dto.getAge() != null && dto.getAge() >= 18;}


                1. 拆分逻辑。将其转换为最简单的 if ... else 语句。然后增加的单元测试,如下所示:


                  public boolean checkEffectiveUser(UserDto dto) {    if (!Objects.equals(dto.getSex(), 1)) {        return false;    }    if (!Objects.equals(dto.getFlag(), 1)) {        return false;    }    if (dto.getAge() == null) {        return false;    }    if (dto.getAge() < 18) {        return false;    }    return true;}


                  1. 拆分后我们可以看到,咱们只需要 5 条单元测试就能做到全覆盖。


                    public class UserServiceTest extends BaseTest {    @InjectMocks    private UserService userService;    // 覆盖第一个 return     @Test    public void testCheckEffectiveUser_0() {        UserDto dto =new UserDto();        boolean result = userService.checkEffectiveUser(dto);        assertFalse(result);    }    // 覆盖第二个 return     @Test    public void testCheckEffectiveUser_1() {        UserDto dto =new UserDto();        dto.setSex(1);        boolean result = userService.checkEffectiveUser(dto);        assertFalse(result);    }    // 覆盖第三个 return     @Test    public void testCheckEffectiveUser_2() {        UserDto dto =new UserDto();        dto.setSex(1);        dto.setFlag(1);        boolean result = userService.checkEffectiveUser(dto);        assertFalse(result);    }    // 覆盖第四个 return    @Test    public void testCheckEffectiveUser_3() {        UserDto dto =new UserDto();        dto.setSex(1);        dto.setFlag(1);        dto.setAge(1);        boolean result = userService.checkEffectiveUser(dto);        assertFalse(result);    }    // 覆盖第五个 return    @Test    public void testCheckEffectiveUser_4() {        UserDto dto =new UserDto();        dto.setSex(1);        dto.setFlag(1);        dto.setAge(18);        boolean result = userService.checkEffectiveUser(dto);        assertTrue(result);    }}


                    1. 单测覆盖率检测检测


                    单元测试全覆盖.png

                    35

                    publicbooleancheckEffectiveuser(userDtodto)

                    if(lobjects.eguals(dto.getsexO),b:1))

                    36

                    returnfalse;

                    37

                    38

                    b:1))f

                    if(lobjects.equals(dto.getFlag()

                    39

                    returnfaise;

                    40

                    41

                    if(dto.getageO)anu11)f

                    42

                    returnfaise;

                    43

                    ]

                    44

                    (dto.getage()<18)

                    if

                    45

                    returnfaise;

                    46

                    47

                    returntrue;

                    48

                    49


                    3. 通过断言校验方法参数


                    1. assert:断言是 java 的一个保留字,用来对程序进行调试,后接逻辑运算表达式,如下:


                      int a = 0, b = 1;assert a == 0 && b == 0;// 使用方法:javac编译源文件,再 java -ea class文件名即可。


                      1. 在 Spring-Boot 中可以使用 Spring 提供的 Assert 类的方法对前端来的参数进行校验,如:


                        // 检查年龄 >= 18 岁public boolean checkUserAge(UserDto dto){    Assert.notNull(dto.getAge(), "用户年龄不能为空");    Assert.isTrue(dto.getAge() >= 18, "用户年龄不能小于 18 岁");    return Boolean.TRUE;}


                        1. 如果是需要转换为,rest api 返回的统一相应消息,我们可以通过:


                          @ControllerAdvicepublic class GlobalExceptionHandler {    @ResponseBody    @ExceptionHandler(value = IllegalArgumentException.class)    public Response<String> handleArgError(IllegalArgumentException e){        return new Response().failure().message(e.getMessage());    }}


                          总结


                          原则上来讲,在功能模块的设计过程中我们应该遵循一下原则(参考 《软件工程-结构化设计准则》):


                          1. 模块大小适中
                          2. 合适的系统调用深度
                          3. 多扇入、少扇出(增加复用度, 减少依赖程度)
                          4. 单入口,单出口
                          5. 模块的作用域,应该在模块内
                          6. 功能应该可以预测的
                          7. 高内聚,低耦合
                          8. 系统分解有层次
                          9. 较少的数据冗余
                          相关文章
                          |
                          7月前
                          |
                          Java 测试技术 Python
                          《手把手教你》系列基础篇(八十)-java+ selenium自动化测试-框架设计基础-TestNG依赖测试-番外篇(详解教程)
                          【6月更文挑战第21天】本文介绍了TestNG中测试方法的依赖执行顺序。作者通过一个实际的自动化测试场景展示了如何设计测试用例:依次打开百度、搜索“selenium”、再搜索“selenium+java”。代码示例中,`@Test`注解的`dependsOnMethods`属性用于指定方法间的依赖,确保执行顺序。如果不设置依赖,TestNG会按方法名首字母排序执行。通过运行代码,验证了依赖关系的正确性。
                          73 4
                          |
                          2月前
                          |
                          Java 测试技术 Maven
                          Java一分钟之-PowerMock:静态方法与私有方法测试
                          通过本文的详细介绍,您可以使用PowerMock轻松地测试Java代码中的静态方法和私有方法。PowerMock通过扩展Mockito,提供了强大的功能,帮助开发者在复杂的测试场景中保持高效和准确的单元测试。希望本文对您的Java单元测试有所帮助。
                          259 2
                          |
                          6月前
                          |
                          Java 测试技术 Spring
                          详解单元测试问题之PowerMock不建议使用如何解决
                          详解单元测试问题之PowerMock不建议使用如何解决
                          64 1
                          |
                          7月前
                          |
                          XML 设计模式 Java
                          PowerMock:静态方法与私有方法测试
                          PowerMock是Java单元测试中扩展Mockito的框架,允许模拟静态方法、构造函数、私有方法和final类,以增强测试隔离和覆盖率。主要应用场景包括静态方法模拟、私有方法测试和构造函数/Final类模拟。然而,使用时需注意配置复杂性、避免过度使用、精确控制模拟行为和遵循最佳实践。示例展示了如何模拟静态方法,通过添加PowerMock依赖和使用PowerMockito.mockStatic进行静态方法的模拟和验证。正确使用PowerMock能提升测试质量,但应谨慎以保持代码可读性和测试有效性。
                          402 5
                          PowerMock:静态方法与私有方法测试
                          |
                          6月前
                          |
                          XML 测试技术 数据格式
                          《手把手教你》系列基础篇(八十五)-java+ selenium自动化测试-框架设计基础-TestNG自定义日志-下篇(详解教程)
                          【7月更文挑战第3天】TestNG教程展示了如何自定义日志记录。首先创建一个名为`TestLog`的测试类,包含3个测试方法,其中一个故意失败以展示日志。使用`Assert.assertTrue`和`Reporter.log`来记录信息。接着创建`CustomReporter`类,继承`TestListenerAdapter`,覆盖`onTestFailure`, `onTestSkipped`, 和 `onTestSuccess`,在这些方法中自定义日志输出。
                          59 6
                          |
                          7月前
                          |
                          Java 测试技术 Python
                          《手把手教你》系列基础篇(八十一)-java+ selenium自动化测试-框架设计基础-TestNG如何暂停执行一些case(详解教程)
                          【6月更文挑战第22天】本文介绍了如何在TestNG中不执行特定测试用例。当部分模块未准备好时,可以通过以下方式暂停测试:③使用`@Test(enabled=false)`注解来禁用测试用例。作者提供了一个Java Selenium自动化测试的示例,展示如何通过修改`enabled`参数控制测试方法的执行。代码中,`testSearch2()`方法被禁用,因此在测试运行时不执行。文章还包含了测试报告和执行过程的截图。
                          68 7
                          |
                          7月前
                          |
                          Java 测试技术 Python
                          《手把手教你》系列基础篇(七十九)-java+ selenium自动化测试-框架设计基础-TestNG依赖测试-下篇(详解教程)
                          【6月更文挑战第20天】TestNG是一个Java测试框架,提供两种测试方法依赖机制:强依赖(所有前置方法成功后才运行)和弱依赖(即使前置方法失败,后置方法仍运行)。文中通过代码示例展示了这两种依赖如何实现,并解释了当依赖方法失败时,如何影响后续方法的执行。文章还包含了TestNG Suite的运行结果截图来辅助说明。
                          61 8
                          |
                          7月前
                          |
                          XML Java 测试技术
                          《手把手教你》系列基础篇(八十二)-java+ selenium自动化测试-框架设计基础-TestNG测试报告-上篇(详解教程)
                          【6月更文挑战第23天】TestNG 是一个用于自动化测试的 Java 框架,它自动生成测试报告,包括 HTML 和 XML 格式。报告可在 `test-output` 文件夹中找到。要创建测试用例,可创建一个实现了 `@Test` 注解的方法。通过 `testng.xml` 配置文件来组织和执行测试。默认报告包括测试结果、失败点和原因。用户还能实现 `ITestListener` 和 `IReporter` 接口来自定义报告和记录器。
                          68 2
                          |
                          7月前
                          |
                          Java 测试技术 Python
                          《手把手教你》系列基础篇(七十七)-java+ selenium自动化测试-框架设计基础-TestNG依赖测试- 上篇(详解教程)
                          【6月更文挑战第18天】TestNG是一个Java测试框架,它允许在测试方法间定义执行顺序和依赖关系。当不指定依赖时,TestNG默认按方法名首字母排序执行。`@Test`注解的`dependsOnMethods`属性用于指定方法依赖,如`test1`依赖`test4`,则实际执行顺序为`test4`、`test2`、`test3`、`test1`。如果依赖的方法失败,后续依赖的方法将被跳过。此外,`dependsOnGroups`属性通过组名指定依赖,方便管理多个相关测试方法。通过`groups`定义方法所属组,然后在其他方法中用`dependsOnGroups`引用这些组。
                          54 5
                          |
                          7月前
                          |
                          XML Web App开发 测试技术
                          《手把手教你》系列基础篇(七十八)-java+ selenium自动化测试-框架设计基础-TestNG依赖测试- 中篇(详解教程)
                          【6月更文挑战第19天】本文介绍了使用TestNG框架配置XML文件来管理测试用例的分组和依赖关系。
                          153 2