2021-07-05

3 篇文章 0 订阅
1 篇文章 0 订阅

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>
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值