当前位置:   article > 正文

开始spock单测_spock csdn

spock csdn

目录

引入maven依赖

项目实战

Spock测试框架

一、为什么要使用Spock

二、Spock基本概念

三、Spock基本使用

基本标签语句组合形

with() 和 verifyAll()

spock异常处理

spring环境中使用spock

四、Spock数据驱动

数据表(data table)

数据管道(Data Pipes)

多变量的数据管道(Multi-Variable Data Pipes)

五、spock测试桩mock和stub的使用

使用stub测试桩

使用mock测试桩

mock和stub测试桩的对比

六、Spock注解使用

七、实战案例

1、数据驱动

2、stub测试桩

八、注意事项

1、新增ut

九、其他


引入maven依赖

  1. <!-- spock -->
  2. <dependency>
  3. <groupId>org.spockframework</groupId>
  4. <artifactId>spock-core</artifactId>
  5. <version>1.0-groovy-2.4</version>
  6. <scope>test</scope>
  7. </dependency>

项目实战

  1. package com.youzan.ebiz.salesman.wap.biz.groovy
  2. import com.youzan.ebiz.mall.commons.helper.ResultHelper
  3. import com.youzan.ebiz.salesman.api.push.NsqPushService
  4. import com.youzan.ebiz.salesman.api.relation.RelationApiService
  5. import com.youzan.ebiz.salesman.api.shoppingguide.ShoppingGuideCheckApiService
  6. import com.youzan.ebiz.salesman.wap.api.dto.relation.CustomerRelationSaveWapDTO
  7. import com.youzan.ebiz.salesman.wap.api.dto.resp.StatusDTO
  8. import com.youzan.ebiz.salesman.wap.biz.api.customer.CustomerWapApiServiceImpl
  9. import com.youzan.ebiz.salesman.wap.biz.service.WhitelistService
  10. import com.youzan.ebiz.salesman.wap.biz.service.dependency.shoppingguide.GuideFrontRelationWrapperService
  11. import com.youzan.ebiz.salesman.wap.biz.service.dependency.shoppingguide.ShoppingGuideReadApiWrapperService
  12. import com.youzan.guide.api.service.relation.CustomerRelationWriteApiService
  13. import spock.lang.Specification
  14. import spock.lang.Unroll
  15. /**
  16. * @author zhangkun* @date 2022/1/18 下午7:10
  17. * @version 1.0
  18. */
  19. class CustomerWapApiServiceImplTest extends Specification {
  20. def customerWapApiServiceImpl = new CustomerWapApiServiceImpl();
  21. def nsqPushService = Mock(NsqPushService);
  22. def whitelistService = Mock(WhitelistService);
  23. def guideFrontRelationWrapperService = Mock(GuideFrontRelationWrapperService);
  24. def relationApiService = Mock(RelationApiService);
  25. def customerRelationWriteApiService = Mock(CustomerRelationWriteApiService);
  26. def shoppingGuideCheckApiService = Mock(ShoppingGuideCheckApiService)
  27. def shoppingGuideReadApiWrapperService = Mock(ShoppingGuideReadApiWrapperService)
  28. def setup() {
  29. customerWapApiServiceImpl.nsqPushService = nsqPushService
  30. customerWapApiServiceImpl.whitelistService = whitelistService
  31. customerWapApiServiceImpl.guideFrontRelationWrapperService = guideFrontRelationWrapperService
  32. customerWapApiServiceImpl.relationApiService = relationApiService
  33. customerWapApiServiceImpl.customerRelationWriteApiService = customerRelationWriteApiService
  34. customerWapApiServiceImpl.shoppingGuideCheckApiService = shoppingGuideCheckApiService
  35. customerWapApiServiceImpl.shoppingGuideReadApiWrapperService = shoppingGuideReadApiWrapperService
  36. }
  37. @Unroll
  38. def "testBindCustomerRelation #caseName"() {
  39. given: "准备数据"
  40. def customerRelationSaveWapDTO = new CustomerRelationSaveWapDTO(bindSourceType: bindSourceType, kdtId: kdtId, sellerFrom: sellerFrom, buyerId: buyerId, shoppingGuideBindSourceType: shoppingGuideBindSourceType);
  41. and: "mock数据"
  42. shoppingGuideCheckApiService.isNormalShoppingGuide(_) >> isShoppingGuide;
  43. shoppingGuideReadApiWrapperService.getShoppingGuideByUserId(_, _) >> isPosShoppingGuide
  44. nsqPushService.pushNsq(_) >> null;
  45. whitelistService.inWhiteList(_, _) >> inWhiteList;
  46. guideFrontRelationWrapperService.bindCustomerRelationForWap(_) >> true;
  47. when: "调用接口"
  48. print("入参:--->" + customerRelationSaveWapDTO);
  49. def res = customerWapApiServiceImpl.bindCustomerRelation(customerRelationSaveWapDTO);
  50. print("返回结果--->" + res)
  51. then: "校验结果"
  52. code == res.code;
  53. data == res.data;
  54. message == res.message;
  55. where:
  56. caseName | isShoppingGuide | isPosShoppingGuide | inWhiteList | userId | shoppingGuideBindSourceType | bindSourceType | kdtId | buyerId | fansId | fansType | sellerFrom | code | data | message
  57. "userId不存在" | null | null | false | 1 | 1 | 1 | 1 | 1 | 1 | 1 | "sl" | 300000000 | null | "查询结果为空"
  58. "sl为空" | null | null | false | 1 | 1 | 1 | 1 | 1 | 1 | 1 | null | 200 | new StatusDTO(true) | "successful"
  59. "sl为undefined" | null | null | false | 1 | 1 | 1 | 1 | 1 | 1 | 1 | "undefined" | 200 | new StatusDTO(true) | "successful"
  60. "sl错误" | null | null | false | 1 | 1 | 1 | 1 | 1 | 1 | 1 | "sajjv" | 300000000 | null | "查询结果为空"
  61. "切流新方法" | ResultHelper.success(true) | null | true | 1 | 1 | 1 | 1 | 1 | 1 | 1 | "sajjv" | 200 | new StatusDTO(true) | "successful"
  62. "未切流新方法" | ResultHelper.success(true) | null | false | 1 | 1 | 1 | 1 | 1 | 1 | 1 | "sajjv" | 200 | new StatusDTO(false) | "successful"
  63. }
  64. }

