51Testing软件测试论坛

 找回密码
 (注-册)加入51Testing

QQ登录

只需一步,快速开始

微信登录,快人一步

查看: 594|回复: 1
打印 上一主题 下一主题

[转贴] Javaer 如何做单元测试?(二)

[复制链接]

该用户从未签到

跳转到指定楼层
1#
发表于 2022-8-2 14:44:23 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
本帖最后由 草帽路飞UU 于 2022-8-10 16:35 编辑

III.使用Mock,在执行单元测试前,将依赖但又没办法获取到实现类的 bean 注入进去。
  将mokito包加入项目。
<!-- https://mvnrepository.com/artifact/org.mockito/mockito-inline -->
  <dependency>
      <groupId>org.mockito</groupId>
      <artifactId>mockito-inline</artifactId>
      <version>4.5.1</version>
      <scope>test</scope>
  </dependency>
  <!-- https://mvnrepository.com/artifact/org.mockito/mockito-core -->
  <dependency>
      <groupId>org.mockito</groupId>
      <artifactId>mockito-core</artifactId>
      <version>4.5.1</version>
      <scope>test</scope>
  </dependency>
  <!-- https://mvnrepository.com/artifact/net.bytebuddy/byte-buddy-agent -->
  <dependency>
      <groupId>net.bytebuddy</groupId>
      <artifactId>byte-buddy-agent</artifactId>
      <version>1.12.9</version>
      <scope>test</scope>
  </dependency>
  <!-- https://mvnrepository.com/artifact/net.bytebuddy/byte-buddy -->
  <dependency>
      <groupId>net.bytebuddy</groupId>
      <artifactId>byte-buddy</artifactId>
      <version>1.12.8</version>
  </dependency>
使用 @MockBean 和 MockitoAnnotations.openMocks(this) 可以将依赖的 bean 注入进去。
@ExtendWith(SpringExtension.class)
  @SpringBootTest(classes = CommonTestApplication.class)
  public class CommonEntityServiceTest {
      private final Logger logger = LoggerFactory.getLogger(this.getClass());
      @Autowired
      private CommonEntityService commonEntityService;
      @MockBean
      public WebRpc webRpc;
      @MockBean
      public WebRpc2 webRpc2;
      @BeforeEach
      public void before(){
          MockitoAnnotations.openMocks(this);
      }
      @Test
      public void test() {
          ApiResult<Void> testSuccess = commonEntityService.test(new CommonEntity());
          Assert.isTrue(testSuccess.getSuccess(), "testSuccess fail");
          logger.info("testSuccess: {}", JSON.toJSONString(testSuccess));
      }
  }
此时再执行 test() 方法,不再出现 NoSuchBeanDefinitionException 异常,但会出现 NullPointerException 异常。这是因为我们虽然注入了 bean,但这个 bean 是个空的,因此在 commonEntityService.test 方法中执行 webRpc.get() 时,会报 NullPointerException 异常。为解决这个问题,我们可以继续使用 mock,Mockito.when(). thenReturn()。
@ExtendWith(SpringExtension.class)
  @SpringBootTest(classes = CommonTestApplication.class)
  public class CommonEntityServiceTest {
      private final Logger logger = LoggerFactory.getLogger(this.getClass());
      @Autowired
      private CommonEntityService commonEntityService;
      @MockBean
      public WebRpc webRpc;
      @MockBean
      public WebRpc2 webRpc2;
      @BeforeEach
      public void before(){
          MockitoAnnotations.openMocks(this);
      }
      @Test
      public void test() {
          Mockito.when(webRpc.get()).thenReturn(ApiResult.success("mock result 1"));
          Mockito.when(webRpc.get2("test")).thenReturn(ApiResult.success("mock result 2"));
          ApiResult<Void> testSuccess = commonEntityService.test(new CommonEntity());
          Assert.isTrue(testSuccess.getSuccess(), "testSuccess fail");
          logger.info("testSuccess: {}", JSON.toJSONString(testSuccess));
      }
  }
