博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
使用Groovy+Spock构建可配置的订单搜索接口测试用例集
阅读量:4982 次
发布时间:2019-06-12

本文共 8740 字,大约阅读时间需要 29 分钟。

概述

测试是软件成功上线的安全网。基本的测试包含单元测试、接口测试。在 一文中已经讨论了使用GroovySpock编写简洁的单测,本文讲解使用Groovy+Spock来构建订单搜索的接口测试用例集合。

主工程是用Java写的。之所以采用Groovy, 是因为其语法近似Python的简洁,可以方便地构造List, Map 及使用闭包方便地遍历这些容器,可以使用元类方便地访问Java类的私有成员。Groovy 是与 Java 系统集成的好伙伴。

接口测试用例遵循“条件-结果检测”模式,即:给定一系列条件,调用接口返回结果,并对结果进行校验。如果结果与条件有关联,那么结果检测需要将条件与结果关联起来。接口测试用例必须是自动化可重复执行的。

对于订单搜索来说,给定搜索条件 (CA=a, CB=b, CC=c, ......) ,调用订单搜索接口返回的订单列表中,应该校验每一个订单满足 (RA=a, RB=b, RC=c, ......)。

出于对企业源代码的保护,这里业务敏感的信息和代码会略去。 仅保留与 groovy 与 可配置化相关的内容,不影响读者理解其中的技巧。

思路及实现

思路

这里的关键点在于: 如何将订单搜索条件与搜索结果的关联做成可配置化的。 如果需要添加测试用例,只要增加新的配置项,而无需更改测试代码。

订单搜索条件,是设置 OrderSearchParam 的字段,比如 orderTypeDesc, receiverName ; 返回结果 SearchResultModel 包含一个 map[field, value] 和一个 total , map 包含每个订单对应的字段 order_type, receiver_name 。 显然,可以构建一个三元组 basicFieldsSearchConfig: [attributeInOrderSearchParam, returnedFieldName, testValue] 。搜索条件设置 attributeInOrderSearchParam = testValue , 然后从返回结果 map 中取出 returnedFieldName 字段,验证其值为 testValue。 搜索设置条件 attributeInOrderSearchParam = testValue, 用到了 Groovy 元类的能力。

类 OrderSearchParam 中含有订单搜索的各种条件字段。