下面再来仔细介绍下spock框架

Spock测试框架

一、为什么要使用Spock

用JUnit写的单元测试

  1. @Test
  2. public void addPerson() {
  3.     // 正常添加
  4.     PersonVo personVo = PersonVo.builder()
  5.         .idCardNo("1345")
  6.         .name("Jack")
  7.         .sex("male")
  8.         .build();
  9.     Assert.assertTrue(this.personService.addPerson(personVo));
  10.     // 名字重复
  11.     personVo = PersonVo.builder()
  12.         .idCardNo("1346")
  13.         .name("Jack")
  14.         .sex("male")
  15.         .build();
  16.     Assert.assertFalse(this.personService.addPerson(personVo));
  17.     // idCardNo重复
  18.     personVo = PersonVo.builder()
  19.         .idCardNo("1345")
  20.         .name("Jack Chen")
  21.         .sex("male")
  22.         .build();
  23.     Assert.assertFalse(this.personService.addPerson(personVo));
  24. }

使用Spock编写同样的单元测试

  1. @Unroll
  2. def "addPerson:(idCardNo->#idCardNo, sex->#sex, name->#name), expect:#result"() {
  3.     // 前置条件 同setup
  4.     given:
  5.     def personVo = PersonVo(
  6.         idCardNo: idCardNo,
  7.         name: name,
  8.         sex: sex
  9.     )
  10.     // 预期
  11.     expect:
  12.     result == this.personService.addPerson(personVo)
  13.     // 条件
  14.     where:
  15.     // 数据定义方法一
  16.     // |用来分隔输入 ||用来分隔输出
  17.     idCardNo | name   | sex      || result
  18.     "5101"   | "Jack" | "male"   || true
  19.     // idCardNo重复
  20.     "5101"   | "John" | "male"   || false
  21.     // name重复
  22.     "5102"   | "Jack" | "male"   || false
  23.     "123456" | "Lucy" | "female" || true
  24. }