再次执行 test() 方法,此时执行已经成功了,打印日志如下所示。
2022-05-21 22:23:23.094  INFO 3760 --- [           main] c.p.j.s.c.c.s.i.CommonEntityServiceImpl  : insert 0 common entity
  2022-05-21 22:23:23.161  INFO 3760 --- [           main] c.p.j.s.c.c.s.CommonEntityServiceTest    : apiResult: {"code":"200","msg":"调用成功","success":true}
虽然已经成功执行了单元测试,但如果需要 mock 的 bean 很多的话,那不是每个测试类都需要写一遍 mock,很浪费时间啊,因此,我们可以把需要 mock 的 bean 全都放到一个类中进行管理。
@Component
  public class CommonMockFactory {
      @BeforeEach
      public void before(){
          MockitoAnnotations.openMocks(this);
      }
      @MockBean
      public WebRpc webRpc;
      @MockBean
      public WebRpc2 webRpc2;
  }
然后在需要单元测试的类中进行注入即可
@ExtendWith(SpringExtension.class)
  @SpringBootTest(classes = CommonTestApplication.class)
  public class CommonEntityServiceTest {
      private final Logger logger = LoggerFactory.getLogger(this.getClass());
      @Autowired
      private CommonEntityService commonEntityService;
      @Autowired
      private CommonMockFactory commonMockFactory;
      @Test
      public void test() {
          Mockito.when(commonMockFactory.webRpc.get()).thenReturn(ApiResult.success("mock result 1"));
          Mockito.when(commonMockFactory.webRpc.get2("test")).thenReturn(ApiResult.success("mock result 2"));
          ApiResult<Void> testSuccess = commonEntityService.test(new CommonEntity());
          Assert.isTrue(testSuccess.getSuccess(), "testSuccess fail");
          logger.info("testSuccess: {}", JSON.toJSONString(testSuccess));
      }
  }
2-3.提高单元测试覆盖率
  使用idea自带的单元测试覆盖率工具可以查看相应的覆盖率。绿色的条代表已覆盖,红色的条代表未覆盖。

以下是单元测试的覆盖率文档,分别是类覆盖率、方法覆盖率、行覆盖率,从图中可以看出我们的行覆盖率只有64%,还有提升的空间。

如何提升呢?答案就是 mock。
  先上改造后的代码:
@ExtendWith(SpringExtension.class)
  @SpringBootTest(classes = CommonTestApplication.class)
  public class CommonEntityServiceTest {
      private final Logger logger = LoggerFactory.getLogger(this.getClass());
      @Autowired
      private CommonEntityService commonEntityService;
      @Autowired
      private CommonMockFactory commonMockFactory;
      @Test
      public void test() {
          Mockito.when(commonMockFactory.webRpc.get()).thenReturn(ApiResult.success("mock result 1"));
          Mockito.when(commonMockFactory.webRpc.get2("test")).thenReturn(ApiResult.success("mock result 2"));
          ApiResult<Void> testSuccess = commonEntityService.test(new CommonEntity());
          Assert.isTrue(testSuccess.getSuccess(), "testSuccess fail");
          logger.info("testSuccess: {}", JSON.toJSONString(testSuccess));
      }
      @Test
      public void testWithMock() {
          Mockito.when(commonMockFactory.webRpc.get()).thenReturn(ApiResult.success("mock result 1"));
          Mockito.when(commonMockFactory.webRpc.get2("test")).thenReturn(ApiResult.success("mock result 2"));
          ApiResult<Void> testSuccess = commonEntityService.test(new CommonEntity());
          Assert.isTrue(testSuccess.getSuccess(), "testSuccess fail");
          logger.info("testSuccess: {}", JSON.toJSONString(testSuccess));
          // 模拟 webRpc.get() 失败
          Mockito.when(commonMockFactory.webRpc.get()).thenReturn(ApiResult.error(BizRespStatusEnum.ILLEGAL_PARAM));
          Mockito.when(commonMockFactory.webRpc.get2("test")).thenReturn(ApiResult.success("mock result 2"));
          ApiResult<Void> testFail1 = commonEntityService.test(new CommonEntity());
          Assert.isTrue(!testFail1.getSuccess(), "testFail1 fail");
          logger.info("testFail1: {}", JSON.toJSONString(testFail1));
          Mockito.when(commonMockFactory.webRpc.get()).thenReturn(ApiResult.success("mock result 1"));
          // 模拟 webRpc.get2() 失败
          Mockito.when(commonMockFactory.webRpc.get2("test")).thenReturn(ApiResult.error(BizRespStatusEnum.ILLEGAL_PARAM));
          ApiResult<Void> testFail2 = commonEntityService.test(new CommonEntity());
          Assert.isTrue(!testFail2.getSuccess(), "testFail1 fail");
          logger.info("testFail2: {}", JSON.toJSONString(testFail2));
          Mockito.when(commonMockFactory.webRpc.get()).thenReturn(ApiResult.success("mock result 1"));
          Mockito.when(commonMockFactory.webRpc.get2("test")).thenReturn(ApiResult.success("mock result 2"));
          try (MockedStatic<RmiUtil> rmiUtilMockedStatic = Mockito.mockStatic(RmiUtil.class)) {
              // 模拟 RmiUtil.getRemoteResult() 失败
              rmiUtilMockedStatic.when(RmiUtil::getRemoteResult).thenReturn(Optional.empty());
              ApiResult<Void> testFail3 = commonEntityService.test(new CommonEntity());
              Assert.isTrue(!testFail3.getSuccess(), "testFail3 fail");
              logger.info("testFail3: {}", JSON.toJSONString(testFail3));
          }
      }
  }















