TestNG + PowerMock 单元测试

玩技站长
玩技站长
管理员, Keymaster
11056
文章
0
粉丝
测试资讯评论128字数 1960阅读6分32秒阅读模式
摘要单元测试(Unit Testing),是指对软件或项目中最小可测试单元进行正确性检验的测试工作。单元是人为规定最小可测试的功能模块,可以是一个模块,一个函数或者一个类。单元测试需要...

TestNG + PowerMock 单元测试插图

单元测试(Unit Testing),是指对软件或项目中最小可测试单元进行正确性检验的测试工作。单元是人为规定最小可测试的功能模块,可以是一个模块,一个函数或者一个类。单元测试需要与模块开发进行隔离情况下进行测试。文章源自玩技e族-https://www.playezu.com/194274.html

在程序开发完成后,我们往往不能保证程序 100% 的正确,通过单元测试的编写,我们可以通过自动化的测试程序将我们的输入输出程序进行定义,通过断言来 Check 各个 Case 的结果,检测我们的程序。以提高程序的正确性,稳定性,可靠性,节省程序开发时间。我们在项目中主要用到的单元测试框架有 Spring-Boot-Test TestNG、PowerMock 等。文章源自玩技e族-https://www.playezu.com/194274.html

TestNG,即 Testing, Next Generation,下一代测试技术,是一套根据 JUnit 和 NUnit 思想而构建的利用注释来强化测试功能的一个测试框架,即可以用来做单元测试,也可以用来做集成测试。文章源自玩技e族-https://www.playezu.com/194274.html

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

常用注解

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 依赖依赖如下:文章源自玩技e族-https://www.playezu.com/194274.html

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-test</artifactId>
  4. <scope>test</scope>
  5. </dependency>
  6. <dependency>
  7. <groupId>org.testng</groupId>
  8. <artifactId>testng</artifactId>
  9. <version>${testng.version}</version>
  10. <scope>test</scope>
  11. </dependency>
  12. <dependency>
  13. <groupId>org.powermock</groupId>
  14. <artifactId>powermock-api-mockito2</artifactId>
  15. <version>${powermock.version}</version>
  16. <scope>test</scope>
  17. </dependency>
  18. <dependency>
  19. <groupId>org.powermock</groupId>
  20. <artifactId>powermock-module-junit4</artifactId>
  21. <version>${powermock.version}</version>
  22. <scope>test</scope>
  23. </dependency>
  24. <dependency>
  25. <groupId>org.powermock</groupId>
  26. <artifactId>powermock-module-testng</artifactId>
  27. <version>${powermock.version}</version>
  28. <scope>test</scope>
  29. </dependency>

2. 增加单元测试

增加测试代码文章源自玩技e族-https://www.playezu.com/194274.html

  1. importcom.test.testng.dto.OrderDto;
  2. importcom.test.testng.dto.UserDto;
  3. importorg.mockito.*;
  4. importorg.powermock.modules.testng.PowerMockTestCase;
  5. importorg.testng.annotations.BeforeMethod;
  6. importorg.testng.annotations.Test;
  7. importstaticorg.junit.jupiter.api.Assertions.*;
  8. importstaticorg.mockito.Mockito.when;
  9. publicclassOrderServiceTestextendsPowerMockTestCase{
  10. @BeforeMethod
  11. publicvoidbefore(){
  12. MockitoAnnotations.openMocks(this);
  13. }
  14. @InjectMocks
  15. privateOrderServiceorderService;
  16. @Mock
  17. privateUserServiceuserService;
  18. //正常测试
  19. @Test
  20. publicvoidtestCreateOrder(){
  21. //1.mockmethodstart
  22. UserDtouserDto=newUserDto();
  23. userDto.setId(100);
  24. when(userService.get()).thenReturn(userDto);
  25. //2.callbusinessmethod
  26. OrderDtoorder=orderService.createOrder(newOrderDto());
  27. //3.assert
  28. assertEquals(order.getId(),100);
  29. }
  30. //异常测试
  31. @Test
  32. publicvoidtestCreateOrderEx(){
  33. //1.mockmethodstart
  34. when(userService.get()).thenThrow(newRuntimeException());
  35. Exceptionexception=null;
  36. try{
  37. //2.callbusinessmethod
  38. orderService.createOrder(newOrderDto());
  39. }catch(RuntimeExceptione){
  40. exception=e;
  41. }
  42. //3.assert
  43. assertNotNull(exception);
  44. }
  45. }