二、Spock基本概念

Specification:

测试类都必须继承Specification类

Fixture Methods

  1. // 每个spec前置
  2. def setupSpec() {
  3. }
  4. // 每个spec后置
  5. def cleanupSpec() {
  6. }
  7. // 每个方法前置
  8. def setup() {
  9. }
  10. // 每个方法后置
  11. def cleanup() {
  12. }

Feature methods

  1. // 动态方法名
  2. @Unroll
  3. def "addPerson:(idCardNo->#idCardNo, sex->#sex, name->#name), expect:#result"() {
  4. }
  5. // 固定方法名
  6. def addPerson(){
  7. }

setup/given Blocks

在这个block中会放置与这个测试函数相关的初始化程序

  1. given: // 也可以写作setup
  2. def stack = new Stack()
  3. def elem = "push me"

when and then Blocks

  1. when:
  2. stack.push(elem) 
  3. then:
  4. !stack.empty
  5. stack.size() == 1
  6. stack.peek() == elem

expect Blocks
when and then Blocks例子可以替换为:

  1. given:
  2. def stack = new Stack()
  3. def elem = "push me"
  4. stack.push(elem)
  5. expect:
  6. stack.empty == false
  7. stack.size() == 1
  8. stack.peek() == elem

where Blocks
做测试时最复杂的事情之一就是准备测试数据,尤其是要测试边界条件、测试异常分支等,这些都需要在测试之前规划好数据.

  1. def "maximum of two numbers"() {
  2.     expect:
  3.     // exercise math method for a few different inputs
  4.     Math.max(13) == 3
  5.     Math.max(74) == 7
  6.     Math.max(00) == 0
  7. }
  8. // 可以替换为
  9. def "maximum of two numbers"() {
  10.     expect:
  11.     Math.max(a, b) == c
  12.     where:
  13.     a | b || c
  14.     3 | 5 || 5
  15.     7 | 0 || 7
  16.     0 | 0 || 0
  17. }

三、Spock基本使用

  1. class CalculateSpec extends Specification {
  2.     // 每个spec前置
  3.     def setupSpec() {
  4.         calculateService = new CalculateService()
  5.         println ">>>>>>   setupSpec"
  6.     }
  7.     // 每个方法前置
  8.     def setup() {
  9.         println ">>>>>>   setup"
  10.     }
  11.     // 每个方法后置
  12.     def cleanup() {
  13.         println ">>>>>>   cleanup"
  14.     }
  15.     // 每个spec后置
  16.     def cleanupSpec() {
  17.         println ">>>>>>   cleanupSpec"
  18.     }
  19.     def "test life cycle"() {
  20.         given:
  21.         def a = 1
  22.         def b = 2
  23.         expect:
  24.         a < b
  25.         println "test method finished!"
  26.     }
  27. }

基本标签语句组合形

  • given … expect … 
  • given … when … then …
  • when … then …
  • given … expect … where …
  • expect … where …
  • expect

with() 和 verifyAll()

  1. def "test person use with(p)"() {
  2.         given: "init a person"
  3.         Date now = new Date()
  4.         Person p = new Person(name: "yawn", age: 18, birthday:   now)
  5.         expect: "测试p"
  6.         with(p) {
  7.             name == "yawn"
  8.             age < 20
  9.             birthday == now
  10.         }
  11.     }

spock异常处理

  1. @Unroll
  2. def "exception handle"() {
  3.     when:
  4.     ValidateUitls.validate(new SomeDTO(id: id, name: name))
  5.     
  6. then:
  7.     def exception = thrown(Exception)
  8.     errMsg == exception.getMessage
  9.     
  10. where:
  11.     id           |  name      |  errMsg
  12.     null         |  null      |  "some error message"
  13. }

spring环境中使用spock

  1. @SpringBootTest
  2. @ContextConfiguration
  3. class SpringBootSpec extends Specification {
  4.     @Share
  5.     CalculateService calculateService;
  6.     def "spring boot test"() {
  7.         expect: "asas"
  8.         z == calculateService.minus(x, y)
  9.         where:
  10.         x << [987]
  11.         y << [654]
  12.         z << [333]
  13.     }
  14.     def "spring boot test2"() {
  15.         expect: "asas"
  16.         z == calculateService.minus(x, y)
  17.         where:
  18.         x | y | z
  19.         9 | 8 | 1
  20.         6 | 5 | 1
  21.         3 | 3 | 0
  22.     }
  23. }

