SoapUI+Groovy 接口自动化测试
谈到接口自动化测试,老牌的 SoapUI 搭配 Groovy 脚本,曾一度成为众多技术团队的标配方案。不过,这套组合的学习曲线确实偏高,尤其对刚入门的新手来说,光是环境搭建与脚本编写就足以让人望而却步。下面就来详细拆解,使用 SoapUI + Groovy 执行自动化测试,究竟需要经历哪些关键步骤。
工程
首先从创建一个工程说起。这是所有操作的起点,后续的测试套件、测试用例以及脚本都挂载在这个工程之下。

新建 TestSuit
TestSuit 即测试套件,它就像一个文件夹,内部可以收纳多个 TestCase(测试用例)。一个测试套件里具体包含多少个用例,完全取决于你的测试场景设计。

新建 TestCase
套件创建完成后,接下来就向其中添加测试用例。这一步非常直观,在刚建好的 TestSuit 上右键选择新建 TestCase 即可。

添加 Groovy 脚本
结构搭建好之后,真正的重头戏就来了——在 TestCase 里添加 Groovy Script 与 Properties。
这个环节需要补充四个 Groovy 脚本:
- Start
- Process
- Check Response
- End
同时还需要准备五个 Properties:
- Input
- Baseline
- Output
- Result
- fieldResult

最终在 TestCase 里看到的层级关系如下所示:

