Java DT 指导说明

[toc]

背景

  1. 在公司的CleanCode的大背景下,践行开发者自测试(TDD)也是CleanCode文化比较重要的内容之一。
  2. 在现实的软件开发过程中,我们经常需要协同其他同事一起来完成某个模块的功能开发,或者需要第三方资源,比如您需要一个短信网关,您需要一个数据库,您需要一个消息中间件,当您进

行测试依赖于这些资源的代码时候,不可能在每次都具备相应的环境,这将是一个很不现实的问题,如果当您依赖于其他模块而无法进行单元测试的时候,此时该模块的质量风险就有两个,第一是

您所负责的代码,第二是您所依赖的代码,您所依赖您没有办法在很快的时间协调到资源,那么您所负责的代码由于不具备单元测试环境没有办法进行测试,很可能存在极大的风险,因此如何测试

您的代码,让他的质量达到百分之百的可用,这就是 Mock 存在的必要 。

  1. **您使用集成测试做单元测试么?**

很多人所谓的测试恐怕更多的是一种集成测试,也就是点击页面某个按钮看看是否能够顺利执行,所依赖的各种资源都必须正常运行,如果出现问题就很难让整个流程执行下去,
我们如何在没有数据库的时候能够测试我们的 Service,这才是单元测试 Mock 要解决的问题

一、Java Mock框架

查询了当前阶段可查询到的Mock框架并且进行了一个简单的对比:

  • EasyMock
  • jMock
  • Mockito
  • Unitils Mock
  • PowerMock(EasyMock Api)
  • PowerMock(Mockito Api)
  • JMockit

其中PowerMock可以基于EasyMock,也可以基于Mockito,PowerMock是两者的增强版,所以EasyMock和Mockito可以不考虑;

之所以提到PowerMock而不是Mock,是因为自己服务器端的配置数据获取的方法是静态方法,如果使用mock方式来模拟数据,只有PowerMock支持Mock静态方法,Mock不支持。

二、测试框架集成

2.1、引入PowerMock

image.png

2.2、 DT配置文件

放在config目录下,大部分内容可与非test非保持一致

image.png

2.3 、启动类

基本与主启动类保持一致,但是配置文件需要执行DT的测试文件

  1. @SpringBootApplication
  2. @TestPropertySource(properties={"spring.config.location=classpath:application.yml"})
  3. @ComponentScan(
  4. basePackages = {
  5. "com.huawei.riemann.cloud.sdk.conf",
  6. "com.huawei.riemann.cloud.notice"
  7. },
  8. includeFilters = {
  9. @ComponentScan.Filter(
  10. type = FilterType.ASSIGNABLE_TYPE,
  11. value = {EurekaSSLConfig.class, FeignSSLBalanceConfig.class, TomcatSSLContainer.class})
  12. },
  13. excludeFilters = {
  14. @ComponentScan.Filter(
  15. type = FilterType.ASSIGNABLE_TYPE,
  16. value = {FeignSSLDefaultConfig.class, KafkaConfig.class})
  17. })
  18. public class TestApplication {
  19. public static void main(String[] args) {
  20. SpringApplication.run(TestApplication.class, args);
  21. }
  22. }