四、Spock数据驱动

Spock数据驱动测试( Data Driven Testing ),就是测试用例的书写格式更加面向数据,spock的数据驱动测试的书写格式,可即很清晰地汇集大量测试数据。


其中测试方法的参数 int a, int b, int c 称为数据变量(data variables),where 标签后的语句块称为数据表(data table)。

  1. class MathSpec extends Specification {
  2.   def "maximum of two numbers"(int a, int b, int c) {
  3.     expect:
  4.     Math.max(a, b) == c
  5.     where:
  6.     a | b | c
  7.     1 | 3 | 3
  8.     7 | 4 | 7
  9.     0 | 0 | 0
  10.   }
  11. }

数据表(data table)

数据表(data table)就是一个看起来直观的存放测试数据的表格。数据表的第一行表头对应数据变量,从第二行开始就是数据行(data rows)。

如果测试方法只有一个数据变量a,则需要

where:

a | _

1 | _

7 | _

0 | _

数据表的简化写法

由于数据变量在被测试的语句Math.max(a, b) == c中有所体现,所以测试方法的参数可以省略掉;此外,被测试的方法Math.max(a, b) == c中,a,b为参数,c

  1. def "maximum of two numbers"() {
  2.     expect:
  3.     Math.max(a, b) == c
  4.     where:
  5.     a | b || c
  6.     1 | 3 || 3
  7.     7 | 4 || 7
  8.     0 | 0 || 0
  9. }

where模块第一行代码是表格的列名,多个列使用|单竖线隔开,||双竖线区分输入和输出变量,即左边是输入值,右边是输出值。格式如下:

输入参数a | 输入参数b || 输出结果c

数据管道(Data Pipes)

其实,上面介绍的数据表只是数据管道的语法糖。数据管道(data pipes)就是给每一个数据变量提供一组值,写法如下:

...

where:

a << [170]

b << [340]

c << [370]

一个数据管道,由两部分组成,<<前面的是数据变量,<<后面的是数据来源( data provider )。其中 数据来源( data provider ) 可以为任何Groovy中可遍历的对象,包括  CollectionStringIterable等类型的对象和实现了 Iterable 的对象。

数据来源( data provider )也不要求是一定是类似的集合,它也可以是从外部的文本文件、数据库、电子表格等获取数据的代码,甚至是自动生成数据的代码片段。

多变量的数据管道(Multi-Variable Data Pipes)

如果一个数据来源(data provider)每次迭代可以提供多个变量,则可以将它做为多个数据变量的数据来源 ,形成一个多变量的数据管道(Multi-Variable Data Pipes)。如下:

  1. @Shared sql = Sql.newInstance("jdbc:mysql://localhost:3306/test""com.mysql.jdbc.Driver""root""root")
  2. def "maximum of two numbers"() {
  3.   expect:
  4.   Math.max(a, b) == c
  5.   where:
  6.   [a, b, c] << sql.rows("select a, b, c from maxdata")
  7. }

如果数据来源的某列返回值我们并不使用,则可以使用下划线“_”,这样就会被忽略,如下:

...

where:

[a, b, _, c] << sql.rows("select * from maxdata")

五、spock测试桩mock和stub的使用

 

如图,有如上的方法调用关系(模块依赖关系):A调用B和E方法,B调用C和D方法。在使用spock进行单元测试时,有如下情景,分别可使用stub和mock。

使用stub测试桩

如果我们需要测试A方法,但是E方法目前还没办法调用,或者还没开发完成。这种场景下,就可以使用stub测试桩。stub测试桩可以给E方法模拟一个或多个假的返回值,我们测试时只需要调用stub对象的E方法即可,调用后的返回值是我们在生成stub对象时指定的。如下:

  1. def "Stub 测试桩"() {
  2.         given: "构造测试桩"
  3.         CalculateInterface calculateService = Stub(CalculateInterface)
  4.         calculateService.plusPlus(_) >> 1
  5.         when:
  6.         int x = calculateService.plusPlus(12)
  7.         int y = calculateService.plusPlus(3)
  8.         then:
  9.         x == 1
  10.         y == 1
  11. }

