背景###

“Groovy元编程简明教程” 一文中,简明地介绍了 Groovy 元编程的特性。 那么,元编程可以应用哪些场合呢?元编程通常可以用来自动生成一些相似的模板代码。

“使用Groovy+Spock构建可配置的订单搜索接口测试用例集” 一文中,谈到了如何将搜索接口的测试用例配置化。 不过,那还只是初级配置化, 含有浓浓的 Java 对象味了, 测试代码与测试用例集合的配置实际上并没有分离,整个测试方法看起来不够清晰。 那么,用元编程的方法,会是怎样呢 ?

自动生成方法###

首先,来看一个简单的例子。 这个例子使用了“闭包”、“静态生成”、“动态生成”三种方式来自动生成方法、注入并执行。 如下代码所示:

代码清单一: AutoGeneratingMethods.groovy

class AutoGeneratingMethods {

    def can(skill) {
return { ->
println "i can $skill"
}
} def canAdvanced(skill) {
AutoGeneratingMethods.metaClass."$skill" = { ->
println "i can $skill advanced."
}
} static void main(args) {
def agm = new AutoGeneratingMethods()
def skills = ['swim', 'piano', 'writing']
skills.each {
agm.can(it)()
}
println("using closure: class methods: " + AutoGeneratingMethods.metaClass.methods.collect { it.name })
println("using closure: object methods: " + agm.metaClass.methods.collect { it.name }) def agm2 = new AutoGeneratingMethods()
def newNewSkills = ['rocking', 'travel', 'climbing']
newNewSkills.each {
def thisSkill = it
agm2.metaClass."$it" = { ->
println "i can $thisSkill dynamically"
}
agm2."$it"()
} println("use object injecting: class methods: " + AutoGeneratingMethods.metaClass.methods.collect { it.name })
println("use object injecting: object methods: " + agm2.metaClass.methods.collect { it.name }) def agm3 = new AutoGeneratingMethods()
def newSkills = ['dance', 'drawing', 'thinking']
newSkills.each {
agm3.canAdvanced(it)()
} println("using class method injecting: class methods: " + AutoGeneratingMethods.metaClass.methods.collect { it.name })
println("using class method injecting: object methods: " + agm3.metaClass.methods.collect { it.name }) }
}

第一种方法中,会将 skill 绑定到闭包内,实际上会有副作用; 第二种方法,是直接在对象上定义新的方法并调用; 第三种方法,是在类 AutoGeneratingMethods 的元类上定义方法并注入,然后运行。 这就是自动生成方法的基本示例了。

关键点:使用 ."$methodName" = { 闭包 } 来动态注入方法。

自动生成测试用例###

首先来看之前的测试代码怎么写的:

public class OldTestCase {

  @TestMethod
String testSearchOrderType() { //conditions: orderTypeDesc = 'someType' eg. NORMAL
//return validations: order_type = 'value for someType' eg. 0 for each order def orderTypeMap = ["NORMAL" :0,
"GROUP" :10] getFinalResult orderTypeMap.collect {
orderTypeDesc, returnValue ->
GeneralOrderSearchParam orderSearchParam = ParamUtil.
buildGeneralOrderSearchParam(kdtId)
orderSearchParam.getOrderSearchParam().setOrderTypeDesc([orderTypeDesc])
PlainResult<SearchResultModel> searchResult = generalOrderSearchService.
search(orderSearchParam)
assertSearchResult(searchResult, 'order_type', returnValue, orderSearchParam)
} } @TestMethod
String testSearchOrderState() { //conditions: stateDesc = 'someState' eg. TOPAY
//return validations: state = 'value for someState' eg. 1 for each order def orderStateMap = ["TOPAY" :1,
"SUCCESS":100] getFinalResult orderStateMap.collect {
orderState, returnValue ->
GeneralOrderSearchParam orderSearchParam = ParamUtil.
buildGeneralOrderSearchParam(kdtId)
orderSearchParam.getOrderSearchParam().setStateDesc([orderState])
PlainResult<SearchResultModel> searchResult = generalOrderSearchService.
search(orderSearchParam)
assertSearchResult(searchResult, 'state', returnValue, orderSearchParam)
} } @TestMethod
String testCombinedFieldsSearch() { //conditions: recName = qin && orderTypeDesc = NORMAL
//return validations: rec_name = 'qin' , order_type = 0 for each order def compositeSearch = [new SingleSearchTestCase('recName', 'rec_name', 'qin',
'qin'), new SingleSearchTestCase(
'orderTypeDesc', 'order_type',
'NORMAL', 0)]
commonGeneralOrderSearchTest(new CompositeSearchTestCase(compositeSearch))
return GlobalConstants.SUCCESS }
}