常用 Mock 方式

1. Mock 静态方法

  1. //静态方法
  2. UserDtodto=newUserDto();
  3. dto.setId(100000);
  4. PowerMockito.mockStatic(UserService.class);
  5. PowerMockito.when(UserService.loginStatic()).thenReturn(dto);
  6. UserDtouserDto=UserService.loginStatic();
  7. assertEquals(100000,userDto.getId().intValue());

2. Mock 私有属性

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

3. Mock 私有方法

  1. //模拟私有方法
  2. MemberModifier.stub(MemberMatcher.method(UserService.class,"get1")).toReturn(newUserDto());
  3. //测试私有方法
  4. Methodmethod=PowerMockito.method(UserService.class,"get1",Integer.class);
  5. ObjectuserDto=method.invoke(userService,1);
  6. assertTrue(userDtoinstanceofUserDto);

进阶使用

1. 参数化批量测试

在测试数据比较多的时候,我们可以通过 @DataProvider 生成数据源,通过 @Test(dataProvider = "xxx") 使用数据, 如下所示:文章源自玩技e族-https://www.playezu.com/194274.html

  1. importcom.test.testng.BaseTest;
  2. importcom.test.testng.dto.UserDto;
  3. importorg.mockito.InjectMocks;
  4. importorg.testng.annotations.DataProvider;
  5. importorg.testng.annotations.Test;
  6. importstaticorg.testng.Assert.assertFalse;
  7. importstaticorg.testng.AssertJUnit.assertTrue;
  8. publicclassUserServiceTest2extendsBaseTest{
  9. @InjectMocks
  10. privateUserServiceuserService;
  11. //定义数据源
  12. @DataProvider(name="test")
  13. publicstaticObject[][]userList(){
  14. UserDtodto1=newUserDto();
  15. UserDtodto2=newUserDto();
  16. dto2.setSex(1);
  17. UserDtodto3=newUserDto();
  18. dto3.setSex(1);
  19. dto3.setFlag(1);
  20. UserDtodto4=newUserDto();
  21. dto4.setSex(1);
  22. dto4.setFlag(1);
  23. dto4.setAge(1);
  24. returnnewObject[][]{{dto1,null},{dto2,null},{dto3,null},{dto4,null}};
  25. }
  26. //正确场景
  27. @Test
  28. publicvoidtestCheckEffectiveUser(){
  29. UserDtodto=newUserDto();
  30. dto.setSex(1);
  31. dto.setFlag(1);
  32. dto.setAge(18);
  33. booleanresult=userService.checkEffectiveUser(dto);
  34. assertTrue(result);
  35. }
  36. //错误场景
  37. @Test(dataProvider="test")
  38. publicvoidtestCheckEffectiveUser(UserDtodto,Objectobject){
  39. booleanresult=userService.checkEffectiveUser(dto);
  40. assertFalse(result);
  41. }
  42. }

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

案例:文章源自玩技e族-https://www.playezu.com/194274.html

1.判断有效用户: 年龄大于 18 并且 sex = 1 并且 flag = 1文章源自玩技e族-https://www.playezu.com/194274.html

  1. publicbooleancheckEffectiveUser(UserDtodto){
  2. //判断有效用户:年龄大于18并且sex=1并且flag=1
  3. returnObjects.equals(dto.getSex(),1)&&
  4. Objects.equals(dto.getFlag(),1)&&
  5. dto.getAge()!=null&&dto.getAge()>=18;
  6. }