上面代码中,calculateService.plusPlus(_) >> 1 给一个并未实现的plusPlus()方法指定了返回值为1,测试代码就可以直接调用这个方法了。

其中这个语句的常用格式有:

subscriber.receive(_) >> "ok"

|          |       |     |

|          |       |     生成返回值

|          |       参数(多个参数可以使用:*_)

|          方法

对象

生成返回值

// 不同参数生成不同的返回值

subscriber.receive("message1") >> "ok"

subscriber.receive("message2") >> "fail"

// 生成多个返回值

subscriber.receive(_) >>> ["ok""error""error""ok"]

通过计算生成返回值

这种方式,生成返回值的格式时一个闭包

// 1.使用方法参数计算

subscriber.receive(_) >> { args -> args[0].size() > 3 "ok" "fail" }

// 2. 使用其他参数

subscriber.receive(_) >> { String message -> message.size() > 3 "ok" "fail" }

如果想调用方法抛出异常

subscriber.receive(_) >> { throw new InternalError("ouch") }

链式生成返回值

subscriber.receive(_) >>> ["ok""fail""ok"] >> { throw new InternalError() } >> "ok"

上面代码中,方法被调用的前三次分别返回 “ok”, “fail”, “ok”,第四次会抛出异常,第五次及以后调用,会返回“ok”。

以上是spock中stub测试桩的使用场景,总结为一句就是: stub测试桩给被调用者( 方法/模块)制造假的返回值,以便不影响调用者的测试。

使用mock测试桩

mock测试桩就是模拟一个测试的结果。如下图,A类调用类B和C类的某个方法:

 

如果要测试A的方法,但是我们没办法调用B来检测结果,就可以使用mock测试桩,生成一个B的mock对象。检验结果时,可以使用B的mock对象替代B。这个结果一般是B和C方法的调用或者状态的改变。

  1. def subscriber = Mock(Subscriber)   // 1. 创建一个mock对象
  2.     def "should send messages subscriber"() {
  3.         when:
  4.         publisher.send("hello")         // 2. publisher 发送一个“hello”
  5.         then:
  6.         1 * subscriber.receive("hello"// 3. subscriber 接收到一个“hello”
  7.         1 * subscriber.messageCount == 1
  8. }

mock和stub测试桩的对比

  • mock测试桩用于检测结果。
  • stub测试桩用于提供测试的条件。

六、Spock注解使用

@Share

在测试类中,Share标记的变量可以在不同的测试方法中使用。

@Ignore 忽略

  • 忽略测试方法

@IgnoreRest 忽略其他

  • 忽略其他测试方法

@Unroll 展开数据管道的测试用例

  • 展开:数据驱动测试中,展开所有的测试结果,分别显示每个测试用例的测试情况

@FailsWith(ArithmeticException.class) 标记失败

  • 记录已经知道的 bug
  • 标记让方法执行失败的测试用例

@Timeout(value = 10, unit = TimeUnit.MILLISECONDS) 超时时间设置

  • 超时就失败

@IgnoreIf 根据条件忽略

@IgnoreIf({ System.getProperty("os.name").contains("windows") })

def "I'll run everywhere but on Windows"() { ... }

@Requires 根据条件执行

@Requires({ os.windows })

def "I'll only run on Windows"() { ... }

@Retry 重试

@Retry(count 5)

@Unroll 展开

 

七、实战案例

1、数据驱动

 

2、stub测试桩

 

checkPayGroupBuilder.buildCheckPayResponseDTO(*_) >> new CheckPayResponseDTO(allOrderPaid: true)

|                    |                        |      |

|                    |                        |      生成返回值

|                    |                        参数(多个参数可以使用:*_)

|                    方法

对象

八、注意事项

1、新增ut

 

 

九、其他

spock官方文档

如何使用spock编写单元测试

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/码创造者/article/detail/760034
推荐阅读
相关标签
  

闽ICP备14008679号