可见, 原来的写法,1. 没有将测试数据(枚举)和测试代码分离;2. 不同的测试入参要写相似的模板代码,不够通用。

定义测试用例元数据####

怎么写法能够“一统天下”呢 ?

仔细来分析下测试用例, 它包含如下两个要素:

  • 入参: 参数名称 和 参数值; 入参可以包含多个参数对。
  • 校验: 返回字段 和 校验值; 校验也可以包含多个基本字段校验。

只要将这两部分配置化即可。 于是,可以定义测试用例元数据结构如下:

define meta structure of test case :
{
params: {
'searchCondField1': searchValue1,
'searchCondField2': searchValue2,
'searchCondFieldN': searchValueN,
},
validations: {
'validationField1': value1,
'validationField2': value2,
'validationFieldN': valueN,
} }

解析这个元数据,获得入参对和校验对,然后根据两者来编写测试代码:

代码清单二: AutoGeneratingTestsPlain.groovy

import com.alibaba.fastjson.JSON
import groovy.util.logging.Log @Log
class AutoGeneratingTestsPlain { def static generateTest(testCase) { def orderSearchParam = new OrderSearchParam()
testCase.params.each { pprop, pvalue ->
orderSearchParam."$pprop" = pvalue
}
log.info(JSON.toJSONString(orderSearchParam))
def result = mockSearch(orderSearchParam)
assert result.code == 200
assert result.msg == 'success'
result.orders.each { order ->
testCase.validations.each { vdField, vdValue ->
assert order."$vdField" == vdValue
}
}
log.info("test passed.")
} static void main(args) {
AutoGeneratingTestsPlain.generateTest(
[
params: [
'orderTypeDesc': ['NORMAL'],
'recName': 'qin'
],
validations: [
'order_type': 0,
'rec_name': 'qin'
]
]
)
} def static mockSearch(orderSearchParam) {
def results = new Expando(msg: 'success' , code: 200)
results.orders = (1..20).collect {
new Expando(order_type:0 , rec_name: 'qin')
}
results
} }

AutoGeneratingTestsPlain.generateTest 展示了新的写法。 这个测试代码流程可以说非常清晰了。设置入参,调用接口,校验返回,一气呵成。

不过,这个方法是写死的,如果我要定义新的测试用例,就不得不编写新的测试方法。 可以将这里面的测试方法体,抽离出来,变成一个动态方法注入。

动态生成测试方法####

如下代码所示。 将原来的测试方法体抽离出来,变成 AutoGeneratingTestsUsingMetap 的元类的动态方法注入,然后调用运行。这样,就可以根据不同的测试用例数据,生成对应的测试方法,然后注入和运行。 是不是更加灵活了?

注意到,与上面不一样的是,这里每一个测试用例都会生成一个单独的测试方法,有一个独有的测试方法名称。而上面的例子,只有一个 generateTest 用来执行测试用例逻辑。

代码清单三: AutoGeneratingTestsUsingMetap.groovy


import com.alibaba.fastjson.JSON
import groovy.util.logging.Log @Log
class AutoGeneratingTestsUsingMetap { def static generateTests(testCases) {
testCases.each {
generateTest(it)
}
} def static generateTest(testCase) { def testMethodName = "test${testCase.params.collect { "$it.key = $it.value" }.join('_')}" AutoGeneratingTestsUsingMetap.metaClass."$testMethodName" = { tdata -> def orderSearchParam = new OrderSearchParam()
tdata.params.each { pprop, pvalue ->
orderSearchParam."$pprop" = pvalue
}
log.info(JSON.toJSONString(orderSearchParam))
def result = mockSearch(orderSearchParam)
assert result.code == 200
assert result.msg == 'success'
result.orders.each { order ->
tdata.validations.each { vdField, vdValue ->
assert order."$vdField" == vdValue
}
}
log.info("test passed.")
}(testCase) println(AutoGeneratingTestsUsingMetap.metaClass.methods.collect{ it.name })
} static void main(args) {
AutoGeneratingTestsUsingMetap.generateTest(
[
params: [
'orderTypeDesc': ['NORMAL'],
'recName': 'qin'
],
validations: [
'order_type': 0,
'rec_name': 'qin'
]
]
)
} def static mockSearch(orderSearchParam) {
def results = new Expando(msg: 'success' , code: 200)
results.orders = (1..20).collect {
new Expando(order_type:0 , rec_name: 'qin')
}
results
} }

