爱上单元测试:基于Groovy的Spock框架

Spock测试框架基于Groovy并吸收了Junit、TestNG、Mockito等测试框架的优点。 Spock编写的单元测试层次清晰,代码量少,可读性好。 Groovy无缝兼容Java:Groovy最终会编译为class文件,JVM并不在乎class来自Java还是Groovy文件,支持各种集成开发环境(eclipse,Intellij Ieda),尤其是Intellij idea已经集成支持Groovy的插件,也支持maven-surefire-plugin、jacoco等maven插件。 1.学习前的准备 官网:http://spockframework.org 必读书籍:《Java Testing with Spock》 如要速成只需要阅读以下两篇文章: 5分钟入门Groovy: https://learnxinyminutes.com/docs/groovy/ 一篇非常详尽的介绍Spock的英文教程:https://semaphoreci.com/community/tutorials/stubbing-and-mocking-in-java-with-the-spock-testing-framework 2.Maven依赖 Eclipse支持Groovy需要安装插件:https://github.com/groovy/groovy-eclipse/wiki 注意Maven作用域控制在test org.spockframework spock-core 1.1-groovy-2.4 test net.bytebuddy byte-buddy 1.6.5 test org.objenesis objenesis 2.5.1 test 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 3. 被测试的类 //数据库映射类 public class DbEntity { long id; String requestNo; //省略get set } //被测试的service @Component public class MyService { @Autowired private DbEntityDao dbEntityDao; private String value; public String getValue() { return value; } public int stringToInteger(String valueStr) { return Integer.valueOf(valueStr); } public String getUtilValue(String id) { return id+MyServiceUtil.getValue(id); } public void setValue(String value) { this.value = value; } public long selectByRequestNo(String requestNo){ DbEntity dbEntity = dbEntityDao.selectByRequestNo(requestNo); return dbEntity.getId(); } public int add(int a,int b){ return a+b; } } //数据库操作类 public interface DbEntityDao { DbEntity selectByRequestNo(@Param("requestNo")String requestNo); } //工具类 public class MyServiceUtil { public static String getValue(String value){ return "MyServiceUtil"; } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 4. given-when-then声明 given-when-then是Spock的基本句式,使单元测试层次清晰。 此外还有and关键字,用于将大段的声明分割开来。 所有的Spock单元测试都继承spock.lang.Specification,Specification基于Groovy dsl提供了测试环境。 Spock支持将一个句子作为方法名,实现自解释。 Stub用来创建要模拟的测对象。 “>>”用来表示模拟对象的返回值 “>>>”用来表示同一方法多次按顺序调用返回不同值 下划线“_”表示匹配所有的输入值。 Spock不使用Assert来校验结果,then声明后面的表达式result == “1”就相当于Junit中的Assert.assertTrue(result.equals(“1”)) 下面通过例子展示以下用法,Groovy可以不写分号 import spock.lang.Specification /** * JUnit 的测试用例总是由 Runner 去执行 * Specification的注解:@RunWith(Sputnik.class) 是对 org.junit.runner.Runner的扩展 */ class MyServiceTest extends Specification { //groovy里面用def定义所有的对象,包含方法声明 def mockRequestNo = "123" def "最基本的测试:mock返回值"() { given: "given用来准备mock对象,可以放到when里面" and:"and声明可选" myService.getValue() >> "1" when: "when里面调用被测试的方法" String result = myService.getValue() then: "then用来验证结果" result == "1" } def "最基本的测试:多次调用返回不同值"() { given: "返回多个值使用三个>" MyService myService = Stub(MyService) myService.getValue() >>> ["1", "2", "3"] //except相当于when then的合并 expect: myService.getValue() == "1" myService.getValue() == "2" myService.getValue() == "3" } def "测试模糊匹配"() { given: MyService myService = Stub(MyService) //下划线表示匹配所有输入值 myService.stringToInteger(_) >> 999 //except相当于when then的合并 expect: "有时候我们并不在乎输入值" myService.stringToInteger("1") == 999 myService.stringToInteger("2") == 999 myService.stringToInteger("3") == 999 } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 5. 校验模拟对象的行为 Stub只能模拟对象的返回值,而Mock更进一步,不仅能模拟对象,还能校验对象的调用次数等行为。 表达式: N * mockedObject.method(arguments)>>value,表示参数为arguments的method方法调用N次,返回值是value。该表达式一般放在then后面。 注意,尽管then声明放在when后面,由于基于Groovy AST语法解析树,Spock会先解析该表达式,然后在when后面的测试类执行后再进入then后面的校验逻辑。 Groovy里面一切field都为public,免去了注入的烦恼 Groovy的lambda表达式比Java 8更加灵活,通过lambda可以抓取测试对象的输入值,嵌套很深的测试类很有必要验证一下输入值是否正确。 def mockRequestNo = "123" def "校验mock对象的调用次数"() { given: DbEntity expectEntity = new DbEntity() //groovy的with语法,简化创建对象 expectEntity.with { id = 456 requestNo = mockRequestNo } //mock跟Stub不同的是可以校验mock对象的调用次数 DbEntityDao dao = Mock(DbEntityDao) MyService myService = new MyService() //Groovy里面所有的field都是public的,可以直接访问 myService.dbEntityDao = dao when: long id = myService.selectByRequestNo(mockRequestNo) then: id == 456 //then除了验证结果,还可以设置mock对象返回值, //Spock会先解析then中定义的mock,等when执行后再校验mock行为 1 * dao.selectByRequestNo(mockRequestNo) >> expectEntity } def "抓取输入值"(){ given: def resultCapture = null DbEntityDao dao = Stub(DbEntityDao.class) //定义lambda表达式,抓取输入值 dao.selectByRequestNo({v-> resultCapture = v })>>null when: dao.selectByRequestNo("123") then: resultCapture == "123" } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 6. 参数化测试 Spock的参数化测试比Junit更加简洁。 直接使用表格形式来定义输入值跟期望值。 注意输入的参数名必须跟被测试方法参数名一致。 如下代码,表格中的输入参数跟测试方法输入参数名均为a,b def "test Parameterized"() { when: MyService myService = new MyService() then: myService.add(a, b) == result where: "准备参数,输入参数名必须跟方法里面的参数名一致" a | b || result 1 | 1 || 2 1 | 2 || 3 2 | 2 || 4 } 1 2 3 4 5 6 7 8 9 10 11 7. 同其他测试框架混搭 对于static、private方法,Spock还是无能为力。这时候可以结合PowerMock框架。 JUnit 的测试用例总是由 Runner 去执行,JUnit 提供了 @RunWith 注解来指定自定义的 Runner。 如果未指定特别的 Runner,那么会采用默认的 Runner 由于Spock的Runner直接继承自Junit Runner可以很好的扩展。下面展示如何跟PowerMock集成。 import spock.lang.Specification @RunWith(PowerMockRunner.class) @PowerMockRunnerDelegate(Sputnik.class) @PrepareForTest([MyServiceUtil.class]) class MyServiceWithOtherRunner extends Specification{ def id = "1024" def "测试难缠的static方法"(){ given: PowerMockito.mockStatic(MyServiceUtil.class) PowerMockito.when(MyServiceUtil.getValue(id)).thenReturn("horrible") MyService myService = new MyService(); when: String result = myService.getUtilValue(id) then: result == id+"horrible" } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 8.其他 该小节展示一些杂项 类似于Junit注解的声明 def setupSpec() { //整个单元测试启动的时候只执行一次,类似Junit的@BeforeClass println "Will run only once" } //每个方法执行前都会执行,等同Junit @Before def setup() { println "Will run before EACH feature" } def cleanup() { //执行后每次执行 println "Will run once after EACH feature" } def cleanupSpec() { //整个单元测试结束后执行 println "Will run once at the end" } 1 2 3 4 5 6 7 8 9 10 11 12 13 模拟异常 def "test trade"() { given: mockObject.method() >> { throw new RuntimeException("哈哈,中计了") } when: "踩坑" mockObject.method() then: "validator" RuntimeException runtimeException = thrown(RuntimeException) runtimeException.message == "哈哈,中计了" } 1 2 3 4 5 6 7 8 9 10 简化校验 then: mockObject.value1 == 1 mockObject.value2 == 2 mockObject.value3 == 3 mockObject.value4 == 4 得益于Groovy with闭包,还可以简化成这样 then: with(mockObject){ value1 == 1 value2 == 2 value3 == 3 value4 == 4 } 同样使用Stub初始化对象也可以写成这样,isEmpty()等均是类WarehouseInventory里面的方法。 when: WarehouseInventory inventory = Stub(WarehouseInventory) { isProductAvailable("bravia",1) >> true isProductAvailable("panasonic",1) >> false isEmpty() >> false } --------------------- 本文来自 plusxia 的CSDN 博客 ,全文地址请点击:https://blog.csdn.net/j16421881/article/details/80358325?utm_source=copy

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值