分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
收藏收藏
回复

使用道具 举报

该用户从未签到

2#
 楼主| 发表于 2022-8-2 14:46:37 | 只看该作者
单元测试的执行结果。
2022-05-21 23:23:46.516  INFO 35136 --- [           main] c.p.j.s.c.c.s.i.CommonEntityServiceImpl  : insert 0 common entity
  2022-05-21 23:23:46.589  INFO 35136 --- [           main] c.p.j.s.c.c.s.CommonEntityServiceTest    : testSuccess: {"code":"200","msg":"调用成功","success":true}
  2022-05-21 23:23:46.590  INFO 35136 --- [           main] c.p.j.s.c.c.s.i.CommonEntityServiceImpl  : getRpc fail: ApiResult{success=false, code='400', msg='参数异常', result=null}
  2022-05-21 23:23:46.590  INFO 35136 --- [           main] c.p.j.s.c.c.s.CommonEntityServiceTest    : testFail1: {"code":"400","msg":"参数异常","success":false}
  2022-05-21 23:23:46.591  INFO 35136 --- [           main] c.p.j.s.c.c.s.i.CommonEntityServiceImpl  : getRpc2 fail: ApiResult{success=false, code='400', msg='参数异常', result=null}
  2022-05-21 23:23:46.591  INFO 35136 --- [           main] c.p.j.s.c.c.s.CommonEntityServiceTest    : testFail2: {"code":"400","msg":"参数异常","success":false}
  2022-05-21 23:23:46.629  INFO 35136 --- [           main] c.p.j.s.c.c.s.i.CommonEntityServiceImpl  : getRemoteResult fail
  2022-05-21 23:23:46.629  INFO 35136 --- [           main] c.p.j.s.c.c.s.CommonEntityServiceTest    : testFail3: {"code":"002","msg":"系统异常","success":false}

再来看看改造之后的覆盖率!从下图中可以看出单元测试的行覆盖率达到了100%,惊不惊喜,意不意外!



  3、总结
  在我们没用 mock 工具时,别说覆盖率了,执行一个单元测试都很麻烦。
  使用 mock 工具之后,我们不仅可以很方便的执行单元测试,还能使用各种奇技淫巧来提升行覆盖率,强烈推荐!
  写好单元测试一点都不简单,本文只是拿了一个简单的场景来举例,在单元测试的行覆盖率达到100%时,代码量就已经是源码的两倍还多了,害!但是 bug 和单元测试总要选一个的,就看大家的选择了~


本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?(注-册)加入51Testing

x
回复 支持 反对

使用道具 举报

本版积分规则

关闭

站长推荐上一条 /1 下一条

小黑屋|手机版|Archiver|51Testing软件测试网 ( 沪ICP备05003035号 关于我们

GMT+8, 2024-4-28 11:12 , Processed in 0.063394 second(s), 23 queries .

Powered by Discuz! X3.2

© 2001-2024 Comsenz Inc.

快速回复 返回顶部 返回列表