Groovy单元测试

本文详细介绍了Spock测试框架的使用,包括其与JUnit和Mockito的集成,以及如何与Spring Boot测试相结合。重点讲解了单元测试的原则和Spock的基础语法,如given-when-then结构,以及数据驱动测试的实现。此外,还分享了如何进行方法的mock,断言验证,以及事务管理。示例代码展示了如何进行多场景测试和多行数据的比较。
摘要由CSDN通过智能技术生成

单元测试说明

spock是基于groovy的测试框架,spock本身集成了Mockito+junit的功能,并且可以springboot-test结合启动容器测试。静态方法和私有方法仍需要使用PowerMock进行功能增强。
第一次使用推荐先看一下demo代码再看官方文档。
demo代码:可参考金融网关 CiticLoanApplyMsgConvertServiceImplTest(基础用法) 和 CiticbankLoanApplyServiceImplTest(Data Table 例子)
官方学习文档:http://spockframework.org/spock/docs/1.3/all_in_one.html

单元测试需要保证以下几点:

  1. 不依赖外部系统
  2. 单元测试足够“单元”,避免流程过长的测试逻辑
  3. 测试方法的数据操作不影响真实数据源的数据现状
  4. 每个测试方法都要有verify或者Assert的验证或断言的操作,否则都是无效的测试方式
  5. 一些依赖外部系统的调用,或不需要每次单元测试都执行的测试类或测试方法,及时使用@Ignore,避免mvn test时执行单元测试异常
基础语法
段落

spock单测方法是场景化测试,将单元测试分成了 [given->]when->then[->where] 或者 [given->]expect->where 段落,
每一个段落都有固定的作用
given: 准备数据阶段,可以以没有
when: 一般放置各种mock和测试方法调用,所以spock要求这个段落必须有(或者用expect)
then: 用于写断言的区域,spock要求必须有
expect: 可以理解为when + then的集合,需要固定跟 where搭配使用
where: 变量填充区。例如我们需要测试入参不同走不同逻辑的测试,junit需要写多个测试用例和mock。spock允许将这部分以数据表格集成到同一个用例,
每行代表一个测试场景,每一列代表一个变量

//@Subject是定义一个主题,目前没什么用处
@Subject(CiticLoanApplyMsgConvertServiceImpl.class)
class CiticLoanApplyMsgConvertServiceImplSpockTest extends SpockBaseRunner {

def init() {
//等于junit @before
}

//spock默认遇到不通过的测试场景就不跑后续单测,@Unroll代表不中断
@Unroll 
def "方法名称,建议直接使用中文"() {
    given: 
    //准备数据阶段,没有可写null,或可以不写

    when: 
    //这里写各种mock方法的返回等和调用需要单测的目标方法,没有可写null
    null

    then:
    //then用于编写断言,判断相等可以直接用 ==
    loanApplyRequest.data.applyAmt == 1004.00  

    and://断言分段编写用and连接
    def contract = loanApplyRequest.data.contractInfoList[0]
    contract.billCode == '60000144202004020128P'
}

}

启动模式

集成springboot-test 测试类继承 SpockBaseRunner,使用这种模式初始化方法为 init() 类似@Before(SpockBaseRunner封装了一下原生的setup())
这种模式下默认会为单测开启数据库事务,单测执行完后自动回滚事务
Specification 纯粹mock的形式类似纯junit + mockito,使用这种模式初始化方法为 setup() 类似junit @Before

Mock

mock一个对象例子如下

def serviceAObject = new ServiceA(
 property1: Mock(ServiceB)

//多重mock
property2: Mock(new ServiceC(
    cProperty1: Mock(ServiceD)
))

)

需要注意的是如果使用SpockBaseRunner 启动,默认容器就会注入所有依赖的bean,所以这时如果需要局部Mock某些属性,需要主动
在原来的Service上暴露对应属性set方法,然后再mock,可以参考 CiticbankLoanApplyServiceImplTest 中 Mock sellSendFileService

class CiticbankLoanApplyServiceImplTest extends SpockBaseRunner {

def sellSendFileService = Mock(SellSendFileService)

@Override
def init() {
    ...
    citicbankLoanApplyService.sellSendFileService = sellSendFileService
}

}

@Setter //方便单元测试局部mock注入
@Service
public class CiticbankLoanApplyServiceImpl implements SellLoanApplyService
@Autowired
@Qualifier(“citicbankSellSendFileServiceImpl”)
private SellSendFileService sellSendFileService;
}

mock 方法的返回值

citicbankHttpServerTemplate.packageRequest(_) >> response
_指代入参,类似Mockit的any(),
_标识任意个参数

赋值

变量 << 值
变量 << [数组]
变量 << new 对象(property1: value1, property2: value2)

断言

断言值相等 loanApplyRequest.data.applyAmt == 1004.00
其他场景可以转化为类似 表达式 == true

代表方法必须执行一次 1 * sellSendFileService.uploadAndSendFiles()
代表方法至少行一次 (1…
) * sellSendFileService.uploadAndSendFiles(
)
代表方法执行2~5次 (2…5) * sellSendFileService.uploadAndSendFiles(*
)

多行数据比较(CiticLoanApplyMsgConvertServiceImplSpockTest)

假设方法正常运行应该保存了4行数据,需要验证实际入库行数是否4行,各行的列值是否正确


when:
citicLoanApplyMsgConvertService.saveFileList(transNo, loanApplyRequest.data)

List fileInfoList = sellFileInfoMapper.selectList(queryWrapper)
then:
fileInfoList.businessTransNo == [transNo, transNo, transNo, transNo]
fileInfoList.fileCode == [‘QT20200407000002179’, ‘QT20200402000001406’, ‘FM20200407112812’, ‘FM20200402117504’]

一个单测多个用例(data table)使用(CiticbankLoanApplyServiceImplTest)

spock 运行 when和then 的表达式带有变量,具体的变量在运行时通过 where段落提供的数据填充,
例如以下代码提供了where 3个测试用例,每个用例对应where 里面的一行数据,复杂的变量(response)可以通过数组的方式单独赋值余列表方式等效:


when:
citicbankHttpServerTemplate.packageRequest(*_) >> response


then:
uploadFileCount * sellSendFileService.uploadAndSendFiles(*_)
sellApply.getSellState() == sellState

where:
response << [
new Response(//模拟成功
code: CommonConstants.ResponseCode.SUCCESS
),
new Response(//模拟前置接口失败
code: CommonConstants.ResponseCode.FRONT_FAILURE
),
new Response(//模拟中信主动返回失败
code: CommonConstants.ResponseCode.FAILURE,
data: new Response()
)]

sellState | uploadFileCount
Constants.SellState.INIT.getState() | 1
Constants.SellState.INIT.getState() | 1
Constants.SellState.FAIL.getState() | 0

@WebAppConfiguration
@SpringBootTest
class SpockBaseRunner extends Specification {

    @Autowired
    DataSourceTransactionManager dataSourceTransactionManager
    @Autowired
    TransactionDefinition transactionDefinition
    TransactionStatus transactionStatus

    def setup() {
        transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);
        init()
    }

    def init() {

    }

    def cleanup() {
        dataSourceTransactionManager.rollback(transactionStatus)
        clean()
    }

    def clean() {

    }
}

在这里插入图片描述

@VisibleForTesting
protected
也可以使用这个进行单测编写

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值