2.4 、主要配置类

  1. /**
  2. * 功能描述: 测试类
  3. * @since 2021-11-12
  4. */
  5. @WebAppConfiguration
  6. @SpringBootTest(classes = TestApplication.class)
  7. @RunWith(PowerMockRunner.class)
  8. @PowerMockRunnerDelegate(SpringRunner.class)
  9. @PowerMockIgnore({
  10. "javax.management.*", "javax.net.ssl.*", "javax.crypto.*", "javax.xml.*", "org.xml.*", "org.w3c.dom.*",
  11. "org.apache.*"
  12. })
  13. @TestPropertySource(properties = {"spring.config.location=classpath:application.yml"})
  14. public class StarAtlasNoticeBaseTest {
  15. @Autowired
  16. NoticeServiceImpl noticeService;
  17. @Test
  18. public void testDemo() {
  19. System.out.println(1);
  20. }
  21. }
  • SpringBootTest:表明这是一个springboot测试类,class指定的是springboot主启动程序类
  • RunWith:使用powermock自己的Runner,每一个 Runner 都有各自的特殊功能,你要根据需要选择不同的 Runner 来运行你的测试代码。JUnit 中有一个默认 Runner ,即 BlockJUnit4ClassRunner
  • PowerMockRunnerDelegate:将powermock整合到spring容器中。集成PowerMock的时候因为Junit的Runner只能设置一个,所以不知道该设置
    • PowerMockRunner还是SpringRunner。如果设置了PowerMockRunner,虽然可以使用mock和spy功能,但是无法使用Spring提供的功能。
    • PowerMockRunnerDelegate解决了此问题,既可以使用PowerMock的强大的mock和Spy的功能,也可以使用Spring提供的功能
  • PowerMockIgnore: 避免两个类的ClassLoader不同,导致JVM认为这两个类没有关联。PowerMock的工作原理即是使用自定义的类加载器来加载被修改过的类,从而达到打桩的目的。(这里需要是因为不加的话,加解密的时候是会报错的)
  • TestPropertySource: 解决SpringBoot默认加载的顺序第一优先级会加载file:./config/目录下的文件,导致test目录下的application.yml无法被加载的问题,指定加载的yaml文件

2.5、运行测试

Run -> with Coverage

三、Mock方法的常见场景

3.1、Mock返回值为void的方法

  1. PowerMockito.doNothing.when(userService, "method", args);
  2. PowerMockito.doNothing.when(userService).method(args);

3.2 、Mock调用真实方法

  1. UserService userService = PowerMockito.mock(UserService .class);
  2. Whitebox.setInternalState(userService , "field", fildValue); 设置变量值
  3. PowerMockito.when(userService.method(args)).thenCallRealMethod();

3.3、 Mock静态函数返回值

  1. 先要PrepareForTest静态类
  1. @RunWith(PowerMockRunner.class)
  2. @PrepareForTest({LogManager.class, LoadMessageProfile.class, TrunkLinkDesignUtils.class, SpringBeanUtil.class})

先后用mockStatic申明要mock的静态类,定义返回值,返回也可以是mock对象

  1. PowerMockito.mockStatic(SpringBeanUtil.class);
  2. DesignifccfgisisMapper overNeInfoMapper = PowerMockito.mock(DesignifccfgisisMapper.class);
  3. PowerMockito.when(SpringBeanUtil.getBean(DesignifccfgisisMapper.class)).thenReturn(overNeInfoMapper);

3.4 、写私有函数的DT时,可用mock对象invoke反射形式进行调用

  1. // 1. 获取class
  2. Class<?> clazz = obj.getClass();
  3. //2. 创建一个方法的反射对象
  4. Method method = clazz.getDeclaredMethod(methodName, parameterTypes);
  5. //3. 设置对私有方法的访问权限
  6. method.setAccessible(true);
  7. //4. 执行方法
  8. method.invoke(obj, args);

四、DT测试代码覆盖率

4.1、使用步骤

1、在IDEA中安装Code Coverage for Java插件

Java DT测试 - 图3

2、执行测试用例时选择Run Tests xxxx with Coverage,可以选择执行单个测试用例,也可以执行某一个package下的所有测试用例

Java DT测试 - 图4

3、执行完成后,在弹出框中选择“Add to active suites”,就可以把多次执行的结果汇总统计

Java DT测试 - 图5

4、在弹出的Coverage信息框中,可以查看已执行用例的平均覆盖率,某个package下所有类的覆盖率,以及某个类的覆盖率信息,也可以导出覆盖率报告

Java DT测试 - 图6

4.2、常见问题

最多的问题就是无法出现统计信息,当我们无法查看统计信息的时候,最可能的原因:

  • 测试代码的包路径,没有和被测试代码的包路径保持一致。
  • 是的,如果统计信息无法出现,可以看一下Test的配置,是否为 moudle根package。

https://www.codenong.com/cs106948440/:解决无法排除配置文件的方法

如果原有工程报错:

Failed to execute goal org.apache.maven.plugins:maven-surefire-plugin:2.12.4:test (default-test) on project StarAtlasMockDemo: Execution default-test of goal org.apache.maven.plugins:maven-surefire-plugin:2.12.4:test failed: The forked VM terminated without saying properly goodbye. VM crash or System.exit called ?

按照下面操作设置之后,可以正常运行通过: