赞
踩
目录
多变量的数据管道(Multi-Variable Data Pipes)
- <!-- spock -->
- <dependency>
- <groupId>org.spockframework</groupId>
- <artifactId>spock-core</artifactId>
- <version>1.0-groovy-2.4</version>
- <scope>test</scope>
- </dependency>
- package com.youzan.ebiz.salesman.wap.biz.groovy
-
- import com.youzan.ebiz.mall.commons.helper.ResultHelper
- import com.youzan.ebiz.salesman.api.push.NsqPushService
- import com.youzan.ebiz.salesman.api.relation.RelationApiService
- import com.youzan.ebiz.salesman.api.shoppingguide.ShoppingGuideCheckApiService
- import com.youzan.ebiz.salesman.wap.api.dto.relation.CustomerRelationSaveWapDTO
- import com.youzan.ebiz.salesman.wap.api.dto.resp.StatusDTO
- import com.youzan.ebiz.salesman.wap.biz.api.customer.CustomerWapApiServiceImpl
- import com.youzan.ebiz.salesman.wap.biz.service.WhitelistService
- import com.youzan.ebiz.salesman.wap.biz.service.dependency.shoppingguide.GuideFrontRelationWrapperService
- import com.youzan.ebiz.salesman.wap.biz.service.dependency.shoppingguide.ShoppingGuideReadApiWrapperService
- import com.youzan.guide.api.service.relation.CustomerRelationWriteApiService
- import spock.lang.Specification
- import spock.lang.Unroll
-
- /**
- * @author zhangkun* @date 2022/1/18 下午7:10
- * @version 1.0
- */
- class CustomerWapApiServiceImplTest extends Specification {
-
- def customerWapApiServiceImpl = new CustomerWapApiServiceImpl();
- def nsqPushService = Mock(NsqPushService);
- def whitelistService = Mock(WhitelistService);
- def guideFrontRelationWrapperService = Mock(GuideFrontRelationWrapperService);
- def relationApiService = Mock(RelationApiService);
- def customerRelationWriteApiService = Mock(CustomerRelationWriteApiService);
- def shoppingGuideCheckApiService = Mock(ShoppingGuideCheckApiService)
- def shoppingGuideReadApiWrapperService = Mock(ShoppingGuideReadApiWrapperService)
-
- def setup() {
- customerWapApiServiceImpl.nsqPushService = nsqPushService
- customerWapApiServiceImpl.whitelistService = whitelistService
- customerWapApiServiceImpl.guideFrontRelationWrapperService = guideFrontRelationWrapperService
- customerWapApiServiceImpl.relationApiService = relationApiService
- customerWapApiServiceImpl.customerRelationWriteApiService = customerRelationWriteApiService
- customerWapApiServiceImpl.shoppingGuideCheckApiService = shoppingGuideCheckApiService
- customerWapApiServiceImpl.shoppingGuideReadApiWrapperService = shoppingGuideReadApiWrapperService
- }
-
- @Unroll
- def "testBindCustomerRelation #caseName"() {
- given: "准备数据"
- def customerRelationSaveWapDTO = new CustomerRelationSaveWapDTO(bindSourceType: bindSourceType, kdtId: kdtId, sellerFrom: sellerFrom, buyerId: buyerId, shoppingGuideBindSourceType: shoppingGuideBindSourceType);
-
- and: "mock数据"
- shoppingGuideCheckApiService.isNormalShoppingGuide(_) >> isShoppingGuide;
- shoppingGuideReadApiWrapperService.getShoppingGuideByUserId(_, _) >> isPosShoppingGuide
- nsqPushService.pushNsq(_) >> null;
- whitelistService.inWhiteList(_, _) >> inWhiteList;
- guideFrontRelationWrapperService.bindCustomerRelationForWap(_) >> true;
-
- when: "调用接口"
- print("入参:--->" + customerRelationSaveWapDTO);
- def res = customerWapApiServiceImpl.bindCustomerRelation(customerRelationSaveWapDTO);
- print("返回结果--->" + res)
-
- then: "校验结果"
- code == res.code;
- data == res.data;
- message == res.message;
-
-
- where:
- caseName | isShoppingGuide | isPosShoppingGuide | inWhiteList | userId | shoppingGuideBindSourceType | bindSourceType | kdtId | buyerId | fansId | fansType | sellerFrom | code | data | message
- "userId不存在" | null | null | false | 1 | 1 | 1 | 1 | 1 | 1 | 1 | "sl" | 300000000 | null | "查询结果为空"
- "sl为空" | null | null | false | 1 | 1 | 1 | 1 | 1 | 1 | 1 | null | 200 | new StatusDTO(true) | "successful"
- "sl为undefined" | null | null | false | 1 | 1 | 1 | 1 | 1 | 1 | 1 | "undefined" | 200 | new StatusDTO(true) | "successful"
- "sl错误" | null | null | false | 1 | 1 | 1 | 1 | 1 | 1 | 1 | "sajjv" | 300000000 | null | "查询结果为空"
- "切流新方法" | ResultHelper.success(true) | null | true | 1 | 1 | 1 | 1 | 1 | 1 | 1 | "sajjv" | 200 | new StatusDTO(true) | "successful"
- "未切流新方法" | ResultHelper.success(true) | null | false | 1 | 1 | 1 | 1 | 1 | 1 | 1 | "sajjv" | 200 | new StatusDTO(false) | "successful"
-
- }
-
- }
下面再来仔细介绍下spock框架
用JUnit写的单元测试
- @Test
-
- public void addPerson() {
-
- // 正常添加
-
- PersonVo personVo = PersonVo.builder()
- .idCardNo("1345")
- .name("Jack")
- .sex("male")
- .build();
-
- Assert.assertTrue(this.personService.addPerson(personVo));
- // 名字重复
-
- personVo = PersonVo.builder()
- .idCardNo("1346")
- .name("Jack")
- .sex("male")
- .build();
-
- Assert.assertFalse(this.personService.addPerson(personVo));
- // idCardNo重复
- personVo = PersonVo.builder()
- .idCardNo("1345")
- .name("Jack Chen")
- .sex("male")
- .build();
- Assert.assertFalse(this.personService.addPerson(personVo));
-
- }
使用Spock编写同样的单元测试
- @Unroll
- def "addPerson:(idCardNo->#idCardNo, sex->#sex, name->#name), expect:#result"() {
- // 前置条件 同setup
- given:
- def personVo = PersonVo(
- idCardNo: idCardNo,
- name: name,
- sex: sex
- )
-
- // 预期
- expect:
- result == this.personService.addPerson(personVo)
-
- // 条件
- where:
- // 数据定义方法一
- // |用来分隔输入 ||用来分隔输出
- idCardNo | name | sex || result
- "5101" | "Jack" | "male" || true
- // idCardNo重复
- "5101" | "John" | "male" || false
- // name重复
- "5102" | "Jack" | "male" || false
- "123456" | "Lucy" | "female" || true
-
- }
Specification:
测试类都必须继承Specification类
Fixture Methods
- // 每个spec前置
- def setupSpec() {
-
- }
-
- // 每个spec后置
- def cleanupSpec() {
-
- }
-
- // 每个方法前置
- def setup() {
-
- }
-
- // 每个方法后置
- def cleanup() {
-
- }
Feature methods
- // 动态方法名
- @Unroll
- def "addPerson:(idCardNo->#idCardNo, sex->#sex, name->#name), expect:#result"() {
-
- }
-
- // 固定方法名
- def addPerson(){
-
- }
setup/given Blocks
在这个block中会放置与这个测试函数相关的初始化程序
- given: // 也可以写作setup
- def stack = new Stack()
- def elem = "push me"
when and then Blocks
- when:
- stack.push(elem)
-
-
- then:
- !stack.empty
- stack.size() == 1
- stack.peek() == elem
expect Blocks
when and then Blocks例子可以替换为:
- given:
- def stack = new Stack()
- def elem = "push me"
- stack.push(elem)
-
-
- expect:
- stack.empty == false
- stack.size() == 1
- stack.peek() == elem
where Blocks
做测试时最复杂的事情之一就是准备测试数据,尤其是要测试边界条件、测试异常分支等,这些都需要在测试之前规划好数据.
- def "maximum of two numbers"() {
- expect:
- // exercise math method for a few different inputs
- Math.max(1, 3) == 3
- Math.max(7, 4) == 7
- Math.max(0, 0) == 0
- }
-
- // 可以替换为
-
- def "maximum of two numbers"() {
- expect:
- Math.max(a, b) == c
- where:
- a | b || c
- 3 | 5 || 5
- 7 | 0 || 7
- 0 | 0 || 0
-
- }
- class CalculateSpec extends Specification {
-
- // 每个spec前置
- def setupSpec() {
- calculateService = new CalculateService()
- println ">>>>>> setupSpec"
- }
-
- // 每个方法前置
- def setup() {
- println ">>>>>> setup"
- }
-
- // 每个方法后置
- def cleanup() {
- println ">>>>>> cleanup"
- }
-
- // 每个spec后置
- def cleanupSpec() {
- println ">>>>>> cleanupSpec"
-
- }
-
-
-
- def "test life cycle"() {
- given:
- def a = 1
- def b = 2
-
- expect:
- a < b
- println "test method finished!"
- }
-
- }
- def "test person use with(p)"() {
-
- given: "init a person"
- Date now = new Date()
- Person p = new Person(name: "yawn", age: 18, birthday: now)
-
- expect: "测试p"
-
- with(p) {
- name == "yawn"
- age < 20
- birthday == now
- }
- }
- @Unroll
-
- def "exception handle"() {
- when:
- ValidateUitls.validate(new SomeDTO(id: id, name: name))
-
- then:
- def exception = thrown(Exception)
- errMsg == exception.getMessage
-
- where:
- id | name | errMsg
- null | null | "some error message"
-
- }
- @SpringBootTest
- @ContextConfiguration
- class SpringBootSpec extends Specification {
- @Share
- CalculateService calculateService;
-
- def "spring boot test"() {
-
- expect: "asas"
- z == calculateService.minus(x, y)
- where:
- x << [9, 8, 7]
- y << [6, 5, 4]
- z << [3, 3, 3]
-
- }
-
- def "spring boot test2"() {
-
- expect: "asas"
- z == calculateService.minus(x, y)
-
- where:
- x | y | z
- 9 | 8 | 1
- 6 | 5 | 1
- 3 | 3 | 0
- }
-
- }
Spock数据驱动测试( Data Driven Testing ),就是测试用例的书写格式更加面向数据,spock的数据驱动测试的书写格式,可即很清晰地汇集大量测试数据。
其中测试方法的参数 int a, int b, int c
称为数据变量(data variables),where 标签后的语句块称为数据表(data table)。
- class MathSpec extends Specification {
- def "maximum of two numbers"(int a, int b, int c) {
- expect:
- Math.max(a, b) == c
- where:
- a | b | c
- 1 | 3 | 3
- 7 | 4 | 7
- 0 | 0 | 0
- }
- }
数据表(data table)就是一个看起来直观的存放测试数据的表格。数据表的第一行表头对应数据变量,从第二行开始就是数据行(data rows)。
如果测试方法只有一个数据变量a,则需要
|
数据表的简化写法
由于数据变量在被测试的语句Math.max(a, b) == c
中有所体现,所以测试方法的参数可以省略掉;此外,被测试的方法Math.max(a, b) == c
中,a,b为参数,c
- def "maximum of two numbers"() {
- expect:
- Math.max(a, b) == c
- where:
- a | b || c
- 1 | 3 || 3
- 7 | 4 || 7
- 0 | 0 || 0
- }
where
模块第一行代码是表格的列名,多个列使用|
单竖线隔开,||
双竖线区分输入和输出变量,即左边是输入值,右边是输出值。格式如下:
输入参数a | 输入参数b || 输出结果c
其实,上面介绍的数据表只是数据管道的语法糖。数据管道(data pipes)就是给每一个数据变量提供一组值,写法如下:
|
一个数据管道,由两部分组成,<<
前面的是数据变量,<<
后面的是数据来源( data provider )。其中 数据来源( data provider ) 可以为任何Groovy中可遍历的对象,包括 Collection
, String
, Iterable
等类型的对象和实现了 Iterable
的对象。
数据来源( data provider )也不要求是一定是类似的集合,它也可以是从外部的文本文件、数据库、电子表格等获取数据的代码,甚至是自动生成数据的代码片段。
如果一个数据来源(data provider)每次迭代可以提供多个变量,则可以将它做为多个数据变量的数据来源 ,形成一个多变量的数据管道(Multi-Variable Data Pipes)。如下:
- @Shared sql = Sql.newInstance("jdbc:mysql://localhost:3306/test", "com.mysql.jdbc.Driver", "root", "root")
-
- def "maximum of two numbers"() {
- expect:
- Math.max(a, b) == c
- where:
- [a, b, c] << sql.rows("select a, b, c from maxdata")
- }
如果数据来源的某列返回值我们并不使用,则可以使用下划线“_”,这样就会被忽略,如下:
|
如图,有如上的方法调用关系(模块依赖关系):A调用B和E方法,B调用C和D方法。在使用spock进行单元测试时,有如下情景,分别可使用stub和mock。
如果我们需要测试A方法,但是E方法目前还没办法调用,或者还没开发完成。这种场景下,就可以使用stub测试桩。stub测试桩可以给E方法模拟一个或多个假的返回值,我们测试时只需要调用stub对象的E方法即可,调用后的返回值是我们在生成stub对象时指定的。如下:
- def "Stub 测试桩"() {
- given: "构造测试桩"
- CalculateInterface calculateService = Stub(CalculateInterface)
- calculateService.plusPlus(_) >> 1
-
- when:
- int x = calculateService.plusPlus(12)
- int y = calculateService.plusPlus(3)
-
- then:
- x == 1
- y == 1
-
- }
上面代码中,calculateService.plusPlus(_) >> 1
给一个并未实现的plusPlus()方法指定了返回值为1,测试代码就可以直接调用这个方法了。
其中这个语句的常用格式有:
|
生成返回值
|
通过计算生成返回值
这种方式,生成返回值的格式时一个闭包
|
如果想调用方法抛出异常
|
链式生成返回值
|
上面代码中,方法被调用的前三次分别返回 “ok”, “fail”, “ok”,第四次会抛出异常,第五次及以后调用,会返回“ok”。
以上是spock中stub测试桩的使用场景,总结为一句就是: stub测试桩给被调用者( 方法/模块)制造假的返回值,以便不影响调用者的测试。
mock测试桩就是模拟一个测试的结果。如下图,A类调用类B和C类的某个方法:
如果要测试A的方法,但是我们没办法调用B来检测结果,就可以使用mock测试桩,生成一个B的mock对象。检验结果时,可以使用B的mock对象替代B。这个结果一般是B和C方法的调用或者状态的改变。
- def subscriber = Mock(Subscriber) // 1. 创建一个mock对象
- def "should send messages subscriber"() {
- when:
- publisher.send("hello") // 2. publisher 发送一个“hello”
- then:
- 1 * subscriber.receive("hello") // 3. subscriber 接收到一个“hello”
- 1 * subscriber.messageCount == 1
-
- }
@Share
在测试类中,Share标记的变量可以在不同的测试方法中使用。
@Ignore 忽略
@IgnoreRest 忽略其他
@Unroll 展开数据管道的测试用例
@FailsWith(ArithmeticException.class) 标记失败
@Timeout(value = 10, unit = TimeUnit.MILLISECONDS) 超时时间设置
@IgnoreIf 根据条件忽略
|
@Requires 根据条件执行
|
@Retry 重试
|
@Unroll 展开
|
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。