2.拆分逻辑。将其转换为最简单的 if ... else 语句。然后增加的单元测试,如下所示:文章源自玩技e族-https://www.playezu.com/194274.html

  1. publicbooleancheckEffectiveUser(UserDtodto){
  2. if(!Objects.equals(dto.getSex(),1)){
  3. returnfalse;
  4. }
  5. if(!Objects.equals(dto.getFlag(),1)){
  6. returnfalse;
  7. }
  8. if(dto.getAge()==null){
  9. returnfalse;
  10. }
  11. if(dto.getAge()<18){
  12. returnfalse;
  13. }
  14. returntrue;
  15. }

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

  1. publicclassUserServiceTestextendsBaseTest{
  2. @InjectMocks
  3. privateUserServiceuserService;
  4. //覆盖第一个return
  5. @Test
  6. publicvoidtestCheckEffectiveUser_0(){
  7. UserDtodto=newUserDto();
  8. booleanresult=userService.checkEffectiveUser(dto);
  9. assertFalse(result);
  10. }
  11. //覆盖第二个return
  12. @Test
  13. publicvoidtestCheckEffectiveUser_1(){
  14. UserDtodto=newUserDto();
  15. dto.setSex(1);
  16. booleanresult=userService.checkEffectiveUser(dto);
  17. assertFalse(result);
  18. }
  19. //覆盖第三个return
  20. @Test
  21. publicvoidtestCheckEffectiveUser_2(){
  22. UserDtodto=newUserDto();
  23. dto.setSex(1);
  24. dto.setFlag(1);
  25. booleanresult=userService.checkEffectiveUser(dto);
  26. assertFalse(result);
  27. }
  28. //覆盖第四个return
  29. @Test
  30. publicvoidtestCheckEffectiveUser_3(){
  31. UserDtodto=newUserDto();
  32. dto.setSex(1);
  33. dto.setFlag(1);
  34. dto.setAge(1);
  35. booleanresult=userService.checkEffectiveUser(dto);
  36. assertFalse(result);
  37. }
  38. //覆盖第五个return
  39. @Test
  40. publicvoidtestCheckEffectiveUser_4(){
  41. UserDtodto=newUserDto();
  42. dto.setSex(1);
  43. dto.setFlag(1);
  44. dto.setAge(18);
  45. booleanresult=userService.checkEffectiveUser(dto);
  46. assertTrue(result);
  47. }
  48. }

4.单测覆盖率检测检测

TestNG + PowerMock 单元测试插图1

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

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

  1. inta=0,b=1;
  2. asserta==0&&b==0;
  3. //使用方法:javac编译源文件,再java-eaclass文件名即可。

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

  1. //检查年龄>=18岁
  2. publicbooleancheckUserAge(UserDtodto){
  3. Assert.notNull(dto.getAge(),"用户年龄不能为空");
  4. Assert.isTrue(dto.getAge()>=18,"用户年龄不能小于18岁");
  5. returnBoolean.TRUE;
  6. }

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

  1. @ControllerAdvice
  2. publicclassGlobalExceptionHandler{
  3. @ResponseBody
  4. @ExceptionHandler(value=IllegalArgumentException.class)
  5. publicResponse<String>handleArgError(IllegalArgumentExceptione){
  6. returnnewResponse().failure().message(e.getMessage());
  7. }
  8. }

总结

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

  1. 模块大小适中
  2. 合适的系统调用深度
  3. 多扇入、少扇出(增加复用度, 减少依赖程度)
  4. 单入口,单出口
  5. 模块的作用域,应该在模块内
  6. 功能应该可以预测的
  7. 高内聚,低耦合
  8. 系统分解有层次
  9. 较少的数据冗余

参考文档

https://testng.org/doc/

https://github.com/powermock/powermock

https://www.netconcepts.cn/detail-41004.html

 
匿名

发表评论

匿名网友
确定

拖动滑块以完成验证