soapui 自动化教程(四)
来源:互联网 发布:cf专业刷枪软件 编辑:程序博客网 时间:2024/06/18 10:28
上一节介绍到了使用groovy实现接口自动化测试的基本功能。
本节将介绍groovy执行用例动态参数、参数回传、参数加密、soapui引入第三方jar包、生成随机参数(绕过业务逻辑中的一些唯一校验阻碍自动化),以适应更真实、复杂的场景。
soapui引入第三方jar包
现在JAVA的优势在于JVM平台和累积起来的丰富的第三方资源了,这也是groovy类JVM语言的优势。
将第三方jar包拷贝只<soapui安装目录>/bin/ext
目录下即可。
然后代码中使用import引入:例import customer.RSAUtils
加密
本例使用RSA加密,groovy需要使用java的第三方包,从网上找了个工具类打包到custom.jar,拷贝到soapui扩展目录。
1.生成公私密钥到d盘RSAUtils.generateKeyPair('d:/')
2.文本编辑器打开生成的publicKey.keystore
文件,将三行合并成一行用\n
分隔。例:
PUBLIC_KEY_BASE64 = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCC4d0q2qR7G21TmObv5l0fxpMHcD34pqCjJoIl\nvU/Oa+0rsNkkZljvQAenY8ZNpOPzcfUL/F+qwTpuJh5ny6zl9gPloQRd6PcWob1Z+cuSoEAwBZx4\n+Yw/2QAARjxs5e8GeF0IdY/HK/HmpTCKbmKbUxNaftmeRwfgaG/TGZ93CwIDAQAB"
3.修改任务文件,对账号密码进行加密。
{ "comment": "登录接口 status=1 登录成功。reqName格式为:TestCaseName-TestStepName;expect为期望值;extParams的属性可以动态设置值并覆盖用例的默认参数", "reqName": "TestSuite-login", "extParams": { "account": "rsaEncrypt{lj745280746}", "password": "rsaEncrypt{123456}" }, "expect": { "status": "1" }}
4.修改代码。任务文件新增了rsaEncrypt{<content>}
语法,需要在代码中解析。
//新增方法----------------------------//rsa 加密内容def rsaEncrypt(value) { return RSAUtils.encrypt(RSAUtils.getPublicKey(PUBLIC_KEY_BASE64), value)}//添加扩展参数def addExtParams(params, testStep) { if (!params || params.size() == 0) return params.each {k, v -> testStep.setPropertyValue(k, interpreter(v)) }}//解析替换字符串中的自定义语法def interpreter(v) { def rsaParamPattern = ~/rsaEncrypt\{\s*(.*?)\s*\}/ def rs = v def sb = new StringBuffer() //rsa加密 替换 例:"rsaEncrypt{18600000000}" def mR = rsaParamPattern.matcher(rs) sb.delete(0, sb.length()) while(mR.find()) { mR.appendReplacement(sb, rsaEncrypt(mR.group(1))) } mR.appendTail(sb) rs = sb.toString()}//······//执行任务发送请求前覆盖默认参数({ //加载任务 def taskFile = new File(CURRENT_TESTCASE.properties.task_file.value) def tasks = JSON.parseText(taskFile.getText()) //执行任务 tasks.each { def caseName = it.reqName.split("-")[0] def stepName = it.reqName.split("-")[1] def testStep = TEST_SUITE.getTestCaseByName(caseName).getTestStepByName(stepName) //发送请求前覆盖默认参数 addExtParams(it.extParams, testStep) def testStepContext = new WsdlTestRunContext(testStep) def result = testStep.run(testRunner, testStepContext) log.info "【${caseName}-${stepName} result data】" + result.responseContent def rsJson = JSON.parseText(result.responseContent) if (verifyExcept(rsJson, it.expect)) { log.info "【${it.reqName}】success!" assert true } else { log.error "【${it.reqName}】fail!" assert false } }}())
后台查看请求数据,已经过加密:
参数回传
常有一些场景,接口B接收的参数依赖接口A返回的数据。典型的如打开一个创建页面调用A接口生成唯一的单号No
,录入信息后调用B接口保存数据。此时No
是动态的,必须临时存起来。
本教程举例:testA接口依赖login接口返回的token。修改任务文件,新增cache
属性,表示从返回值中获取cache
属性中的变量名,临时缓存起来,在后续请求中使用cache{<用例名>-<变量名>}
拿到缓存的值。代码中扩展interpreter
函数,使其支持该语法。(此处不贴代码了,在本节结尾提供完整代码。)
生成随机参数
实现该功能是为了解决一些场景会对输入信息做唯一性校验(如手机号),测试数据中如果写死手机号,第一次脚本执行成功,第二次校验接口将返回失败,因为同一个手机在第一次已经存进数据库。
例如testA接口的参数a需要唯一校验,按如下修改,然后扩展interpreter
函数,使其支持该语法(查看本节结尾提供完整代码)。
groovy完整代码及任务文件
现在已基本够用了(我够用了…),如上节所说还有些功能未实现,有兴趣请自行扩展。也许还有些bug。。。
后台接口代码、custom.jar、soapui项目文件,点击下载。
以下是groovy完整代码:
import com.eviware.soapui.impl.wsdl.testcase.WsdlTestRunContextimport groovy.json.JsonSlurperimport customer.RSAUtilsJSON = new JsonSlurper()CURRENT_TESTCASE = testRunner.testCaseTEST_SUITE = CURRENT_TESTCASE.parent//使用JAVA加密 必须导入customer.RSAUtils。customer.jar文件拷贝至目录:<soapui安装目录>\bin\ext 后重启soapuiPUBLIC_KEY_BASE64 = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCC4d0q2qR7G21TmObv5l0fxpMHcD34pqCjJoIl\nvU/Oa+0rsNkkZljvQAenY8ZNpOPzcfUL/F+qwTpuJh5ny6zl9gPloQRd6PcWob1Z+cuSoEAwBZx4\n+Yw/2QAARjxs5e8GeF0IdY/HK/HmpTCKbmKbUxNaftmeRwfgaG/TGZ93CwIDAQAB"//缓存 后续需要回传的值CACHE = [:]//============================================= UTILS =======================================================//递归 深度比较返回值与期望是否匹配def verifyExcept(actual, expect) { def rs = true //类型判断 java Collection 与 Map 不同的遍历方式 if (expect instanceof Collection) { expect.eachWithIndex {it, i -> //log.info "i:" + i + " it:" + it if (it instanceof Map || it instanceof Collection) { return rs = verifyExcept(actual[i], it) } else { def actualValue = actual != null ? actual[i] : null if (actualValue != it) { log.error "expect: $index:${i}=${it} actual:${actualValue}" return rs = false } } } } else if (expect instanceof Map) { expect.each {k, v -> //log.info "k:" + k + " v:" + v if (v instanceof Map || v instanceof Collection) { return rs = verifyExcept(actual[k], v) } else { def actualValue = actual != null ? actual[k] : null if (actualValue != v) { log.error "expect: ${k}=${v} actual:${actualValue}" return rs = false } } } } else { log.error "expect is not Object!" rs = false } return rs}//rsa 加密内容def rsaEncrypt(value) { return RSAUtils.encrypt(RSAUtils.getPublicKey(PUBLIC_KEY_BASE64), value)}//添加扩展参数def addExtParams(params, testStep) { if (!params || params.size() == 0) return params.each {k, v -> testStep.setPropertyValue(k, interpreter(v)) }}//解析替换字符串中的自定义语法 正则替换有疑问请查java APIdef interpreter(v) { def cachePattern = ~/cache\{\s*(.*?)(\[.+\])*\s*\}/ def rsaParamPattern = ~/rsaEncrypt\{\s*(.*?)\s*\}/ def randomPattern = ~/random\{\s*(.*?)\s*\}/ def rs = v def sb = new StringBuffer() def mC = cachePattern.matcher(v) //从缓存中取值 替换 例:"cache{login-token}" while(mC.find()) { if (mC.group(2)) { //使用Eval可以获取多层级的属性值 login-abc['a']['b'] def tmp = Eval.me('CACHE', CACHE, "CACHE['${mC.group(1)}']${mC.group(2)}") mC.appendReplacement(sb, tmp) } else { mC.appendReplacement(sb, CACHE[mC.group(1)]) } } mC.appendTail(sb) rs = sb.toString() //rsa加密 替换 例:"rsaEncrypt{18600000000}" def mR = rsaParamPattern.matcher(rs) sb.delete(0, sb.length()) while(mR.find()) { mR.appendReplacement(sb, rsaEncrypt(mR.group(1))) } mR.appendTail(sb) rs = sb.toString() //生成随机数 替换 例:"soapui自动化-random{1000}" def mRd = randomPattern.matcher(rs) sb.delete(0, sb.length()) while(mRd.find()) { mRd.appendReplacement(sb, Math.round(Math.random() * Integer.valueOf(mRd.group(1))) + "") } mRd.appendTail(sb) rs = sb.toString() return rs}//缓存需要的数据def cacheData(items, rsData, prefix) { if (!items || items.size() == 0) return //默认在请求返回数据中取值 也可先缓存固定值共后续请求使用,使数据保持一致 items.each { if (it instanceof Map) { it.each {k, v -> CACHE["${prefix}-${k}"] = v } } else { CACHE["${prefix}-${it}"] = rsData[it] } }}//============================================= RUN =======================================================({ //加载任务 def taskFile = new File(CURRENT_TESTCASE.properties.task_file.value) def tasks = JSON.parseText(taskFile.getText()) //执行任务 tasks.each { def caseName = it.reqName.split("-")[0] def stepName = it.reqName.split("-")[1] def testStep = TEST_SUITE.getTestCaseByName(caseName).getTestStepByName(stepName) //发送请求前覆盖默认参数 addExtParams(it.extParams, testStep) def testStepContext = new WsdlTestRunContext(testStep) def result = testStep.run(testRunner, testStepContext) log.info "【${caseName}-${stepName} result data】" + result.responseContent def rsJson = JSON.parseText(result.responseContent) if (verifyExcept(rsJson, it.expect)) { cacheData(it.cache, rsJson, stepName) log.info "【${it.reqName}】success!" assert true } else { log.error "【${it.reqName}】fail!" assert false } }}())return
任务文件:
[{ "comment": "登录接口 status=1 登录成功。reqName格式为:TestCaseName-TestStepName;expect为期望值;extParams的属性可以动态设置值并覆盖用例的默认参数", "reqName": "TestSuite-login", "cache": ["token"], "extParams": { "account": "rsaEncrypt{lj745280746}", "password": "rsaEncrypt{123456}" }, "expect": { "status": "1" }},{ "comment": "测试接口A", "reqName": "TestSuite-testA", "extParams": { "token": "cache{login-token}", "a": "test-random{10000}" }, "expect": { "data": { "status": "1" } }},{ "comment": "测试接口B。未插入token,status=0", "reqName": "TestSuite-testB", "expect": { "data": { "status": "0" } }}]
- soapui 自动化教程(四)
- soapui 自动化教程(一)
- soapui 自动化教程(二)
- soapui 自动化教程(三)
- soapui教程
- SoapUI + Groovy 接口自动化
- SoapUI自动化资料整理
- SoapUI接口自动化测试
- 零基础APP自动化测试教程(四)
- 【SoapUI】自动化测试实施方法
- SoapUI 使用教程链接
- soapui的菜鸟教程
- soapui接口性能测试(四)---- 输出报告和统计
- soapui+groovy进行接口自动化测试浅谈
- SoapUI自动化--Groovy脚本常用操作
- soapui + groovy 接口自动化测试 第一章
- soapui + groovy 接口自动化测试 第二章
- SoapUI + Selenium Webdriver 自动化测试学习
- winscp 上传文件发生错误码 4的问题
- C++Primer第五版 7.1.1节练习
- 黑马程序员-------OC----self总结
- Swift下的函数
- [LeetCode-29] Divide Two Integers(两个整数相除,不用乘除取余算术符)
- soapui 自动化教程(四)
- mysql批量插入数据方法
- POJ 1785 Binary Search Heap Construction 笛卡尔树
- Grunt、webpack个人笔记
- 单词缩写
- Java数据结构与算法之数组(一)
- 文章标题
- Cookie的主要属性及Java实现
- Apache POI3.9解析Excel