开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 10 天,点击查看活动详情
实习中拿到另一个改接口的需求,自测后需要通过公司的CI单元测试,然后要自己写单元测试。有一说一单元测试是什么我都没听过,然后就开始了赶鸭子上架的学习+实现。记录下我应对这个任务的学习路程。最后覆盖率也算勉强应对过去了。
我们公司用的应该是PowerMock和Mockito来自己写单元测试,然后Jacoco来统计代码覆盖率
一、原代码
这部分主要是为我添加的功能新建了个Service类。然后里面实现了两个方法,因为比较里面还是有一些敏感的东西,这里就做了一个脱敏。
import FlightMarketUtil;
public class FlightService {
public String function1(String A,String B){
String res = FlightMarketUtil.fun(A,B);
return res;
}
public String function2(String A,String B,String C){
if (C!=null && C!=""){
return C;
}else{
String res = FlightMarketUtil.fun(A,B);
return res;
}
}
}
这个梳理之后逻辑比较简单吧。其实整个需求都是比较简单的,但是各类配置和开发流程还是让我受益良多。
二、概念解析
带我的好大哥让我参考他之前的生成的代码和公司给的文档把这个单元测试写了。虽然灰度已经测完了,在等着上线。但是迟早也要面对自己写单元测试。
然后我就开始读代码的痛苦之旅。因为我确实没接触过这个东西,很多基础概念都不明晰,所以首先我梳理了一下概念。
mock
mock用英文翻译过来,具有虚假的,模拟的模仿的意思。我理解它其实是一种思想。
据百度百科:mock测试就是在测试过程中,对于某些不容易构造或者容易获取的对象,用一个虚拟的对象来创建以便测试的测试方法。
@PrepareForTest :
示例:
@PrepareForTest({FlightMarketUtil.class})
官方解释是当我们虚拟构造final、static、等方法时加以注解。
这里我们mock调用的方法包(FlightMarketUtil)里面的方法都是静态,必然需要加入此注解。一般我们自己的方法包都要加入此注解。
@SuppressStaticInitializationFor : 忽略静态类不必要的初始化
示例:
@SuppressStaticInitializationFor({"com.umetrip.data.UmeFlightTopology.base.scheduledload.FlightMarketUtil"})
官方解释是用此注解的方式来阻止静态代码块中代码的执行。
这个里面全是静态代码,也就是说我们调用这个包的方法去执行的话在测试类中不会真正执行。也不用担心不会返回结果,有指定方法返回结果的。)
@mock:
由Mockito框架根据接口的实现帮我们mock一个虚假对象,该对象的方法不会真正去执行。对函数的调用均执行mock(即虚假函数),不执行真正部分。
@InjectMocks
由Mockito框架根据接口的实现帮我们创建一个实例对象(注意是实例对象,所以调用该类的方法都会被执行)。简单的说是这个Mock可以调用真实代码的方法。
实战案例
1.新建测试类,继承PowerMockTestCase
public class FlightSeviceCITest extends PowerMockTestCase {
}
2.在测试类中创建被测试类实例
@InjectMocks
private FlightService flightService;
3.写一个单元测试模块,
注意@Test引用来自
import org.testng.annotations.Test;
@Test
public void testCommon() {
String A = "A";
String B = "B";
String C = "C";
PowerMockito.mockStatic(FlightMarketUtil.class);//加载静态类
PowerMockito.when(FlightMarketUtil.fun(Mockito.anyString(), Mockito.anyString(), Mockito.anyString())).thenReturn(Optional.of("C"));
//下面的flightService.function2代码中在执行FlightMarketUtil.fun这个方法时,不会真正执行而会直接返回C
flightService.function2(A, B, C);
}
这个单元测试没有测试flightService.function1是因为function2中调用了function1,从覆盖率来讲没问题的。
这个经过测试覆盖率并不高,主要是因为C不为空else部分代码没有覆盖到。所以还需要一个新的案例。
String A = "A";
String B = "B";
String C = "";
PowerMockito.mockStatic(FlightMarketUtil.class);//加载静态类
PowerMockito.when(FlightMarketUtil.fun(Mockito.anyString(), Mockito.anyString(), Mockito.anyString())).thenReturn(Optional.of("C"));
//下面的flightService.function2代码中在执行FlightMarketUtil.fun这个方法时,不会真正执行而会直接返回C
flightService.function2(A, B, C);
合并完整版
public class FlightSeviceCITest extends PowerMockTestCase {
@InjectMocks
private FlightService flightService;
@Test
public void testCommon() {
String A = "A";
String B = "B";
String C = "C";
PowerMockito.mockStatic(FlightMarketUtil.class);//加载静态类
PowerMockito.when(FlightMarketUtil.fun(Mockito.anyString(), Mockito.anyString(), Mockito.anyString())).thenReturn(Optional.of("C"));
//下面的flightService.function2代码中在执行FlightMarketUtil.fun这个方法时,不会真正执行而会直接返回C
flightService.function2(A, B, C);
PowerMockito.mockStatic(FlightMarketUtil.class);//加载静态类
PowerMockito.when(FlightMarketUtil.fun(Mockito.anyString(), Mockito.anyString(), Mockito.anyString())).thenReturn(Optional.of("C"));
//下面的flightService.function2代码中在执行FlightMarketUtil.fun这个方法时,不会真正执行而会直接返回C
flightService.function2(A, B, "");
}
}
这样是一个基本全覆盖的版本了。
三、经验总结
PowerMockito.when(FlightMarketUtil.fun(Mockito.anyString(), Mockito.anyString(), Mockito.anyString())).thenReturn("C");
PowerMockito.when(FlightMarketUtil.class,"fun",Mockito.anyString(), Mockito.anyString(), Mockito.anyString()).thenReturn("C");
这两个方法看起来一样,但是第一种return对象为list时出了问题,应该是解析出了点差错,导致一部分一直覆盖不到。
@PrepareForTest
@SuppressStaticInitializationFor
这两个注解就给他锁死吧。因为加入的类多了,很可能踩坑。