动态生成测试用例####

手动编写测试用例会很枯燥。可以根据具体的测试配置值,自动生成测试用例。比如说,有一个 orderType 的枚举配置, ["NORMAL" :0, "GROUP" :10], 完整的可以定义为:['condField':'orderTypeDesc', 'validationField': 'order_type', 'valuePair': ["NORMAL" :0,"GROUP" :10]] 可以写个方法来生成指定的测试用例数据,做个结构转换即可。

代码清单四:AutoGeneratingTestData.groovy


class AutoGeneratingTestData { def static orderTypeTestData = ['condField':'orderTypeDesc', 'validationField': 'order_type',
'valuePair': [["NORMAL"] :0,
["GROUP"] :10]] def static stateTestData = ['condField':'stateDesc', 'validationField': 'state',
'valuePair': ["TOPAY" :1,
"SUCCESS":100]] def static generateAllTestCases(testDatas) {
testDatas.collect {
generateTestCases(it)
}.flatten()
} def static generateTestCases(testData) { testData.valuePair.collect { key, value ->
def searchCondField = testData['condField']
def validationField = testData['validationField'] return [
params: [
"$searchCondField": key
],
validations: [
"$validationField": value
]
]
}
} static void main(args) {
println AutoGeneratingTestData.generateTestCases(orderTypeTestData)
println AutoGeneratingTestData.generateTestCases(stateTestData)
}
}

接下来,可以把所有这些衔接起来:

代码清单五:PutingAllTogether.groovy


class PutingAllTogether { static void main(args) {
def testDatas = AutoGeneratingTestData.declaredFields.grep { it.name.endsWith('TestData') }.collect { it.get(AutoGeneratingTestData.class) } def testCases = AutoGeneratingTestData.generateAllTestCases(testDatas)
AutoGeneratingTestsUsingMetap.generateTests(testCases)
}
}

现在,只要在 AutoGeneratingTestData 添加以 TestData 结尾的测试数据静态变量, 就可以自动生成测试用例集合,并自动执行自动生成的测试方法啦。

遗留的问题:复合搜索条件的测试用例怎么自动生成呢 ? 这个就留给读者去思考啦!

小结###

本文通过元编程的方法,重新思考和自动构造了订单搜索接口的测试用例集合,并使之更加清晰、灵活可配置。要应用元编程,定义清晰的元数据结构,是非常必要的基础工作。元编程实质上就是基于元数据做一些自动的类、方法、变量注入。

当需要编写一些相似的重复代码时,不妨先定义一些元数据结构和应用模板,并基于此来自动生成相关的代码。此外,从不同的思维视角来看待同一件事物是有益的。

