Mockito和Spock实战
最佳答案 问答题库668位专家为你答疑解惑
文章目录
- 一、背景
- 1.1、启动spring服务的集成测试
- 1.2、单元测试
- 二、单测规范
- 2.1 命名
- 2.2单元测试
- 2.3 有效断言
- 三、Mockito
- 3.1pom
- 3.2常用注解
- 3.2.1 @RunWith
- 3.2.2 @InjectMocks
- 3.2.3 @Mock注解
- 3.2.4 @Spy
- 3.2.5@Before
- 3.2.6 @Ignore
- 3.3常用方法
- 3.3.1 参数相关-any ()
- 3.3.2 Mock使用-mock()
- 3.3.3 mockStatic()
- 3.3.4 when()
- 3.3.5 reset()
- 3.3.6 mock方法返回值-doNothing()
- 3.3.7 thenReturn()
- 3.3.8 doReturn()
- 3.3.9 doThrow()和thenThrow()
- 3.3.10 thenCallRealMethod()
- 3.3.11 verify()
- 四、Spock
- 4.1环境配置
- 4.2创建spock测试类
- 4.3 类
- 4.4 方法 -标签
- 4.4.1 given
- 4.4.2 when
- 4.4.3 then
- 4.4.4 where
- 4.5 常用注解(Ignore、Unroll)
- 4.5.1 Ignore
- 4.5.2 @Unroll
- 4.5.3 mock 静态、私有方法、final相关注解
- 4.6 字符含义(_ 、 * 、 # 、 >>)
- 4.6.1 _下划线
- 4.6.2
- 4.6.3 *
- 4.6.4 >>
- 五、实战
- 5.1 mock-网络资源(DB、Rpc、线程池、Redis等)
- 5.1.1MyLion
- 5.1.1.1 使用Mockito
- 5.1.1.2 使用Spock
- 5.1.2 Mockito - DB、RPC
- 5.1.3 Mockito MyMcc
- 5.1.4 Mockito - Redis
- 5.1.5 Mockito - 线程池
- 5.1.6 Spock - DB、Rpc、MyMcc、Redis和线程池
- 5.2 mock-static静态方法
- 5.2.1 Mockito
- 5.2.2 Spock
- 5.3 mock-private私有方法
- 5.3.1 Mockito
- 5.3.2 Spock
- 5.4 mock-private私有方法 + mock 网络资源
- 5.4.1 Mockito
- 5.4.2 Spock
- 5.5 mock-参数校验
- 5.5.1 Mockito
- 5.5.2 Spock
- 5.6 mock-循环(for、while)
- 5.6.1 Mockito
- 5.6.2 Spock
- 5.7 打印日志
一、背景
需求倒排,时间紧,任务重。很多测试都是集成测试,且需要启动Spring。
1.1、启动spring服务的集成测试
@RunWith(SpringRunner.class)
@SpringBootTest(classes = ApplicationLoader.class)
@Slf4j
public class BaseTest {// ---}
public class MyServiceTest extends BaseTest {@Resourceprivate MyService myService;@Testpublic void queryUserInfoTest() {Map<Long, Long> map = myService.func(323L));Assert.assertNotNull(map);}
}
- 优点:可以一把debug到底2、节省了写单测时间
- 缺点
- 测试时,无论是run,还是debug,服务启动很慢
- PR 流水线慢
- 执行结果依赖外部环境,单测不稳定
- 测试方法容易失败依赖服务部署在泳道,泳道机器被回收了,单测会失败
- 测试方法,起不到验证作用依赖db|rpc数据,有数据时,走到A逻辑分支,没数据走到B分支。其他同学改了分支A的逻辑,但测试时,因为依赖的外部环境没数据,进而走到了分支B。单测正常,实际上这个单测跳过了对改动点的测试,没起到作用。
1.2、单元测试
@RunWith(MockitoJUnitRunner.class)
public class MyServiceTest {@InjectMocksprivate MyServiceImpl myService;@Mockprivate MyRepository myRepository;@Testpublic void queryUserInfoTest() {// 1.mock db查询when(myRepository.func(any())).thenReturn(Lists.newArrayList());// 2.真实调用List<UserInfo> result = myService.queryUserInfo(323L);// 3.判断Assert.assertTrue(CollectionUtils.isEmpty(result));}
}
好处:
-
测试时秒级启动
-
大大缩短PR时间
-
不依赖外部环境,每次执行结果都唯一
其他同学改了分支A的逻辑,单测方法可验证到分支A,若改动点不符合测试断言,测试方法会失败,自然会引起关注,取排查失败原因。
缺点:
需要写不少单元测试代码(测试代码,基本没啥复杂逻辑,代码量虽多,但是写单测耗时不会太多)
3、补充:单测使用单元测试,可以使用集成测试做全流程debug
二、单测规范
2.1 命名
2.2单元测试
2.3 有效断言
1、常见断言方法:org.junit.Assert
Assert.assertTrue(CollectionUtils.isNotEmpty(skuIdList));
assertEqualsAssert.assertEquals(resp.getCode(), 0);
assertNotNullAssert.assertNotNull(resp);
assertThat(T actual, Matcher<? super T> matcher)其中Matcher见下文org.hamcrest.CoreMatchers
2、常用Matcher(均为org.hamcrest包下类)
- Matchers:方法更全
- CoreMatchers: 常用方法
@Test
public void testC() {//boolean:不为true则抛出对应错误信息boolean result = false;Assert.assertTrue("测试失败~",result);// 一般匹配符int s = new C().add(1, 1);// allOf:所有条件必须都成立,测试才通过assertThat(s, allOf(greaterThan(1), lessThan(3)));// anyOf:只要有一个条件成立,测试就通过assertThat(s, anyOf(greaterThan(1), lessThan(1)));assertThat(s, anyOf(is(1),is(2),is(3));// anything:无论什么条件,测试都通过assertThat(s, anything());// is:变量的值等于指定值时,测试通过assertThat(s, is(2));// not:和is相反,变量的值不等于指定值时,测试通过assertThat(s, not(1));// 数值匹配符double d = new C().div(10, 3);// closeTo:浮点型变量的值在3.0±0.5范围内,测试通过assertThat(d, closeTo(3.0, 0.5));// greaterThan:变量的值大于指定值时,测试通过assertThat(d, greaterThan(3.0));// lessThan:变量的值小于指定值时,测试通过assertThat(d, lessThan(3.5));// greaterThanOrEuqalTo:变量的值大于等于指定值时,测试通过assertThat(d, greaterThanOrEqualTo(3.3));// lessThanOrEqualTo:变量的值小于等于指定值时,测试通过assertThat(d, lessThanOrEqualTo(3.4));// 字符串匹配符String n = new C().getName("Magci");// containsString:字符串变量中包含指定字符串时,测试通过assertThat(n, containsString("ci"));// startsWith:字符串变量以指定字符串开头时,测试通过assertThat(n, startsWith("Ma"));// endsWith:字符串变量以指定字符串结尾时,测试通过assertThat(n, endsWith("i"));// euqalTo:字符串变量等于指定字符串时,测试通过assertThat(n, equalTo("Magci"));// equalToIgnoringCase:字符串变量在忽略大小写的情况下等于指定字符串时,测试通过assertThat(n, equalToIgnoringCase("magci"));// equalToIgnoringWhiteSpace:字符串变量在忽略头尾任意空格的情况下等于指定字符串时,测试通过assertThat(n, equalToIgnoringWhiteSpace(" Magci "));// 集合匹配符List<String> l = new C().getList("Magci");// hasItem:Iterable变量中含有指定元素时,测试通过assertThat(l, hasItem("Magci"));Map<String, String> m = new C().getMap("mgc", "Magci");// hasEntry:Map变量中含有指定键值对时,测试通过assertThat(m, hasEntry("mgc", "Magci"));// hasKey:Map变量中含有指定键时,测试通过assertThat(m, hasKey("mgc"));// hasValue:Map变量中含有指定值时,测试通过assertThat(m, hasValue("Magci"));
}
三、Mockito
3.1pom
mdp集成了mockito的2.15.0版本,无需添加依赖
<!-- https://mvnrepository.com/artifact/org.mockito/mockito-core -->
<dependency><groupId>org.mockito</groupId><artifactId>mockito-core</artifactId><version>4.3.1</version><scope>test</scope>
</dependency>
3.2常用注解
3.2.1 @RunWith
1、作用
是一个运行器告诉Junit使用什么进行测试
2、使用:【推荐】
@RunWith(MockitoJUnitRunner.class)
public class BaseTest{@Testpublic void test(){}
}
3、作用
用MockitoJUnitRunner进行测试@RunWith(PowerMockRunner.class) 则使用PowerMockRunner进行测试
4、对比:【不推荐使用】
@RunWith(SpringRunner.class)
@RunWith(SpringRunner.class)
@SpringBootTest(classes = ApplicationLoader.class)
public class BaseTest {@Testpublic void test(){}
}
作用:
@RunWith(SpringRunner.class)Test测试类要使用注入的类,比如通过@Resource注入的类,有此注解,这些类才能实例化到spring容器中,自动注入才能生效,否则就是NullPointerExecption
- 不使用@RunWith(SpringRunner.class) - NPE
@SpringBootTest(classes = ApplicationLoader.class)
public class BaseTest {@Resourceprivate MyService myService;//null@Testpublic void new_test(){myService.func(323L);//npe}}
- 使用@RunWith(SpringRunner.class) - 正常注入bean
@RunWith(SpringRunner.class)
@SpringBootTest(classes = ApplicationLoader.class)
public class BaseTest {@Resourceprivate MyService myService;//SellOutProcessServiceImpl@1952@Testpublic void test(){myService.func(323L);//正常调用}
}
@SpringBootTest
针对SpringBoot的测试类,结合@RunWith(SpringRunner.class)注解。加载ApplicationContext,启动spring容器。在测试开始的时候自动创建Spring的上下文
3.2.2 @InjectMocks
1、作用
@InjectMocks生效的类A下,可通过@Mock类中指定类|接口,将其变成Mock对象,注入到A中
2、使用
@InjectMocks 结合 @Mock
@RunWith(MockitoJUnitRunner.class)
public class MyServiceTest {@InjectMocksprivate MyServiceImpl myService;@Mockprivate MyRepository myRepository;@Testpublic void testQueryInfo_success() {when(myRepository.queryInfo(any())).thenReturn(Lists.newArrayList());resp = myService.func(323L);Assert.assertEquals(resp.getCode(), 0);}
}
3、备注
@InjectMocks只能作用在类上,接口不行
3.2.3 @Mock注解
1、作用
通过@InjectMocks注解标注,创建一个实例实例中,通过@Mock注解创建的的mock对象,将被注入到该实例中
2、使用:同上
3、备注
@Mock注解,作用等效mock()方法
3.2.4 @Spy
1、作用
@Spy声明的对象,对函数的调用均执行方法的真实调用
2、使用
场景1:使用MdpBeanCopy对象,走真实方法
- MyConvertor
@BeanCopy
public interface MyConvertor {Target copy(Source source);
}
- 业务
@Service
@Slf4j
public class MyService {@Resourceprivate MyConvertor myConvertor;public List<Long> func(Source req) {// 使用BeanCopy进行数据转换 ,单测时,想走真实的copy方法Target target = myConvertor.copy(req);//----}
}
- @Spy
@RunWith(MockitoJUnitRunner.class)
public class MyServiceTest {@InjectMocksprivate MyServiceImpl myService;@Spyprivate MyConvertor myConvertor = new MyConvertorImpl();//这里必须new接口的实现类@Testpublic void testQueryInfo_success() {//调用queryInfo时,myConvertor.copy()方法,走真实的方法逻辑myService.func();}
}
场景2:走真实线程池
- 使用myExecutor,并发查询
public List<DTO> concurrentQueryData(String date, Long id, List<Long> skuIdList) {return Lists.partition(skuIdList, 100).stream().distinct().map(partSkuList -> CompletableFuture.supplyAsync(() ->queryData(date, id, partSkuList),myExecutor)).collect(Collectors.toList()).stream().map(CompletableFuture::join).flatMap(List::stream).collect(Collectors.toList());
}
- @Spy
@RunWith(MockitoJUnitRunner.class)
public class MyServiceTest {@InjectMocksprivate MyService myService;@Spyprivate ExecutorService myExecutor = Executors.newFixedThreadPool(1);//必须创建一个@Testpublic void testConcurrentQueryData() {//调用concurrentQueryData时,会使用myExecutor线程池,核心线程数为1myService.concurrentQueryData("2021-03-23", 1L ,Lists.newArrayList(1L));}
}
3.2.5@Before
1、作用
在启动@Test之前,先执行此方法。
2、使用
可以通过Tracer将operator信息,set到组件中
- 业务校验operator代码
@Service
public class MyService {public MyResp editConfig(MyReq req) {MyResp resp = new MyResp();//01、权限校验String operator = getOperator();if (StringUtils.isBlank(operator)) {resp.setCode(4000);resp.setMessage("operator为空~");return resp;}req.setMisId(operator);// ---}
}
- @Before
@RunWith(MockitoJUnitRunner.class)
public class MyServiceTest {@InjectMocksprivate MyService myService;@Beforepublic void setUp() throws Exception {Tracer.clearContext("mall.sso.user");}@Testpublic void editConfig() {Tracer.putContext("mall.sso.user", "{\"login\": \"zhangsan\"}");myService.editConfig(req);}
}
3.2.6 @Ignore
1、作用:忽略此测试方法
2、使用
@Ignore
@Test
public void test() {//---
}
3、备注
作用在测试类上,则整个类中的测试方法均被忽略
3.3常用方法
3.3.1 参数相关-any ()
1、作用
使用any(),填充mock的方法参数
2、使用
- 业务
List<Long> func(String date, Integer type);
- mock方法中,使用any()
when(myService.func(any(), any())).thenReturn(Lists.newArrayList());
3、备注
- 待mock对象的方法声明中,有几个参数,则when()调方法时,填写几个any()
- 如果待mock方法的入参为基本类型,比如int,就必须使用anyInt()
public boolean setNx(String key, String value, int expireTime, String redisCategory) {
}
//方法第3个参数为int类型,则mock时必须用anyInt
when(redisGateway.setNxNew(any(), any(), anyInt(), any())).thenReturn(Boolean.TRUE);
- mock时,可根据不同入参,返回不同结果
when(myService.str2Int("m")).thenReturn(94);
when(myService.str2Int("w")).thenReturn(93);Integer m = myService.str2Int("m");
Integer w = myService.str2Int("w");
Integer other = myService.str2Int("x");Assert.assertThat(m, equalTo(93));
Assert.assertThat(w, equalTo(93));
Assert.assertThat(other, equalTo(0));
3.3.2 Mock使用-mock()
1、作用
等效@Mock
2、使用
- 业务
@Service
public class MyService{@Resourceprivate MyRepository myRepository;public List<Long> func(Long id) {return myRepository.queryInfo(id);}
}
- UT
@RunWith(MockitoJUnitRunner.class)
public class MyServiceTest {@InjectMocksprivate MyService myService;private MyRepository myRepository = mock(MyRepository.class);@Testpublic void test() {when(myRepository.queryInfo(any())).thenReturn(Lists.newArrayList());List<DTO> result = myService.func(1L);}
}
3、备注
- 等效1
@Mock
private MyRepository myRepository;
- 等效2
private MyRepository myRepository = mock(MyRepository.class, "myService");
将myRepository,mock注入myService
3.3.3 mockStatic()
1、作用
mock 静态方法
2、使用
MockedStatic<MyLion> myLion = mockStatic(MyLion.class);
3、备注
- 可用于mock静态方法。MyLion本身的方法都是静态方法,所以,也可用于Mock MyLion
- StringUtil等静态方法
- 后面有mock-static
3.3.4 when()
1、作用
打桩
2、使用
when(myRepository.queryInfo(any(), any(), any())).thenReturn(buildData());
3、备注
1)是否通过thenReturn指定返回值
-
正常when(myService.queryName(any())).thenReturn(“hello”);
返回"hello"
-
不指定thenXxx()时,则方法返回值为其类型的默认。when(myService.queryName(any()))
.thenReturn(“hello”);返回String的默认类型:null
-
同理when、thenReturn都不写,方法返回值为其类型的默认。
when(pcGateway.queryName(any())).thenReturn(“hello”);返回String的默认类型:null
2)selectByExample方法,默认返回空集合
//不指定thenReturn(结果),返回的就是selectByExample返回其List<T>类型的默认值:空集合
when(myMapper.selectByExample(any()));
List<XxxDO> result = myRepository.func(model);//空集合
-
方法声明返回Obj时,则返回默认null
业务
@Service public class MyService {//queryUser方法,返回Objectpublic MyUser queryUser() {return new MyUser();} }
指定thenReturn,正常返回
UT
when(myService.queryUser(any())).thenReturn(new MyUser());
- 不指定thenReturn,返回null
when(myService.queryUser(any()));
- 方法声明返回Integer时,则返回默认0
- 方法声明返回String时,则返回默认null
3.3.5 reset()
1、作用
重置mock的对象
2、使用
- 业务
public class MyRepository {@Resourceprivate MyMapper myMapper;public List<XxxDO> func() {// ---return myMapper.selectByExample(xxxExample);}
}
- 不使用reset
@RunWith(MockitoJUnitRunner.class)
public class MyRepositoryTest {@InjectMocksprivate MyRepository myRepository;@Mockprivate MyMapper myMapper;@Testpublic void test() {//场景1when(myRepository.selectByExample(any())).thenReturn(Lists.newArrayList(null));List<SellOutProcessPlanDO> result1 = myRepository.func();//场景2: 获取的selectByExample结果 和 场景1一样。同一个mock对象myRepositoryList<XxxDO> result = myRepository.func();}
}
- 使用reset
@RunWith(MockitoJUnitRunner.class)
public class MyRepositoryTest {@InjectMocksprivate MyRepository myRepository;@Mockprivate MyMapper myMapper;@Testpublic void test() {//场景1,selectByExample的mock结果为含有XxxDO元素的集合when(myMapper.selectByExample(any())).thenReturn(Lists.newArrayList(XxxDO));List<XxxDO> result1 = myRepository.func();//场景2,因为reset了。selectByExample的mock结果不再是含有XxxDO元素的集合,而是空集合reset(myMapper)List<XxxDO> result = myRepository.func();}
}
3.3.6 mock方法返回值-doNothing()
1、作用
mock无返回值方法即void方法
2、使用
- 业务
@Service
public class MyService{public void func(Long id) {// }
}
- UT
doNothing().when(myService).func(any());
3.3.7 thenReturn()
1、作用
mock有返回值方法的结果
2、使用
when(myMapper.countByExample(any())).thenReturn(1);
3备注
每一次调用mock方法,返回不同结果【用于while、for循环调用mock】
//为list.size()方法赋值:第一次调用为1,第二次调用为2、、
when(list.size()).thenReturn(1).thenReturn(2).thenReturn(3).thenReturn(4);assertEquals(1,list.size());//第一次调用,值为1
assertEquals(2,list.size());
assertEquals(3,list.size());
assertEquals(4,list.size());//第四次为4
assertEquals(4,list.size());//第五-N次,同样为4或
指定mock方法的入参,对应mock结果
when(myRepository.countSku(123456789L)).thenReturn(1L);
when(myRepository.countSku(987654321L)).thenReturn(2L);
3.3.8 doReturn()
1、作用
作用同理thenReturn(),只是使用方式不同
2、使用
- doReturn
doReturn(1).when(myMapper).countByExample(any());
- thenReturn
when(myMapper.countByExample(any())).thenReturn(1);
3、备注
doReturn 和 thenReturn大部分情况下都是可以相互替换的。除非特殊mock
3.3.9 doThrow()和thenThrow()
1、作用
mock方法,让其返回异常
2、使用
- void 方法
业务
@Service
public class MyService {@Resourceprivate MyRepository myRepository;public void query() {myRepository.queryInfo(1L);}
}@Repository
public class MyRepository {public void queryInfo(Long skuId) {}
}
UT
@InjectMocks
private MyService myService;@Mock
private MyRepository myRepository;@Test
public void test(){doThrow(IllegalArgumentException.class).when(myRepository).queryInfo(any());try {myService.func();} catch (Exception e) {Assert.assertThat(e,instanceOf(IllegalArgumentException.class));}
}
- 有返回值方法
业务
@Service
public class MyService {@Resourceprivate MyRepository myRepository;public Integer func() {return myRepository.queryInfo(1L);}
}@Repository
public class MyRepository {public void queryInfo(Long skuId) {}
}
UT
@InjectMocks
private MyService myService;@Mock
private MyRepository myRepository;@Test
public void test(){when(myRepository.queryInfo(any())).thenThrow(IllegalArgumentException.class);try {myService.func();} catch (Exception e) {Assert.assertThat(e,instanceOf(IllegalArgumentException.class));}
}
3、备注
异常类型,自己指定
3.3.10 thenCallRealMethod()
1、作用
- 走方法真实的代码逻辑
- when()调用方法的时候,不是thenReturn()方法结果,而是走方法的实际代码逻辑
2、使用
- 业务
@Service
public class MyService {@Resourceprivate MyRepository myRepository;public Integer func() {return myRepository.queryInfo(1);}
}@Repository
public class MyRepository {public Integer queryInfo(Integer sum) {return sum + 1;}
}
- UT
@InjectMocks
private MyService myService;@Mock
private MyRepository myRepository;@Test
public void test(){//这里在调用queryInfo方法时,会走实际的代码逻辑,方法返回值为:sum + 1即 1+1=2when(myRepository.queryInfo(any())).thenCallRealMethod();Integer res = myService.func();Assert.assertThat(res, is(2));
}
3、备注
- 作用同理@Spy注解
业务
@Service
public class MyService {@Resourceprivate MyRepository myRepository;public String func(Integer id) {return myRepository.queryInfo(id);}
}@Repository
public class MyRepository {public String queryInfo(Integer id){if (id < 0) {throw new IllegalArgumentException("参数不合法");}return "" + id;}
}
UT
@Spy
private MyRepository myRepository;@Test
public void t() {try {//当func方法调用queryInfo时,String result = myService.func(-1);//会走queryInfo的真实方法,方法入参为-1,小于0,会抛异常} catch (Exception e) {Assert.assertThat(e, instanceOf(IllegalArgumentException.class));}
}
3.3.11 verify()
1、作用
验证,验证是否执行了mock方法
2、使用
@RunWith(MockitoJUnitRunner.class)
public class MyServiceTest {@InjectMocksprivate MyService myService;@Mockprivate MyRepository myRepository;@Testpublic void t() {when(myRepository.queryInfo(any())).thenReturn("hello");myService.func(-1);//验证myRepository的queryInfo方法,是否被mock执行了1次verify(myRepository, times(1)).queryInfo(any());}
}
3、备注
必须调用了mock方法,触发了其执行,才能使用verift判断。否则会报错:Wanted but not invoked:
when(myRepository.queryInfo(any())).thenReturn("hello");
//myService.func(-1); 将此方法注释掉后,导致:
//不会调用myRepository的queryInfo方法,会报错Wanted but not invoked
verify(myRepository, times(1)).queryInfo(any());
四、Spock
4.1环境配置
- 插件:IDEA安装插件:spock framework Enhancements
- pom
<dependency><groupId>org.spockframework</groupId><artifactId>spock-core</artifactId><version>1.3-groovy-2.4</version><scope>test</scope>
</dependency><dependency><groupId>org.spockframework</groupId><artifactId>spock-spring</artifactId><version>1.3-groovy-2.4</version><scope>test</scope>
</dependency>
4.2创建spock测试类
在指定类下,shift + command + T, 选择Spock创建,选择浅绿色的Spock图标,创建
4.3 类
1、使用
写法1【推荐】
class MyServiceSpec extends Specification {MyService myService = new MyService()MyRepository myRepository = Mock()def setup() {myService.myRepository = myRepository}
}
写法2:不用setup
- 业务
@Service
public class MyService {@Resourceprivate MyRepository myRepository;@Resourceprivate MyGateway myGateway;public void func1(String date, Long skuId) {myRepository.querySku(date, skuId);myGateway.queryRpc(1);}
}
- UT
class MyServiceSpec extends Specification {MyRepository myRepository = Mock()MyGateway myGateway = Mock()MyService myService = new MyService(myRepository:myRepository, myGateway: myGateway)def "test"() {given:myRepository.querySku(_, _) >> {1L}myGateway.queryRpc(_) >> {1L}when:myService.func1("03-26", 123L)then:noExceptionThrown()}
}
2、作用
1)MyService myService = new MyService()
类似Mockito中
@InjectMock
private MyService myService;**//标注测试待测试类**2)MyRepository myRepository = Mock()
类似Mockito中
@Mock
private MyRepository myRepository;**//标注需要mock的对象**3)def setup() { myService.myRepository = myRepository
}类似 Mockito中的@Before,都是做测试功能的前置设置。这里是将依赖的mock对象myRepository的引用,set到测试类myService中。没有这步,myRepository会报npe。
3、备注
1)setup 方法 和 given的区别
- myService.myRepository = myRepository,放在setup方法中mock的对象类似全局变量,不同测试方法都可以使用
class MyServiceSpec extends Specification {MyService myService = new MyService()MyRepository myRepository = Mock()def setup() {myService.myRepository = myRepository}def "test1"() {given:// ---myRepository.countSku(_) >> 1Lwhen:// --then:"结果验证"// --}def "test2"() {given:// ---myRepository.selectSKuDOs(_) >> 2Lwhen:// --then:"结果验证"// --}
}
- 放在某个测试方法的given标签后,类似局部变量,只能此测试方法单独使用
4.4 方法 -标签
4.4.1 given
1、作用
输入条件提前定义要准备的方法参数mock模拟依赖方法的返回值
2、使用
- 业务
@Service
public class MyService {@Resourceprivate MyRepository myRepository;public Long func(MyUser myUser) {return myRepository.queryRepository(myUser);}
}@Repository
public class MyRepository {public Long queryRepository(MyUser myUser) {// ---}
}
- UT
class MyServiceSpec extends Specification {MyService myService = new MyService()MyRepository myRepository = Mock()def setup() {myService.myRepository = myRepository}def "test"() {given:"前提条件"MyUser user = new MyUser()user.setId(323L)and:myRepository.queryRepository(_) >> {777L}when:"执行"Long result = myService.func(user)then:"结果验证"Assert.assertEquals(result, 777L)}
}
3、备注
1)given必须作为第一个标签出现
2)添加标签描述(非强制)
如when:“获取信息”,说明这块代码的作用。
3)and:衔接上个标签,补充的作用。
given 可以结合 and一起使用,结构更清晰given: “参数定义”
and: “mock 需要调用的方法返回”
def "test "() {given:"参数定义"def user = new MyUser(rdcId: 323L)and:"mock方法"myRepository.queryRepository1(_) >> {777L}myRepository.queryRepository2(_,_) >> {"zhangsan"}pcGateway.queryRpc(_,_,_) >> {Lists.newArrayList()}when:"执行"Long result = myService.func(user)then:"结果验证"Assert.assertEquals(result, 777L)
}
4)参数命名,以下都可以
-
def user = new MyUser()
-
MyUser user = new MyUser()
-
def MyUser user = new MyUser()
-
创建对象,建议使用第一种。因为类名较长,创建很多对象时,代码看起来多、长
when中触发执行方法返回结果,建议使用第二种,这样在then中对方法结果判断时,能够一眼知道返回值的类型
5)属性赋值
- MyUser
@Data
public class MyUser{private String name;private Long age;private List<Integer> other;
}
- UT属性赋值
def user = new MyUser(name:"zhangsan", age:2L,other:[1,2])
或
def user = new MyUser()
user.setName("zhangsan")
user.setAge(2L)
4.4.2 when
1、作用
触发动作,指定做什么,是真实调用
2、使用
class MyServiceSpec extends Specification {MyService myService = new MyService()MyRepository myRepository = Mock()def setup() {myService.myRepository = myRepository}def "test "() {given:"前提条件"def user = new MyUser(id: 323L)and:"mock"myRepository.queryRepository(_) >> {777L}when:"执行"Long result = myService.func(user)then:"结果验证"Assert.assertEquals(result, 777L)}
}
4.4.3 then
1、作用
验证结果输出
2、使用
class MyServiceSpec extends Specification {MyService myService = new MyService()MyRepository myRepository = Mock()def setup() {myService.myRepository = myRepository}def "test "() {given:"前提条件"def user = new MyUser(id:323L)and:"mock"myRepository.queryRepository(_) >> {777L}when:"执行"Long result = myService.func(user)then:"结果验证"noExceptionThrown()Assert.assertEquals(result, 3L)with(result) {Assert.assertEquals(result, 777L)}}
}
3、备注
1)一个测试方法,可以包含多个 when-then
2)then: “内容可以如下”
- 判断是否有异常抛出
无异常:noExceptionThrown()
有异常:thrown(IllegalArgumentException.class)
抛出具体异常 和 异常信息
@Service
public class MyService {public Integer func(Integer sum) {return sum / 0;}
}//UT
class MyServiceSpec extends Specification {MyService myService = new MyService()def setup() {}def "test "() {given:Integer sum = 323when:Integer result = myService.func(sum)then:def ex = thrown(ArithmeticException)ex.message == "/ by zero"}
}
3)断言
Assert.assertEquals(result, 323L)
result == 323
4)with
验证返回结果res对象的多个属性,是否符合预期
with(resp) {resp.getId == 323L
}
5)判断调用次数(类似Mockito的verify)
then:
1 * myRepository.queryRepository(_)// 验证该方法是否被调用了
6)then和expect的区别调用和预期判断并不复杂,那么可以用expect将两者合在一起
调用和预期判断并不复杂,那么可以用expect将两者合在一起:when + then => expect
when:
def x = Math.max(1, 2)
then:
x == 2
//等价
expect:
Math.max(1, 2) == 2
4.4.4 where
1、作用
用于编写数据驱动的用例
2、使用
- 业务
@Service
public class MyService {public String func(Long id) {if (Objects.equals(id, 3L)) {return "小明";} else {return "小红";}}
}
- UT
class MyServiceSpec extends Specification {MyService myService = new MyService()def setup() {}def "test qeury id name"() {given:String s = ""when:String result = myService.func(id)then:result == namewhere:id | 3L3L | "小明"7L | "小红"}
}
3、备注
- 在单测方法的最后
- 数据表格至少有两列组成,如果是单例的话,可按照如下形式编写
where:
poiId |_
323 | _
777 | _
null | _
- 真实方法的执行参数,作为where内容时,等价多个测试方法,测试了不同场景
class MyConsumerSpec extends Specification {MyConsumer myConsumer = new MyConsumer()def setup() {}@Unrolldef "test validateParams,#caseDesc"() {given:Msg msg = ownDefineMsgwhen:myConsumer.validateParams(msg)then:def error = thrown(IllegalArgumentException)error.getMessage() == errMsgwhere:caseDesc | ownDefineMsg | errMsg"消息不能为null" | null | "消息不能为null""ID不能为null或0" | new Msg(id:0L) | "ID不能为null或0"}
}//等价两个测试方法
// 方法1:myConsumer.validateParams(null)
//方法2:myConsumer.validateParams(new Msg(id:0L))
- where中字段,可使用${}表达式
def "test qeury #desc"() {given:String str = ""when:String result = myService.func2(id)then:result == namewhere:desc |id | name"小明" | 3L | "${desc},id${id}""小红" | 7L | "${desc},id${id}"
}
4.5 常用注解(Ignore、Unroll)
4.5.1 Ignore
1、作用
此case不执行
2、使用
@Ignore
def "test"() {// ---
}
3、备注
import spock.lang.Ignore是Spcok的
4.5.2 @Unroll
1、作用
自动把测试方法代码拆分成 n个独立的单测测试展示,运行结果更清晰。
2、使用
class MyConsumerSpec extends Specification {MyConsumer myConsumer = new MyConsumer()def setup() {}@Unrolldef "test validateParams,#caseDesc"() {given:Msg msg = ownDefineMsgwhen:myConsumer.validateParams(msg)then:def error = thrown(IllegalArgumentException)error.getMessage() == errMsgwhere:caseDesc | ownDefineMsg | errMsg"消息不能为null" | null | "消息不能为null""ID不能为null或0" | new Msg(id:0L) | "ID不能为null或0"}
}
3、备注
- 一般结合where一同使用
- 不加这个注解,单测失败的时候,不知道具体是where中哪个条件测试失败了
4.5.3 mock 静态、私有方法、final相关注解
具体使用,参考mock static
4.6 字符含义(_ 、 * 、 # 、 >>)
4.6.1 _下划线
given:
_ * myRepository.queryRepository(_) >> {1L}
1、场景
前者_ 作为mock方法的调用次数
- 作用
表示此mock方法queryRepository匹配次数无限制
- 使用
后者_ 作为方法参数
- 同Mockito.any(),表示任何类型的参数
4.6.2
1、作用
一般和 @Unroll注解 + where: 一起使用
where中真实调用方法参数,有多种值,对应多个场景的测试,使用#描述每个测试场景
2、使用
class MyConsumerSpec extends Specification {MyConsumer myConsumer = new MyConsumer()def setup() {}@Unrolldef "test validateParams,#caseDesc"() {given:Msg msg = ownDefineMsgwhen:myConsumer.validateParams(msg)then:def error = thrown(IllegalArgumentException)error.getMessage() == errMsgwhere:caseDesc | ownDefineMsg | errMsg"消息不能为null" | null | "消息不能为null""ID不能为null或0" | new Msg(id:0L) | "ID不能为null或0"}
}
3、备注
4.6.3 *
1、作用
返回值类型通配符,表示任意类型都可以匹配
2、使用
_ * myRepository.queryRepository(_) >> {1L}
或
myRepository.queryRepository(_) >> {1L}
4.6.4 >>
1、作用
作用同Mockito.thenReturn()
2、使用
1)返回值类型
- 返回值为对象
>> new XxxDTO(total: 10, skuIdList: new ArrayList<>())
- 返回值为Map
>> [1L: true]
>> new XxxDTO(skuIdAndDOMap: [1L: new XxxDO()], sellDate: LocalDate.now())
- List
>> Lists.newArrayList(new XxxDO(skuId: 1L, skuName: "苹果", new XxxDO(skuId: 2L, skuName: "香蕉")
- void
>> {}
- 异常
>> { throw new XxxException("mock 异常") }
- Set
>> [1, 2]
2)根据参数不同,返回不同值
- UT
myRepository.countSku(_) >> {Long skuId -> Objects.equals(skuId, 123456789L) ? 1L : 2L}
- 业务
@Service
public class MyService {@Resourceprivate MyRepository myRepository;public Long func(String date, Long id) {return myRepository.query(date, id);}
}@Repository
public class MyRepository {public Long query(String date, Long id) {// 业务}
}
- UT
class MyServiceSpec extends Specification {MyService myService = new MyService()MyRepository myRepository = Mock()def setup() {myService.myRepository = myRepository}def "test "() {given:def date = "2023-03-26"and:"这里根据query()方法2个入参中,第一个入参date的值,匹配得到方法的结果"myRepository.query(_, _) >> {arg1, arg2 -> respDecideByArg(arg1)}when:Long result = myService.func1(date, 123456789L)then:result == 1}// 如果这里根据query方法的第一个入参date为“2023-03-26”,则query返回1Long respDecideByArg(String date) {if (Objects.equals(date, "2023-03-26")) {return 1L} else if (Objects.equals(date, "2023-03-25")) {return 2L} else {return 3L}}
}
五、实战
5.1 mock-网络资源(DB、Rpc、线程池、Redis等)
5.1.1MyLion
5.1.1.1 使用Mockito
MyLion底层的方法都是static方法,需要引入mock static相关的依赖
- pom
// mock静态方法依赖此pom
<dependency><groupId>org.mockito</groupId><artifactId>mockito-inline</artifactId><version>4.11.0</version><scope>test</scope>
</dependency>//不能使用MDP默认的2.15版本,太低不支持mockStatic
<dependency><groupId>org.mockito</groupId><artifactId>mockito-core</artifactId><version>4.3.1</version><scope>test</scope>
</dependency>// bytebuddy基于ASM框架的字节码增强类库和工具
//若无此依赖,会报错:
//Could not initialize plugin: interface org.mockito.plugins.MockMaker (alternate: null)
<dependency><groupId>net.bytebuddy</groupId><artifactId>byte-buddy</artifactId><version>1.9.13</version><scope>test</scope>
</dependency>
- 业务
@Service
public class MyService{//场景1:需要查询MyLion.getLongpublic Long func1() {return LionUtil.checkTime();}//场景2:需要查询MyLion.getListpublic Boolean func2(Long id) {return LionUtil.getIdList(id);}
}
- 业务Lion
@UtilityClass
public class LionUtil {public static long checkTime() {return MyLion.getLong();}public boolean getIdList(Long id) {List<Long> grayList = MyLion.getList);if (grayList.contains(-1L)) {return true;}return grayList.contains(id);}
}
- UT
@RunWith(MockitoJUnitRunner.class)
public class MyServiceTest {@InjectMocksprivate MyService myService;@Testpublic void tsetMockLion() {MockedStatic<MyLion> myLion = mockStatic(MyLion.class);//场景1:mock MyLion.getLongmyLion.when(() -> MyLion.getLong(any(),any(),any())).thenReturn(10L);Long res = myService.func1();Assert.assertThat(res, is(10L));//场景2:mock MyLion.getListmyLion.when(() -> MyLion.getList(any(),any(),any(),any())).thenReturn(Lists.newArrayList(323L));Boolean res1 = myService.func2(323L);Assert.assertTrue(res1);}
}
5.1.1.2 使用Spock
- pom
<!--spock 整合powermock mock 静态方法-->
<dependency><groupId>org.powermock</groupId><artifactId>powermock-module-junit4</artifactId><version>2.0.9</version><scope>test</scope>
</dependency><dependency><groupId>org.powermock</groupId><artifactId>powermock-api-mockito2</artifactId><version>2.0.9</version><scope>test</scope>
</dependency>
- 业务-LionUtil
@Service
public class MyService{// 通过LionUtil获取MyLion配置public Integer func1() {return LionUtil.getLimit();}
}
- 业务-LionUtil调用Lion.getXxx方法
@UtilityClass
public class LionUtil {public static int getLimit() {return MyLion.getInt();}
}
- UT
@RunWith(PowerMockRunner.class)
// 所有需要测试的类列在此处,适用于模拟final类或有static,final, private, native方法的类
@PrepareForTest([LionUtil.class])
// static{}静态代码块或static变量的初始化, 在class被容器load的时候就要被执行
//如果执行错误就会导致junit单元测试错误。故使用下方注解阻止静态初始化
@SuppressStaticInitializationFor(["com.sankuai.groceryscp.returnplan.common.utils.LionUtil"])
//指定Sputnik类去代理运行power mock,而Sputnik类是Spock类的父类。
//故可以在Spock里使用powermock去模拟静态static方法、final方法、私有private方法等
@PowerMockRunnerDelegate(Sputnik.class)
//为了解决使用powermock后,提示classloader错误
@PowerMockIgnore("javax.management.*")
class MyServiceSpec extends Specification {MyService myService = new MyService()def setup() {PowerMockito.mockStatic(LionUtil.class)}def "test lionUtil"() {given: // 这里mock了LionUtil的static方法getLimitPowerMockito.when(LionUtil.getLimit()).thenReturn(5)when:myService.func1()then:noExceptionThrown()}
}
5.1.2 Mockito - DB、RPC
- 业务 网关查询 + DB查询
@Service
public class MyService{@Resourceprivate RedisGateway redisGateway;@Resourceprivate MyRepository myRepository;public Integer func(Long id) {boolean lockResult = redisGateway.setNxNew("key", "val", 1 * 60 * 1000, "xxx");if (lockResult) {return myRepository.queryRepository(id).size();}return -1;}
}
- UT
@RunWith(MockitoJUnitRunner.class)
public class MyServiceTest {@InjectMocksprivate MyService myService;@Mockprivate RedisGateway redisGateway;@Mockprivate MyRepository myRepository;@Testpublic void test() {// mock: rpcwhen(redisGateway.setNxNew(any(), any(), anyInt(), any())).thenReturn(Boolean.TRUE);// mock: dbwhen(myRepository.queryRepository(any())).thenReturn(Lists.newArrayList());Integer result = sellOutProcessService.func(3L);Assert.assertThat(result, is(0));}
}
5.1.3 Mockito MyMcc
- 业务
@Service
public class MyService{@Resourceprivate MyMccConfig myMccConfig;public String func() {return myMccConfig.time;}
}
- MyMccConfig
@Configuration
public class MyMccConfig {@MYConfig(value = "time:23:00")public volatile String time;
}
- UT
@RunWith(MockitoJUnitRunner.class)
public class MyServiceTest {@InjectMocksprivate MyService myService;@Mockprivate MyMccConfig myMccConfig;@Testpublic void test() {mccConfig.time = "12";String result = myService.func();Assert.assertEquals("12", result);}
}
5.1.4 Mockito - Redis
- 业务
@Component
public class RedisGateway {@Resource(name = "redisStoreClient")private RedisStoreClient redisClient;public boolean setNx(String key, String value, int expireTime, String redisCategory) {try {return redisClient.setnx("", value, expireTime);} catch (Exception e) {log.error("RedisGateway setNx occurs exception", e);throw new GatewayException(GATEWAY_EXCEPTION_CODE, e.getMessage());}}
}
- UT
@RunWith(MockitoJUnitRunner.class)
public class SquirrelGatewayTest {@InjectMocksprivate RedisGateway redisGateway;@Mockprivate RedisStoreClient redisStoreClient;@Testpublic void test() {when(redisStoreClient.setnx(any(), any(), anyInt())).thenReturn(Boolean.TRUE);boolean b = redisGateway.setNx("", "", 1, "");Assert.assertTrue(b);}
}
5.1.5 Mockito - 线程池
参考: Mockito 常用注解@Spy
5.1.6 Spock - DB、Rpc、MyMcc、Redis和线程池
- 业务
@Service
public class MyServiceImpl implements MyService {@Resourceprivate MyRepository myRepository;@Resourceprivate MyConvertor myConvertor;@Resourceprivate MyGateway myGateway;@Resourceprivate ExecutorService myExecutor;@Resourceprivate RedisGateway redisGateway;@Resourceprivate MyMccConfig myMccConfig;@Overridepublic void func() {// 01.myMccString key = myMccConfig.time;// 02.redisboolean lockResult = redisGateway.setNx(key, "", 1, "");if (lockResult) {// 03.Rpc[线程池并发调用]List<DTO> rpcDTOList = Lists.partition(Lists.newArrayList(1L, 2L), 100).stream().distinct().map(partSkuList -> CompletableFuture.supplyAsync(() -> myGateway.queryData("2023-03-23", 323L, partSkuList),myExecutor)).collect(Collectors.toList()).stream().map(CompletableFuture::join).flatMap(List::stream).collect(Collectors.toList());// 04.DBList<DO> result = myRepository.queryDOList(model);}}
}
- UT
class MyServiceImplSpec extends Specification {MyService myService = new MyServiceImpl()rtedisGateway redisGateway = Mock()MyMccConfig myMccConfig =Mock()MyGateway myGateway = Mock()ExecutorService myExecutor = Executors.newFixedThreadPool(1)MyConvertor myConvertor = new MyConvertorImpl()//或MyConvertor myConvertor = Spy(MyConvertorImpl)MyRepository myRepository = Mock()def setup() {myService.myRepository = myRepositorymyService.myConvertor = myConvertormyService.myGateway = myGatewaymyService.myExecutor = myExecutormyService.redisGateway = redisGatewaymyService.myMccConfig = myMccConfig}def "test"() {given:myMccConfig.time = "19"and: "mock redis"redisGateway.setNx(_, _, _, _) >> {Boolean.TRUE}and: "mock rpc"myGateway.queryData(_, _, _) >> { Lists.newArrayList()}and: "mock DB"myRepository.queryDOList(_) >> {Lists.newArrayList()}when:myService.func()then:noExceptionThrown()}
5.2 mock-static静态方法
5.2.1 Mockito
- pom
<dependency><groupId>org.mockito</groupId><artifactId>mockito-inline</artifactId><version>4.11.0</version><scope>test</scope>
</dependency>//不引入此pom会报错:
org.mockito.exceptions.base.MockitoException:
The used MockMaker SubclassByteBuddyMockMaker does not support the creation of static mocks
Mockito's inline mock maker supports static mocks based on the Instrumentation API.<dependency><groupId>org.mockito</groupId><artifactId>mockito-core</artifactId><version>4.3.1</version><scope>test</scope>
</dependency>
//mdp自带集成的mockito-core版本是2.15,这个版本过低,
//无法使用用于测试静态方法的mockStatic方法。
- 业务
@Service
public class MyService {public boolean isBlank(String str) {return StringUtils.isBlank(str);}
}
- UT
@RunWith(MockitoJUnitRunner.class)
public class MyServiceTest {@InjectMocksprivare MyService myService;@Testpublic void t1() {boolean b = myService.isBlank("");System.out.println(b);//true}@Testpublic void t2() {MockedStatic<StringUtils> mockStatic = mockStatic(StringUtils.class);when(StringUtils.isBlank(any())).thenReturn(false);boolean b = myService.isBlank("");System.out.println(b);//false}
}
5.2.2 Spock
Spock对静态static方法、私有private方法、final类的Mock,需要基于PowerMock来完成
- pom
<!--spock 整合powermock mock 静态方法、私有、final方法-->
<dependency><groupId>org.powermock</groupId><artifactId>powermock-module-junit4</artifactId><version>2.0.9</version><scope>test</scope>
</dependency><dependency><groupId>org.powermock</groupId><artifactId>powermock-api-mockito2</artifactId><version>2.0.9</version><scope>test</scope>
</dependency>
- 业务-使用静态方法
@Service
public class MyService{// 场景1:通过LionUtil获取MyLion配置public Integer func1() {return LionUtil.getLimit();}
}
- 业务-LionUtil的static方法
@UtilityClass
public class LionUtil {public static int getLimit() {return MyLion.getInt();
}
- UT
@RunWith(PowerMockRunner.class)
// 所有需要测试的类列在此处,适用于模拟final类或有static,final, private, native方法的类
@PrepareForTest([LionUtil.class, GrayUtils.class])
// static{}静态代码块或static变量的初始化, 在class被容器load的时候就要被执行
//如果执行错误就会导致junit单元测试错误。故使用下方注解阻止静态初始化
@SuppressStaticInitializationFor(["com.sankuai.groceryscp.returnplan.common.utils.LionUtil","com.sankuai.groceryscp.returnplan.common.utils.GrayUtils"])
//指定Sputnik类去代理运行power mock,而Sputnik类是Spock类的父类。
//故可以在Spock里使用powermock去模拟静态static方法、final方法、私有private方法等
@PowerMockRunnerDelegate(Sputnik.class)
//为了解决使用powermock后,提示classloader错误
@PowerMockIgnore("javax.management.*")
class MyServiceSpec extends Specification {MyService myService = new MyService()def setup() {PowerMockito.mockStatic(LionUtil.class,GrayUtils.class)}def "test lionUtil"() {given:// 这里mock了LionUtil的static方法getLimitPowerMockito.when(LionUtil.getLimit()).thenReturn(5)when:myService.func1()then:noExceptionThrown()}
}
5.3 mock-private私有方法
5.3.1 Mockito
- pom
<!--spock 整合powermock mock 静态方法、私有方法--><dependency><groupId>org.powermock</groupId><artifactId>powermock-module-junit4</artifactId><version>2.0.9</version><scope>test</scope></dependency><dependency><groupId>org.powermock</groupId><artifactId>powermock-api-mockito2</artifactId><version>2.0.9</version><scope>test</scope>
</dependency>
- 业务
@Service
public class MyService {public Long func(String date) {Long count = 0L;return queryPrivate(date, count);}private Long queryPrivate(String date, Long count) {if (date.compareTo("2023-01-01") <=0 ){throw new IllegalArgumentException("日期早于2023-01-01");}return count + 1;}
}
- UT
@RunWith(PowerMockRunner.class)
@PrepareForTest(MyService.class)
public class MyServiceTest {MyService myService = new MyService();@Testpublic void test() throws Exception {MyService spy = PowerMockito.spy(myService);//不是Mockito的spy()//这种格式不可以!!!:PowerMockito.when(userService, "queryPrivate", any()).thenReturn(777L);// 必须这种格式PowerMockito.doReturn(777L).when(spy, "queryPrivate", any(), any());Long id = spy.func("2000-01-01");Assert.assertEquals(777L, (long) id);}
}
5.3.2 Spock
- pom
<dependency><groupId>org.spockframework</groupId><artifactId>spock-core</artifactId><version>1.3-groovy-2.4</version><scope>test</scope>
</dependency><dependency><groupId>org.spockframework</groupId><artifactId>spock-spring</artifactId><version>1.3-groovy-2.4</version><scope>test</scope>
</dependency><!--spock 整合powermock mock 静态方法、私有方法--><dependency><groupId>org.powermock</groupId><artifactId>powermock-module-junit4</artifactId><version>2.0.9</version><scope>test</scope></dependency><dependency><groupId>org.powermock</groupId><artifactId>powermock-api-mockito2</artifactId><version>2.0.9</version><scope>test</scope>
</dependency>
- 业务
@Service
public class MyService {public Long func(String date) {return queryPrivate(date);}private Long queryPrivate(String date) {if (date.compareTo("2023-01-01") <=0 ){throw new IllegalArgumentException("日期早于2023-01-01");}return 1L;}
}
- UT
@RunWith(PowerMockRunner.class)
@PowerMockRunnerDelegate(Sputnik.class)
@PrepareForTest([MyService.class])
class MyServiceSpec extends Specification {MyService myService = new MyService()def setup(){}def "test"() {given:"mock 私有"def spy = PowerMockito.spy(myService)// 这里queryPrivate的参数必须传 "2000-01-01" 和 0L,原因见5.4中mock-private私有方法 + mock 网络资源的UTPowerMockito.doReturn(777L).when(spy, "queryPrivate", "2000-01-01", 0L)when:Long id = spy.func("2000-01-01")println(id)then:noExceptionThrown()id == 777L}
}
5.4 mock-private私有方法 + mock 网络资源
5.4.1 Mockito
- pom
<!--spock 整合powermock mock 静态方法、私有方法--><dependency><groupId>org.powermock</groupId><artifactId>powermock-module-junit4</artifactId><version>2.0.9</version><scope>test</scope></dependency><dependency><groupId>org.powermock</groupId><artifactId>powermock-api-mockito2</artifactId><version>2.0.9</version><scope>test</scope>
</dependency>
- 业务
@Service
public class MyService {@Resourceprivate MyRepository myRepository;public Long func(String date) {Long count = myRepository.countSku(date);//mock myRepository方法return queryPrivate(date, count);// mock 私有方法}private Long queryPrivate(String date, Long count) {if (date.compareTo("2023-01-01") <=0 ){throw new IllegalArgumentException("日期早于2023-01-01");}return count + 1;}
}@Repository
public class MyRepository {public Long countSku(String date) {//业务}
}
- UT
@RunWith(PowerMockRunner.class)
@PrepareForTest(MyService.class)
public class MyServiceTest {@InjectMocksMyService myService = new MyService();@Mockprivate MyRepository myRepository;@Testpublic void t1() throws Exception {// mock myRepositorywhen(myRepository.countSku(any())).thenReturn(1L);// mock 私有方法MyService spy = PowerMockito.spy(myService);PowerMockito.doReturn(777L).when(spy, "queryPrivate", any(), any());Long id = spy.func("2000-01-01");System.out.println(id);}
}
5.4.2 Spock
- pom
<dependency><groupId>org.spockframework</groupId><artifactId>spock-core</artifactId><version>1.3-groovy-2.4</version><scope>test</scope>
</dependency><dependency><groupId>org.spockframework</groupId><artifactId>spock-spring</artifactId><version>1.3-groovy-2.4</version><scope>test</scope>
</dependency><!--spock 整合powermock mock 静态方法、私有方法--><dependency><groupId>org.powermock</groupId><artifactId>powermock-module-junit4</artifactId><version>2.0.9</version><scope>test</scope></dependency><dependency><groupId>org.powermock</groupId><artifactId>powermock-api-mockito2</artifactId><version>2.0.9</version><scope>test</scope>
</dependency>
- 业务
@Service
public class MyService {@Resourceprivate MyRepository myRepository;public Long func(String date) {Long count = myRepository.countSku(date);//mock myRepository方法return queryPrivate(date, count);// mock 私有方法}private Long queryPrivate(String date, Long count) {if (saleDdateate.compareTo("2023-01-01") <=0 ){throw new IllegalArgumentException("日期早于2023-01-01");}return count + 1;}
}@Repository
public class MyRepository {public Long countSku(String date) {//业务}
}
- UT
@RunWith(PowerMockRunner.class)
@PowerMockRunnerDelegate(Sputnik.class)
@PrepareForTest([MyService.class])
class MyServiceSpec extends Specification {MyService myService = new MyService()MyRepository myRepository = Mock()def setup(){myService.myRepository = myRepository}def "test"() {given:"mock myRepository"myRepository.countSku(_) >> {123L}and:"mock 私有方法"def spy = PowerMockito.spy(myService)PowerMockito.doReturn(777L).when(spy, "queryPrivate", "2000-01-01", 123L)when:Long id = spy.func("2000-01-01")println(id)then:noExceptionThrown()id == 777L}
}
- 注意事项:
public Long func(String date) {Long count = myRepository.countSku(date);//mock myRepository方法return queryPrivate(date, count);// mock 私有方法}private Long queryPrivate(String date, Long count) {if (date.compareTo("2023-01-01") <=0 ){throw new IllegalArgumentException("日期早于2023-01-01");}return count + 1;}私有方法queryPrivate,第一个入参为String date,第二个入参Long count,在被mock时PowerMockito.doReturn(777L).when(spy, "queryPrivate", "2000-01-01", 123L)传递的值,必须等于queryPrivate非mock情况即正常逻辑下,会传入的值eg:
myRepository.countSku(_) >> {123L}的mock结果为123L。即Long count = 123L,所以,正常执行queryPrivate方法时,count字段就会传入123L。同理,spy.func("2000-01-01"),调用func时,传入date = "2000-01-01",所以,正常执行queryPrivate方法时,date字段就会传入"2000-01-01"故
PowerMockito.doReturn(777L).when(spy, "queryPrivate", "2000-01-01", 123L)//必须传递"2000-01-01" 和 123L
5.5 mock-参数校验
- Rpc入参
- MQ内容
5.5.1 Mockito
- 业务-req
@NotNull(message = "id不能为空")@Positive(message = "id不能为负")private Long id;@NotBlank(message = "日期不能为空")@DateFormatCheck(value = "yyyy-MM-dd", message = "日期必须为yyyy-MM-dd格式")private String date;@NotNull(message = "发布状态不能为空")@Range(min = 0, max = 1, message = "状态输入有误, 0:待确认, 1:已发布")private Integer status;@CollectionSizeCheck(value = 5,message = "当前用户输入的skuId集合最多5个")private List<Long> skuIdList;
- 业务校验参数
@Service
public class MyService {public TResp func(TReq req) {try {// 参数校验ValidateUtil.validateParam(req);} catch (IllegalArgumentException e) {// } return response;}
}
- UT
@RunWith(MockitoJUnitRunner.class)
public class MyServiceTest {@InjectMocksprivate MyService myService;@Testpublic void testQueryProcessList_apramError() {//场景1:参数为异常 - id为空TReq req;TResp resp;req = new QueryProcessListTReq();resp = myService.func(req);Assert.assertEquals(resp.getCode(), -1);//场景2:参数为异常 - 日期为空req.setId(323L);resp = myService.func(req);Assert.assertEquals(resp.getCode(),-1);//场景3:参数为异常 - 日格式异常req.setId(3L);req.setDate("20220320");resp = myService.func(req);Assert.assertEquals(resp.getCode(), -1);//场景4:参数为异常 - 状态为空req.setDate("2022-03-20");resp = myService.func(req);Assert.assertEquals(resp.getCode(), -1);//场景5:参数为异常 - 状态值不在0-1之间req.setDate("2022-03-20");req.setStatus(2);resp = myService.func(req);Assert.assertEquals(resp.getCode(),-1);//场景6:参数为异常 - sku大小 > 5req.setStatus(1);req.setSkuIdList(Lists.newArrayList(1L, 2L, 3L, 4L, 5L, 6L));resp = myService.func(req);Assert.assertEquals(resp.getCode(), -1);}
}
5.5.2 Spock
- 业务 -MQ内容
@Data
public class Msg implements Serializable{private Long id;
}
- 业务 -校验MQ内容
@Service("myConsumer")
public class MyConsumer {@KafkaMsgReceivepublic ConsumeStatus receive(String msgBody) {try {Msg msg = GsonUtils.fromJson(msgBody, Msg.class);validateParams(msg);// ---} catch (IllegalArgumentException e) {} catch (Exception e) {}return ConsumeStatus.CONSUME_SUCCESS;}private void validateParams(Msg msg) {Preconditions.checkArgument(Objects.nonNull(msg), "合单消息不能为null");Long id = msg.getId();Preconditions.checkArgument(Objects.nonNull(id) && id > 0, "ID不能为null或0");}
}
- UT
class MyConsumerSpec extends Specification {MyConsumer myConsumer = new MyConsumer()def setup() {}@Unrolldef "test validateParams,#caseDesc"() {given:Msg msg = ownDefineMsgwhen:myConsumer.validateParams(msg)then:def error = thrown(IllegalArgumentException)error.getMessage() == errMsgwhere:caseDesc | ownDefineMsg | errMsg"消息不能为null" | null | "消息不能为null""ID不能为null或0" | new Msg(id:0L) | "ID不能为null或0"}
}
补充:页面请求中,获取前端字符串格式的入参,直接转后端对象,避免了繁琐的属性赋值
String requestStr = "{是个Json字符串}";
TRequest tRequest = GsonUtils.fromJson(requestStr, TRequest.class);
5.6 mock-循环(for、while)
适用于循环中,对方法mock
eg:200、200循环查询db 或 依赖方数据
- 第一次查询,有200条数据
- 第二次查询,有10条数据
总数据量 = 200 + 10
5.6.1 Mockito
- 业务
@Service
public class MyService {@Resourceprivate MyRepository myRepository;public BigDecimal calculateGMV(List<Order> orderList) {BigDecimal gmv = BigDecimal.ZERO;for (Order order : orderList) {//单价BigDecimal skuPrice = order.getPrice();//数量Long skuId = order.getSkuId();Long skuCount = myRepository.countSku(skuId);//销售额gmv = gmv.add(skuPrice.multiply(BigDecimal.valueOf(skuCount)));}// 总销售额return gmv.setScale(2, BigDecimal.ROUND_HALF_DOWN);}
}@Repository
public class MyRepository {public Long countSku(Long skuId) {return null;}
}
- UT
@RunWith(MockitoJUnitRunner.class)
public class MyServiceTest {@InjectMocksprivate MyService myService;@Mockprivate MyRepository myRepository;@Testpublic void test() {Order o1 = Order.builder().skuId(123456789L).price(BigDecimal.valueOf(10.0)).build();Order o2 = Order.builder().skuId(987654321L).price(BigDecimal.valueOf(20.0)).build();when(myRepository.countSku(123456789L)).thenReturn(1L);when(myRepository.countSku(987654321L)).thenReturn(2L);BigDecimal gmv = myService.func(Lists.newArrayList(o1, o2));assertEquals(50L, gmv.longValue());}
}
5.6.2 Spock
- 业务:同上
- UT
class MyServiceSpec extends Specification {MyService myService = new MyService()MyRepository myRepository = Mock()def setup() {myService.myRepository = myRepository}def "test "() {given:def o1 = new Order(skuId:123456789L, price: 10)def o2 = new Order(skuId:987654321L, price: 20)and:"for循环中,2次调用myRepository.countSku定义得到不同结果"2 * myRepository.countSku(_) >> 1L >> 2L//等效 >> {Long skuId -> Objects.equals(skuId, 123456789L) ? 1L : 2L}when:BigDecimal gmv = myService.calculateGMV(Lists.newArrayList(o1, o2))then:"结果验证"gmv.longValue() == 50L // 50 = 1*10 + 2*20}
}
5.7 打印日志
99%的人还看了
相似问题
- Kotlin学习——kt里的集合,Map的各种方法之String篇
- Office文件在线预览大全-Word文档在线预览的实现方法-OFD文档在线预览-WPS文件在线预览
- composer切换全局镜像源的方法
- Python通过selenium调用IE11浏览器报错解决方法
- 测试用例的设计方法(全):正交实验设计方法|功能图分析方法|场景设计方发
- Java8新特性 ----- Lambda表达式和方法引用/构造器引用详解
- C#中抽象类、抽象方法和接口暨内联临时变量的精彩表达
- ChatGLM2 大模型微调过程中遇到的一些坑及解决方法(更新中)
- 类方法,静态方法和实例方法的区别及应用场景
- 【链表的说明、方法---顺序表与链表的区别】
猜你感兴趣
版权申明
本文"Mockito和Spock实战":http://eshow365.cn/6-13018-0.html 内容来自互联网,请自行判断内容的正确性。如有侵权请联系我们,立即删除!
- 上一篇: SQL语句学习系列(1)
- 下一篇: C++中的Template