@Data@ToString(callSuper = true)public class OrderSearchParam extends BaseParam {  private static final long serialVersionUID = 4096875864279002497L;  /** 订单编号 */  private String orderNo;  /**   * 订单类型   *   * 元素取自   * @see xxx.api.constants.OrderType 枚举的 name()   * eg. NORMAL   */  private List
orderTypeDesc; /** 收货人姓名 */ private String receiverName; // ... public OrderSearchParam() {}}

代码实现

import org.junit.Testimport javax.annotation.Resourceclass GeneralOrderSearchServiceTest extends GroovyTest {    @Resource    private GeneralOrderSearchService generalOrderSearchService    @Test    void "testSearchOrderType"() {        expect:        OrderType.values().each {            GeneralOrderSearchParam orderSearchParam = ParamUtil.buildGeneralOrderSearchParam(55)            orderSearchParam.getOrderSearchParam().setOrderTypeDesc([it.name()])            PlainResult
searchResult = generalOrderSearchService.search(orderSearchParam) assertSearchResult(searchResult, 'order_type', it.value, orderSearchParam) } } def basicFieldsSearchConfig = [ ['orderNo', 'order_no', 'E201805072005xxxxxxxxx1'], ['receiverName', 'receiver_name', 'qinshu'], // ... other test cases ] @Test void "testSearchBasicFields"() { expect: basicFieldsSearchConfig.each { commonGeneralOrderSearchTest(it[0], it[1], it[2]) } } void commonGeneralOrderSearchTest(searchParamAttr, returnField, testValue) { GeneralOrderSearchParam orderSearchParam = ParamUtil.buildGeneralOrderSearchParam(55) OrderSearchParam searchParam = orderSearchParam.getOrderSearchParam() searchParam.metaClass.setProperty(searchParam, searchParamAttr, testValue) PlainResult
searchResult = generalOrderSearchService.search(orderSearchParam) assertSearchResult(searchResult, returnField, testValue, orderSearchParam) } void assertSearchResult(searchResult, field, testValue, orderSearchParam) { if (searchResult == null || searchResult.data == null || searchResult.data.total == 0) { println(orderSearchParam) return false } assertSuccess(searchResult) SearchResultModel data = searchResult.getData() // 通常情况下,必须保证搜索结果非空,搜索用例才有意义 assert data.total > 0 data.records.each { rec -> rec.get(field) == testValue } }}

联合搜索

上面的测试用例仅考虑了单个搜索条件的情形。 现在考虑下多个搜索条件。 单个搜索条件,使用了三个元素的列表 [attributeInOrderSearchParam, returnedFieldName, testValue] 来表示;多个搜索条件,尽管可以采用列表的列表,但是不够直观。可以抽象成一个对象。此外,条件值与返回值不一定是相同的,需要分离出来。 单个搜索抽象为类 SingleSearchTestCase:

class SingleSearchTestCase {    String searchParamAttr    String returnField    Object condValue    Object returnValue    SingleSearchTestCase(String searchParamAttr, String returnField, Object condValue, Object returnValue) {        this.searchParamAttr = searchParamAttr        this.returnField = returnField        this.condValue = condValue        this.returnValue = returnValue    }}

同时,搜索条件构建和检测结果,也需要扩展成针对多个搜索条件的。 对于单个搜索条件的测试,为了保持兼容,可以做个适配函数。这样,可以使用 List 来表达联合搜索条件的测试用例 (也可以包装为一个含有 List 实例的 CompositeSearchTestCase 对象),贯穿整个条件设置及结果检测。

def combinedFieldsSearchConfig = [            [new SingleSearchTestCase('receiverName', 'receiver_name', 'qinshu', 'qinshu'),new SingleSearchTestCase('orderTypeDesc', 'order_type', 'NORMAL', 0)]    ]    @Test    void "testCombinedFieldsSearch"() {        expect:        combinedFieldsSearchConfig.each {            commonGeneralOrderSearchTest(it)        }    }    void commonGeneralOrderSearchTest(searchParamAttr, returnField, testValue) {        commonGeneralOrderSearchTest([new SingleSearchTestCase(searchParamAttr, returnField, testValue, testValue)])    }    void assertSearchResult(searchResult, returnField, returnValue, orderSearchParam) {        assertSearchResult(searchResult, [new SingleSearchTestCase('', returnField, '', returnValue)], orderSearchParam)    }    void commonGeneralOrderSearchTest(List
testCase) { GeneralOrderSearchParam orderSearchParam = ParamUtil.buildGeneralOrderSearchParam(55) OrderSearchParam searchParam = orderSearchParam.getOrderSearchParam() testCase.each { def searchParamAttrType = searchParam.metaClass.getMetaProperty(it.searchParamAttr).type def condValue = searchParamAttrType.equals(List.class) ? [it.condValue] : it.condValue searchParam.metaClass.setProperty(searchParam, it.searchParamAttr, condValue) } PlainResult
searchResult = generalOrderSearchService.search(orderSearchParam) assertSearchResult(searchResult, testCase, orderSearchParam) } void assertSearchResult(searchResult, List
testCase, orderSearchParam) { if (searchResult == null || searchResult.data == null) { throw new AssertionError("searchParam: " + JSON.toJSONString(orderSearchParam)) } if (searchResult.data.total == 0) { appendFile(GlobalConstants.resultFile, "[NotGood]testCase: " + JSON.toJSONString(orderSearchParam)) assertSuccess(searchResult) return true } assertSuccess(searchResult) SearchResultModel data = searchResult.getData() // 通常情况下,必须保证搜索结果非空,搜索用例才有意义 appendFile(GlobalConstants.resultFile, "[Normal]testCase: " + JSON.toJSONString(orderSearchParam) + " total: " + data.total) assert data.total > 0 data.records.each { rec -> testCase.each { tc -> rec.get(tc.returnField) == tc.returnValue } } }

集成Spock

在 一文中,见识了Spock简洁优雅的语法。是否可以在接口测试用例里也使用Spock的优点呢? 只要简单的三步即可。

引入依赖

要在Spring工程中引入Spock依赖,并访问Spring注入的Bean,需要同时引入 groovy-all, spock-core, spock-spring, spring-test 。如下所示。 低版本的spring-test 可能不支持。

org.codehaus.groovy
groovy-all
2.4.7
org.spockframework
spock-core
1.1-groovy-2.4
test
org.spockframework
spock-spring
1.1-groovy-2.4
test
org.springframework
spring-test
4.3.9.RELEASE
test

启动类

启动类只要注明 @ContextConfiguration 的 location 即可,不需引入 RunWith。

@ContextConfiguration(locations = "classpath:applicationContext.xml")class GroovyTest extends Specification {    @Test    void "testEmpty" () {    }}

使用Spock

使用Spock语法可将 testSearchBasicFields 改写为:

@Unroll    @Test    void "testSearchBasicFields(#attr,#retField,#testValue)"() {        expect:        commonGeneralOrderSearchTest(attr, retField, testValue)        where:        attr                     | retField                   | testValue        'orderNo'             | 'order_no'                | 'E201805072005xxxxxxxxx1'        'receiverName'     | 'receiver_name'        | 'qinshu'    }

是不是更清晰明了了 ?

Groovy元类

测试用例代码中,使用了 Groovy元类 metaClass 。 可以看做是 Java 反射机制的语法形式的简化。 比如可以使用 getProperty 或 setProperty 设置对象属性的值; 可以使用 getMetaProperty 获取对象属性的类型,可以使用 invokeMethod 调用对象的方法等。

setProperty/getProperty 方法在配置化地构造参数对象时很有用;getMetaProperty 在根据实例成员的类型进行判断并采取某种行为时很有用; invokeMethod 方法在java私有方法的单测中很有用。

groovy元编程的介绍可参阅文章:

小结

本文讲解了使用Groovy来构建订单搜索的接口测试用例集合,并介绍了 groovy 元类的用法。 读完本文,是否对 Groovy 的使用有了更深入的了解呢?

转载于:https://www.cnblogs.com/lovesqcc/p/9536578.html

你可能感兴趣的文章
Sql Server2000分页存储过程
查看>>
quagga源码分析--通用库thread
查看>>
TJU_SCS_软件测试_homework1——《error impressed me most》
查看>>
样例功能截图
查看>>
log4j2 Filter用法详解
查看>>
关于sql当中的isnull和ifnull的区别
查看>>
08-语言入门-08-5个数求最值
查看>>
mysql常见知识点总结
查看>>
网站添加百度影音的方法
查看>>
Comparsion in JavaScript
查看>>
【转】ubuntu磁盘状态查看(转)--脱离鼠标操作
查看>>
hdu 1237 简单计算器 栈
查看>>
当我们在说微服务治理的时候究竟在说什么
查看>>
CAS(Compare And Swap)
查看>>
JAVA中String类以及常量池和常用方法
查看>>
java
查看>>
Oracle 数据库导入、导出
查看>>
批量修改 表结构
查看>>
MySQL的btree索引和hash索引的区别
查看>>
抽象类和接口有什么区别
查看>>