脚本内容
结构搭建完毕后,还需要填充具体的代码。下面这段 Groovy 脚本,是整个自动化流程的核心逻辑。
Start 脚本
Start 脚本主要负责环境初始化工作:处理日期格式、定义日志文件路径、创建目录等。它的任务是在测试开始前把“场地”清理干净,准备好记录测试过程的日志文件。
import ja va.io.*;
def cal = Calendar.instance;
def sysdate = cal.getTime();
def Y = cal.get(Calendar.YEAR);
def M = cal.get(Calendar.MONTH) + 1;
def D = cal.get(Calendar.DATE);
if (D < 10) D = "0" + D;
if (M < 10) M = "0" + M;
date = Y + "-" + M + "-" + D;
time = sysdate.toString().replaceAll(':', '-')
def testSuites = testRunner.testCase.getTestSuite();
def testcase = testRunner.testCase;
def logFolder = new File(context.expand('${#Project#LogFolder}'));
def responseLogName = (context.expand('${#Project#LogFolder}') + date + '\' + testSuites.name + ' - ' + testcase.name + ' - ' + time + ".log");
def responseDetailLogName = (context.expand('${#Project#LogFolder}') + date + '\' + testSuites.name + ' - ' + testcase.name + ' - ' + time + " Response Detail.log");
def responseLogFile = new File(responseLogName);
def responseDetailLogFile = new File(responseDetailLogName);
def subFolder = new File(context.expand('${#Project#LogFolder}') + date + '\')
testcase.setPropertyValue('date', date);
testcase.setPropertyValue('LogFile - Check Response', responseLogName);
testcase.setPropertyValue('LogFile - Response Detail', responseDetailLogName);
if (!logFolder.exists()) {logFolder.mkdirs();}
if (!subFolder.exists()) {subFolder.mkdirs();}
if (!responseLogFile.exists()) {responseLogFile.createNewFile();}
responseLogFile.append("---------------Test Start on " + sysdate + " ------------------------" + '\n');
if (!responseDetailLogFile.exists()) {responseDetailLogFile.createNewFile();}
Process 脚本
Process 脚本是真正的执行中枢。它负责循环读取输入数据、设置属性、发起请求、调用“Check Response”进行校验,并将结果写回到 Excel 或日志文件之中。
result = new Object[2][rows + 3]
result[0][0] = 'Case Description';
result[1][0] = 'Result'
result[0][rows + 1] = 'Start Time:';
result[1][rows + 1] = sysdate.toString();
/*--New object and put the output name and value into this list--*/
output = new Object[baselineSize][rows]
outputTag = new Object[baselineSize][rows]
for (i = 0; i{output[i][0] = Baseline.getPropertyAt(i).name;
outputTag[i][0] = 'PASS';}
for (m = start_Test; m <= end_Test; m++) {
logFile.append('\n' + testcase.name + ": " + m + " " + ". " + sysdate + '\n');
for (i = 0; i{setProperties(input[i][0], input[i][m], inputSheetName)}
for (j = 0; j{setProperties(baseline[j][0], baseline[j][m], baselineSheet)}
testRunner.runTestStepByName(request.name);
Thread.sleep(sleepTime);
testRunner.runTestStepByName("Check Response");
result[0][m] = context.expand('${' + inputSheetName + '#Case Description}')
result[1][m] = context.expand('${' + resultSheet + '#result}')
if (result[1][m] == 'PASS') {passNumbers++;}
for (i = 0; i{output[i][m] = Output.getPropertyAt(i).value;
outputTag[i][m] = fieldResult.getPropertyAt(i).value;}}
result[0][rows + 2] = 'End Time:';
result[1][rows + 2] = sysdate.toString();
result[0][rows] = 'Pass Percentage:';
passPercentage = decFormat.format(passNumbers / (end_Test - start_Test + 1));
result[1][rows] = passPercentage
try {workbook = Workbook.getWorkbook(new File(xlsName));
writableWorkbook = Workbook.createWorkbook(new File(xlsName), workbook);
updateOutput(writableWorkbook, outputSheet, start_Test, end_Test + 1, baselineSize, output, outputTag);
updateResult(writableWorkbook, resultSheet, start_Test, rows + 3, 2, result);
removeSheetByName(writableWorkbook, ComparisonSheet);
if (passPercentage != '100.00%') {updateComparison(writableWorkbook, ComparisonSheet, start_Test, end_Test + 1, baselineSize, output, outputTag, result, baseline);}
writableWorkbook.write();
writableWorkbook.close();
workbook.close();} catch (Exception e) {e.printStackTrace();}
setProperties('passPercentage', passPercentage, 'Result');
testRunner.gotoStepByName('End');
Check Response 脚本
Check Response 是整个流程的校验环节。它会解析接口返回的 XML 响应,提取指定字段的值,然后与 Baseline 中的预期值进行比对。如果匹配则标记为 PASS,否则标记为 FAIL。
import ja va.lang.*;
import ja va.util.*;
import groovy.lang.*;
import groovy.util.*;
import com.eviware.soapui.support.XmlHolder
baselineSheet = "Baseline";
outputSheet = "Output";
resultSheet = "Result";
def testcase = testRunner.testCase;
Baseline = testcase.getTestStepByName(baselineSheet)
baselineSize = Baseline.getPropertyCount();
Output = testcase.getTestStepByName(outputSheet);
def requests = testcase.getTestStepsOfType(com.eviware.soapui.impl.wsdl.teststeps.WsdlTestRequestStep.class)
def request = requests[0];
def response = request.testRequest.response;
respXmlHolder = new XmlHolder(response.contentAsXml)
respXmlHolder.declareNamespace("ns1", "https://schemas.xxx.com/v201203/yourservice")
def statusCode = request.testRequest.response.responseHeaders["#status#"].toString();
def setProperties(Name, Value, Place){name = Name;target = testRunner.testCase.getTestStepByName(Place);target.setPropertyValue(name, Value);}
def getspecifiedValue(field){prefix = "//ns1:";nodePath = "${prefix}${field}"specifiedValue = respXmlHolder.getNodeValue("${nodePath}")}
testRunner.testCase.getTestStepByName(outputSheet).clearPropertyValues();
if (statusCode.contains('200 OK')) {for (i = 1; i{specifiedName = Baseline.getPropertyAt(i).name;specifiedValue = getspecifiedValue(specifiedName);if(specifiedValue != null) {setProperties(specifiedName, specifiedValue, outputSheet)} else {setProperties(specifiedName, '', outputSheet)}}}
else{setProperties(Baseline.getPropertyAt(0).name, Baseline.getPropertyAt(0).value, outputSheet);setProperties(Baseline.getPropertyAt(1).name, statusCode, outputSheet);for (t = 2; tsetProperties(Baseline.getPropertyAt(t).name, '', outputSheet);}}
setProperties(Baseline.getPropertyAt(0).name, Baseline.getPropertyAt(0).value, outputSheet);
setProperties('result', 'PASS', resultSheet);
logFile = new File(context.expand('${#TestCase#LogFile - Check Response}'));
responseDetailLogFile = new File(context.expand('${#TestCase#LogFile - Response Detail}'));
logFile.append(" ------Start Response Check " + " @ " + Calendar.instance.getTime() + "\n");
responseDetailLogFile.append("\n" + testcase.name + " -- " + Baseline.getPropertyAt(0).value + "\n" + response + "\n");
for (i = 0; i{if(Baseline.getPropertyAt(i).value == Output.getPropertyAt(i).value){setProperties(Baseline.getPropertyAt(i).name, 'PASS', 'fieldResult');} else{setProperties(Baseline.getPropertyAt(i).name, 'FAIL', 'fieldResult');setProperties('result', 'FAIL', 'Result');}}
End 脚本
End 脚本的职责是收尾——在日志文件中标记测试结束时间,并追加一条分隔线,便于日后查阅历史记录。
def cal = Calendar.instance;
def sysdate = cal.getTime();
responseLogFile = new File(context.expand('${#TestCase#LogFile - Check Response}'));
responseDetailLogFile= new File(context.expand('${#TestCase#LogFile - Response Detail}'));
responseLogFile.append('\n'+ "---------------Test End on " + sysdate.toString() + " ------------------------"+'\n');
responseDetailLogFile.append('\n'+ "---------------Test End on " + sysdate.toString() + " ------------------------"+'\n');
配置
脚本编写完成后,还需要在 Properties 中配置好相关参数,例如数据源路径、基线文件位置、日志存储目录等。这些配置直接决定了脚本能否正常运行。

运行
所有配置就绪后,直接点击 Run 按钮,整个 TestSuit 就会开始执行自动化测试流程。

运行完成后,界面会展示最终的执行结果:哪些用例通过了,哪些失败了,以及失败的具体字段信息。

结束
不得不承认,这套流程确实相当耗费时间。
从搭建工程、编写脚本、配置参数到最终运行,每一步都离不开 Groovy 代码和手动配置调整。而且,SoapUI 默认不支持中文界面,所有脚本和日志文件都只能用英文处理,这对新手来说门槛极高。尤其当接口返回数据格式复杂、字段较多时,脚本的维护成本也会变得相当可观。
因此,在实际项目中,如果团队规模不大、项目节奏较快,或许可以考虑更轻量化的替代方案。
Apifox 自动化测试
相比之下,Apifox 的自动化测试体验要友好得多。它很好地规避了 SoapUI 的几大痛点:
- 界面默认支持中文,大大降低了上手门槛,无需再面对全英文的菜单而发愁。
- 断言和参数配置均为可视化操作,在接口界面中直接勾选、填写数值,大部分场景下甚至不需要编写脚本。
- 测试用例与测试套件的管理逻辑清晰,支持循环、延迟、环境切换等运行参数,灵活度足够高。
- 测试结果能够细化到单个接口的请求与响应,想分享给团队时,还能直接导出测试报告。
- 额外支持 apifox-cli 命令行方式运行测试用例,将 URL 复制到终端即可触发,无需每次都打开客户端。
接下来看看 Apifox 的自动化测试具体如何操作。
创建接口
首先创建一个接口,填写接口的基本信息——请求方式、路径、参数等。

设置断言
在 Apifox 中设置断言非常直观。直接在“断言”标签页下,通过表单填写你要校验的字段、预期值以及比较规则。

这些都是可视化的操作——填写字段名、选择比较符、输入目标值,完全不需要碰代码。

配置完成后记得点击保存。
新建测试用例
然后切换到“自动化测试”模块,创建一个新的测试用例。

填入名称和所属目录,一个用例就创建好了。

为用例新增接口
用例创建完成后,可以导入之前创建好的接口。这一步相当于将测试目标与执行条件绑定在一起。

从已有的接口列表中,将目标接口勾选进来即可。

设置参数 运行
运行前可以配置参数,例如设定运行次数——想让接口跑 50 次来验证稳定性,直接填写数字即可。

最后点击运行,就能看到测试结果。整个过程不需要写一行代码,从创建到出结果,几分钟就能完成。

Apifox
官网:https://apifox.com/
Apifox 是一款一体化 API 协作平台,集 API 文档、API 调试、API Mock、API 自动化测试于一体。它最大的价值在于将设计、开发、测试三个环节统一到一个平台上,减少了工具切换和数据不一致的麻烦。个人使用体验不错,尤其适合希望在接口自动化测试上快速落地的团队。

知识扩展:
- 前端自动化测试教程
- 实测!最好用的自动化测试平台
