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"        }    }}]
0 0