Groovy元编程应用之自动生成订单搜索接口测试用例集的更多相关文章

  1. 使用Groovy+Spock构建可配置的订单搜索接口测试用例集

    概述 测试是软件成功上线的安全网.基本的测试包含单元测试.接口测试.在 "使用Groovy+Spock轻松写出更简洁的单测" 一文中已经讨论了使用GroovySpock编写简洁的单 ...

  2. Groovy元编程简明教程

    同函数式编程类似,元编程,看上去像一门独派武学. 在 <Ruby元编程>一书中,定义:元编程是运行时操作语言构件的编程能力.其中,语言构件指模块.类.方法.变量等.常用的主要是动态创建和访 ...

  3. Oracle 函数 “自动生成订单号”

    create or replace function get_request_code return varchar2 AS --函数的作用:自动生成订单号 v_mca_no mcode_apply_ ...

  4. iBatis——自动生成DAO层接口提供操作函数(详解)

    iBatis——自动生成DAO层接口提供操作函数(详解) 在使用iBatis进行持久层管理时,发现在使用DAO层的updateByPrimaryKey.updateByPrimaryKeySelect ...

  5. 【Golang】基于录制,自动生成go test接口自动化用例

    背景 之前写过一篇博客,介绍怎么用Python通过解析抓包数据,完成自动化用例的编写.最近这段时间在使用go test,所以就在想能不能也使用代码来生成自动化用例,快速提升测试用例覆盖率.说干就干. ...

  6. 使用Allure+testNG自动生成漂亮强大的测试用例报告

    最近领导让我找一个可以每次打包自动生成测试用例的东西,jenkins或者idea都可以, 最后找到了这个allure,也踩了很多坑,废话不多说!,总结一下: 1 使用原生allure 添加依赖: &l ...

  7. VS2017+WIN10自动生成类、接口的说明(修改类模板的方法)

    微软发布VS2017的时候,我第一时间离线一份专业版,安装到了自己的电脑上,开始体验,但是问题来了,在开发中建立类和接口的时候,说 明注释总要自己写一次,烦!~~于是还是像以前一样改IDE默认的类和接 ...

  8. 使用swagger实现在线api文档自动生成 在线测试api接口

    使用vs nuget包管理工具搜索Swashbuckle 然后安装便可 注释依赖于vs生成的xml注释文件

  9. Ruby元编程:执行某个目录下的全部测试用例

    目前手里有个测试项目各个feature的测试用例都放在对应的子目录下,虽然有自动化测试框架的帮助执行起来很方便,但是偶尔也有需要在本地执行某个feature的全部测试用例集合.因为本人对shell脚本 ...

随机推荐

  1. 并发编程~~~协程~~~greenlet模块, gevent模块

    一 协程 1. 协程: 单线程下的并发,又称微线程,纤程.协程是一种用户态的轻量级线程,即协程是由用户程序自己控制调度的. 并发真正的核心: 切换并且保持状态. 开启协程并发的执行,自己的程序把控着C ...

  2. tomcat启停脚本

    脚本存放目录 /etc/init.d/ #!/bin/bash # description: Tomcat8 Start Stop Restart # processname: tomcat8 # c ...

  3. 今日资源帖-PPT逆袭秘籍72集+2000套模板,太经典了

    好资源不私藏,分享是一种态度 今日给大家分享的是PPT教程和2000套模板 如何让PPT成为你职场的利器 如何让你的PPT更具表现力 2000套模板随便选 PPT视频教程 链接 https://pan ...

  4. 《Web Development with Go》Mangodb插入map,slice,Embedded Documents

    这几个好理解, 更好的实现,再说. package main import ( "fmt" "log" "time" "gopkg ...

  5. 菜鸟刷面试题(三、Redis篇)

    目录: redis是什么?都有哪些使用场景? redis有哪些功能? redis和memecache有什么区别? redis为什么是单线程的? 什么是缓存穿透?怎么解决? redis支持的数据类型有哪 ...

  6. 跨域问题,解决方案-Nginx反向代理

    跨域问题,解决之道 跨域问题,在日常开发过程中,是一个非常熟悉的名词.今天的话题,结合我之前的项目场景,讨论下<跨域问题,解决之道>. 跨域是什么 跨域问题,是由于JavaScript出于 ...

  7. [译]OpenSSL Cookbook

    记录个人学习过程吧,顺便翻译一下.另外,本文并不会包括原连接中的所有内容,仅包括个人在工作中会经常遇到的. 参考:OpenSSL Cookbook 前言 由于协议特性和实现的复杂性,有时很难确定安全服 ...

  8. mysql workbench 报错:Can't analyze file, please try to change encoding type...

    Mysql workbench 导入csv can't analyze file 原因: workbench 识别csv第一行作为column名,column名不能为中文,所以报错.解决方法:csv第 ...

  9. mkdir函数 (创建文件夹函数)

    mkdir函数 #include <stdio.h> int main(){ mkdir("C:\\Users\\admin\\desktop\\test"); ; }

  10. opencv---(腐蚀、膨胀、边缘检测、轮廓检索、凸包、多边形拟合)

    一.腐蚀(Erode) 取符合模板的点, 用区域最小值代替中心位置值(锚点) 作用: 平滑对象边缘.弱化对象之间的连接. opencv 中相关函数:(erode) // C++ /** shape: ...