1.单元测试相关知识 单元测试概念
相关注解 @Test 标识当前是一个单元测试方法 @Before会在单元测试执行之前做一些事情 @Ignore:这个注释是用来忽略有关不需要执行的测试的。
断言Assert
简单来讲 断言就是"判断"。 在单元测试中 我们可以针对接口预期的返回结果,进行判断
Junit所有的断言都包含在 Assert 类中。
Assert 类中的一些有用的方法列式如下:
void assertEquals(boolean expected, boolean actual):检查两个变量或者等式是否平衡
void assertTrue(boolean expected, boolean actual):检查条件为真
void assertFalse(boolean condition):检查条件为假
void assertNotNull(Object object):检查对象不为空
void assertNull(Object object):检查对象为空
void assertSame(boolean condition):assertSame() 方法检查两个相关对象是否指向同一个对象
void assertNotSame(boolean condition):assertNotSame() 方法检查两个相关对象是否不指向同一个对象
void assertArrayEquals(expectedArray, resultArray):assertArrayEquals() 方法检查两个数组是否相等
使用单元测试好处
可以书写一系列的测试方法,对项目所有的接口或者方法进行单元测试。 启动后,自动化测试,并判断执行结果, 不需要人为的干预。(使用Assert断言) 只需要查看最后结果,就知道整个项目的方法接口是否通畅。(以防止修改代码之后出现不必要的bug遗漏) 添加,删除,屏蔽测试方法,不影响其他的测试方法。(比如可以对方法、类上添加@Ignore注解以跳过这个测试 但是不推荐)
2.单元测试实操
单元测试中含有Rpc的测试思路 单元测试中含有RPC带来的问题: 由于强依赖于对方的服务,如果远程服务未发布,或者对方服务出现问题,将会导致单元测试的失败。 RPC可能涉及到一系列的逻辑,比如控制中枢接口,即使我们传递了正确的参数,依然需要在雅典娜上进行一系列的配置 才会返回命中,其实他具体的规则 我们是不关心的 如果直接调用RPC ,我们就需要去真实去修改各种配置项目,已达到命中的目的,比较费时。 针对以上的几点,我们可以使用Bean后置处理器,以及Mockito工具,进行模拟接口请求,使得原有需要借助网络的请求,直接在本地模拟出来返回值 Mock本地模拟的好处 灵活,不需要关注对方RPC服务一系列的规则配置,可以自定义返回值【重要】,入参 稳定,不需要依赖网络,和真实的服务。确保单元测试可以稳定运行
使用Mockito 进行Mock
mockito配可以将被注解注入的对象生成一个Mock对象。我们可以针对这个Mock对象,进行预期的行为
例如我们要测试A类,A类的依赖关系如下
假如Class B是一个IO/RPC引用,我们可以尝试去创建mock对象
例子:
@Mock
IntermodalServiceI intermodalServiceI; //这是一个RPC服务 相当于ClassB
@Test
public void testMock(){
//打桩 //自定义行为
IntermodalApp intermodalApp = new IntermodalApp();
intermodalApp.setAppName("Mock游戏名字");
Mockito.when(intermodalServiceI.selectAppByGameId(1)).thenReturn(ResponseEntity.success(intermodalApp));
//调用rpc测试
IntermodalApp intermodalAppReturn = ResponseEntity.wrap(intermodalServiceI.selectAppByGameId(2));
System.out.println(intermodalAppReturn.getAppName()); //这个还会走真实的RPC
intermodalAppReturn = ResponseEntity.wrap(intermodalServiceI.selectAppByGameId(1));
System.out.println(intermodalAppReturn.getAppName()); //这个会输出Mock的游戏名字
}
如果传递任何的Int参数,我们都希望能够进行Mock调用,可以这样写
Mockito.when(intermodalServiceI.selectAppByGameId(Mockito.anyInt())).thenReturn(ResponseEntity.success(intermodalApp));
效果
以上简单的写法,可以模拟真实的dubbo请求,并自定义响应。但是真实的情况往往没那么简单
我们依然要测试Class A, ClassA 引用了本地的ClassB(B不需要Mock) , Class D是我们需要Mock的对象。如图:
举例 ,创建一个ClassB
@Service
public class ClassB {
@DubboReference
IntermodalServiceI intermodalServiceI; //Class D
public IntermodalApp testB(int gameId){
return ResponseEntity.wrap(intermodalServiceI.selectAppByGameId(gameId));
}
}
这时候Mock RPC是会失败的,这是因为单元测试中虽然对RPC进行了Mock和打桩。但是和ClassB中会真实创建RPC对象的引用,这两个RPC并不是同一个对象。所以导致了失败
尝试在真实业务中加上@Getter方法,手动使用Mockito.mock()创建Mock对象
@Service
public class ClassB {
@Getter
@DubboReference
IntermodalServiceI intermodalServiceI; //Class D
public IntermodalApp testB(int gameId){
return ResponseEntity.wrap(intermodalServiceI.selectAppByGameId(gameId));
}
}
//发现还是不行,这是因为我们只是基于某一个类 生成了Mock对象,然而实际ClassB中注入的依然是原始Rpc代理。对象不是同一个 自然无法Mock成功
@Test
public void testMock(){
//打桩 //自定义行为
IntermodalApp intermodalApp = new IntermodalApp();
intermodalApp.setAppName("Mock游戏名字");
IntermodalServiceI mockServiceI = Mockito.mock(classB.getIntermodalServiceI().getClass());
Mockito.when(mockServiceI.selectAppByGameId(Mockito.anyInt())).thenReturn(ResponseEntity.success(intermodalApp));
//调用Class B测试
IntermodalApp intermodalAppReturn = classB.testB(2);
System.out.println(intermodalAppReturn.getAppName());
}
解决方式:
写一个Bean后置处理器, 偷偷的把真实代理 替换成我们Mock对象。这样就可以满足真实业务场景了
Mock RPC接口 / Mock Mq的生产者
注册这个bean处理器,并在BaseTest中
@Import({TestConfig.class})
使用方式:
@Autowired
ClassB classB;
@Test
public void testMock(){
//打桩 //自定义行为
IntermodalApp intermodalApp = new IntermodalApp();
intermodalApp.setAppName("Mock游戏名字");
Mockito.when(classB.getIntermodalServiceI().selectAppByGameId(Mockito.anyInt()))
.thenReturn(ResponseEntity.success(intermodalApp));
//调用Class B测试
IntermodalApp intermodalAppReturn = classB.testB(2);
System.out.println(intermodalAppReturn.getAppName());
Assert.assertNotNull("游戏数据不能为空!" , intermodalApp );
}
3.单元测试覆盖率
编写业务Biz层, 每写完一个业务方法,即开始对其进行测试。一般来说每个单元测试方法下都应该有一个Assert断言判断
举例 限额功能 流程:针对一个用户 ,首先调用控制中枢接口判断游戏是否开启限额开关,如果开启调用实名认证接口 拿到用户的年龄。再根据年龄进行一系列的计算统计,返回对应的额度限制【简化的流程】
首先构思应该减少依赖,因为限额计算依赖于 控制中枢返回值,首先将控制中枢的调用拆出去。 这样对于限额计算的单元测试就更加方便.。否则控制中枢就必须返回true 才可以测的到限额。
针对限额方法进行单元测试(注意使用的是Junit下的@Test注解 别用错了)
测试
@Test(expected = BusinessServiceException.class) //expected代表这个单元测试预期抛出什么异常类
public void validRechargeLimit() {
RealNameAuthResponse realNameAuthResponse= RealNameAuthResponse.builder().uuid(UUID).age(8).cardNo("dsfadfsdf").packageName("com.meta.box")
.verifyStatus(1).build();
//参数1 下单扩展信息 参数2:本次实付金额 参数3:实名信息 包含用户的年龄等等
validOrderBiz.validRechargeLimit(null , 5000 , realNameAuthResponse);
}
略,观察sonar,本地的coverage, 看代码覆盖情况,针对业务情况进行增加Case!!!!!!!!!!!!!
4.集成jacoco插件生成测试报告 提交至sonar(略)
主pom添加sonar配置属性
<!-- Sonar-Jacoco -->
<sonar.java.coveragePlugin>jacoco</sonar.java.coveragePlugin>
<sonar.dynamicAnalysis>reuseReports</sonar.dynamicAnalysis>
<sonar.coverage.jacoco.xmlReportPaths>
${project.basedir}/../meta-finance-base-start/target/site/jacoco-aggregate/jacoco.xml
</sonar.coverage.jacoco.xmlReportPaths>
<sonar.language>java</sonar.language>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.5</version>
</plugin>
start模块中添加插件,主要是配置聚合报告(说白了就是start项目依赖那个模块,就会检测哪块的覆盖,一般是依赖service模块)
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<executions>
<execution>
<id>report-aggregate</id>
<phase>verify</phase>
<goals>
<goal>report-aggregate</goal>
</goals>
</execution>
</executions>
</plugin>
|