• 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
    @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
    也可以使用这个进行单测编写

  • 相关阅读:
    WindTerm:新一代开源免费的终端工具,GitHub星标6.6k+,太酷了
    足球资讯查询易语言代码
    Kotlin基础——DSL
    【Shell篇<Ⅲ>】——shell函数、字符串的处理
    软件项目管理 5.2.任务分解方法
    ES系列二之常见问题解决
    Fastadmin 子级菜单展开合并,分类父级归纳
    基于 Retina-GAN 的视网膜图像血管分割
    PHP字符串函数的解析
    大龄程序员4000场亚瑟王谈《成人如何学钢琴》
  • 原文地址:https://blog.csdn.net/QQ563627436/article/details/127124294