0


Groovy单元测试

单元测试说明

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@SpringBootTestclassSpockBaseRunnerextendsSpecification{@AutowiredDataSourceTransactionManager dataSourceTransactionManager
    @AutowiredTransactionDefinition transactionDefinition
    TransactionStatus transactionStatus

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

    def init(){}

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

    def clean(){}}
标签: 单元测试 junit

本文转载自: https://blog.csdn.net/QQ563627436/article/details/127124294
版权归原作者 MiMing仔 所有, 如有侵权,请联系我们删除。

“Groovy单元测试”的评论:

还没有评论