基于TestNG+Rest Assured+Allure的接口自动化测试框架
一、前言
当今,“自动化测试”大行其道,其中“接口自动化测试”便是同行们谈得最多的话题之一。了解测试金字塔分层理念的童鞋都清楚,接口自动化测试有以下优点。
- 投入低,产出高。
- 比UI自动化更稳定。
- 比单元测试更接近真实业务。
正因为以上优点,接口自动化测试逐渐成为了业界主流,各种工具/框架层出不穷,比如Postman,Jmeter,Htttpclient,Rest assured,HttpRunnerManager等。
二、背景
此前笔者曾基于Jenkins+Ant+Git+Jmeter搭建过一套接口自动化框架,期间亦针对Jmeter做了许多功能的扩展,比如:生成excle结果文件、数据库断言、自动提交缺陷、自动更新案例执行结果至Testlink等。虽说Jmeter简单易上手,但在笔者看来,其并不是接口自动化测试的首选,其中的原因暂不祥谈,毕竟仁者见仁。
近段时间,笔者一直在思索,学习前辈们优秀的经验,并从公司项目架构出发,搭建了一套基于Jenkins+Maven+Git+TestNG+RestAssured+Allure的持续集成测试框架,相比原先Jmeter的那套,其易用性更好、效率更高、扩展性更强。
三、框架理念

- 根据用例模板编写测试用例(包含响应报文断言及接口入参等)。
- 编写数据库断言文件。
- 编写测试类。
- 配置环境及测试用例集。
- Jenkins选择运行环境及用例集,触发构建。
- 生成测试报告,邮件通知相关人员。
四、操作步骤
1、编写用例

用例文件名称与测试类名一致,比如开户的测试类名为OpenAcc,则用例文件名为OpenAcc.xls,用例模板由以下几部分组成。
- caseNo、testPoint分别与案例管理系统的案例编号、案例标题对应,方便后续同步执行结果。
- preResult为响应报文断言,目前支持jsonpath、包含断言、不包含断言。
- YorN为案例执行标志,Y表示执行。
- tableCheck为数据库断言标志,Y表示进行数据库断言。
- 其他字段为接口入参,目前支持以下五种供数方式。
(1)自定义函数供数。引用格式为:__phone()表示生成11位手机号。__idno()表示生成18位身份证号。
(2)查询数据池供数。引用格式为:${dp.sql(select accountNo from M_account where status = 1)}
(3)查询数据库供数。引用格式为:${db.sql(select accountNo from M_account_card where status = 1)}
(4)先接口请求,然后提取响应报文供数。引用格式为:${SendmsgYg.case023.post($.data.code)},表示先以post方式发送SendmsgYg接口请求,然后再提取响应报文的code字段。支持接口之间的多重依赖。
(5)先接口请求,然后查询数据库/池供数。引用格式为:${SendmsgYg.case023.post.db.sql(select accountNo from M_account_card where status = 1)},表示先以post方式发送SendmsgYg接口请求,然后再查询数据库(db)/数据池(dp)获取数据。
2、编写数据库断言
数据库断言文件名称与测试类名一致,比如开户的测试类名为OpenAcc,则断言文件为OpenAcc.xml。一个接口对应一个数据库断言文件,一个断言文件里可包含多条案例,每条案例可以断言多张表,模板如下。
ps:为了提升效率,后续亦会提供一个生成数据库断言文件小工具。
<?xml version="1.0" encoding="utf-8"?>
<dbCheck dbCheck_name="开户绑卡数据库检查点">
<caseNo case_no="case085"> <!--案例编号-->
<table table_name="M_ACCOUNT"> <!--表名-->
<priKey key_name="ACCOUNT_NO">ACCOUNT_NO</priKey> <!--主键-->
<column column_name="CUST_ID">CUST_ID</column> <!--其他字段的预期结果-->
<column column_name="MERCHANT_ID">MERCHANT_ID</column> <!--其他字段的预期结果-->
<column column_name="ACCOUNT_STATUS">1</column> <!--其他字段的预期结果-->
<column column_name="ORGAN_NO">0019901</column> <!--其他字段的预期结果-->
</table>
</caseNo>
<caseNo case_no="case086">
<table table_name="M_ACCOUNT_CARD">
<priKey key_name="ACCOUNT_NO">ACCOUNT_NO</priKey>
<priKey key_name="CARD_NO">CARD_NO</priKey>
<column column_name="CARD_TYPE">2</column>
<column column_name="MERCHANT_ID">MERCHANT_ID</column>
<column column_name="CUST_ID">CUST_ID</column>
<column column_name="CARD_IMG">CARD_IMG</column>
<column column_name="OPEN_BANKNAME">NOTNULL</column>
</table>
</caseNo>
</dbCheck>
对于未确定的预期结果,使用变量代替,后续编写测试类时再做映射。
3、编写测试类
测试类分为两大类,一是只需响应报文断言,二是需要响应报文及数据库断言。测试人员按照以下模板编写脚本即可。
/*
*短信发送接口
* 环境参数在SetUpTearDown 父类定义
*/
@Feature("分类账户改造")
public class SendmsgYg extends SetUpTearDown {
@Story("发送短信")
@Test(dataProvider = "dataprovider",
dataProviderClass = DataProviders.class,
description = "发送短信")
public void runCase(String caseMess, String bodyString) throws IOException, SQLException, ClassNotFoundException {
//发送请求
Response response = RunCaseJson.runCase(bodyString, "post");
//只进行响应报文断言
asserts(caseMess, bodyString, response.asString(),"",null);
}
}
数据库断言文件中的变量,可通过调用封装的方法取值,比如查数据库、提取响应报文、调用接口等方式。
/*
*开立分类账户
* 环境参数在SetUpTearDown 父类定义
*/
@Feature("分类账户改造")
public class OpenYg extends SetUpTearDown {
@Story("分类账户开户")
@Test(dataProvider = "dataprovider",
dataProviderClass = DataProviders.class,
description = "开户")
public void runCase(String caseMess, String bodyString) throws IOException, SQLException, ClassNotFoundException {
//发送请求
Response response = RunCaseJson.runCase(bodyString, "post");
//如果需要数据库断言,此处添加断言文件变量的map映射
//可通过调用封装的方法取值,比如查数据库、提取响应报文、调用接口等方式。
Map<String, String> map = new HashMap<>();
//查询数据库获取,取不到值返回""
String account = DataBaseCRUD.selectData("select accountNo from M_ACCOUNT where status =1");
//提取响应报文,取不到值返回""
String custId = RespondAssertForJson.getBuildValue(response.asString(),"$.data.custid");
//执行SendmsgYg接口的case023案例,然后提取响应报文的merchanId ,取不到值返回""
String merchanId = RespondAssertForJson.getBuildValue("","${SendmsgYg.case023.post($.data.merchanId)}");
map.put("ACCOUNT_NO",account);
map.put("CUST_ID",custId);
map.put("MERCHANT_ID",merchanId);
//断言(包含响应报文断言和数据库断言)
String xmlFileName = this.getClass().getSimpleName(); //数据库断言xml文件名(与类名保持一致)
asserts(caseMess, bodyString, response.asString(),xmlFileName,map);
}
}
4、用例集
对于多个suite,可通过suite-files配置。testng.xml文件配置如下。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="IIACCOUNT自动化测试" parallel="classes">
<listeners>
<!--失败重跑-->
<listener class-name="com.iiaccount.listener.FailedRetryListener"/>
</listeners>
<test verbose="2" name="IIACCOUNT_YG">
<classes>
<class name="com.iiaccout.yiguan.OpenYg"/>
<class name="com.iiaccout.yiguan.SendmsgYg"/>
</classes>
</test>
</suite>
5、Jenkins构建
选择环境及测试用例集,开始构建,构建完成后生成测试报告及日志。也可根据需要设置定时构建,持续进行质量监控,具体的设置方法在笔者的另一篇文章《基于Jmeter的性能测试框架搭建》改进一有提到。



6、报告分析
在这个注重颜值的世界,allure框架出来的测试报告绝对称得上“报告界的小鲜肉”,笔者在文章《高大上的测试报告-Allure开源框架探索》亦有详细介绍。
测试报告总览包含用例通过率、测试套件、环境、feature、类别、趋势等信息。以下示例截图的案例全部执行失败,所以总览的通过率是0%。

类别主要展现失败的用例信息,可根据项目情况自定制报告内容,比如请求报文、响应报文、断言结果等。


时间刻度展现了每条案例的执行时间。

报告更详细的功能可阅览《高大上的测试报告-Allure开源框架探索》一文。
五、框架实现方案
1、工具/框架
- Jenkins
- Maven
- Gitlab
- TestNG
- Rest Assured
- Allure
2、工程目录



3、pom依赖
支持多环境(sit,uat)切换,结合Jenkins使用。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>HFIIACCOUNT</groupId>
<artifactId>ApiAutoTest</artifactId>
<version>1.0-SNAPSHOT</version>
<!--通过“-D”引用变量-->
<properties>
<aspectj.version>1.8.10</aspectj.version>
<!-- 解决mvn编译乱码问题-->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!--外部传参-->
<xmlFileName></xmlFileName>
</properties>
<dependencies>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>6.11</version>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>ru.yandex.qatools.allure</groupId>
<artifactId>allure-testng-adaptor</artifactId>
<version>1.3.6</version>
<exclusions>
<exclusion>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.qameta.allure</groupId>
<artifactId>allure-testng</artifactId>
<version>2.0-BETA14</version>
</dependency>
<dependency>
<groupId>net.sourceforge.jexcelapi</groupId>
<artifactId>jxl</artifactId>
<version>2.6.12</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.2</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.13</version>
</dependency>
<dependency>
<groupId>com.oracle</groupId>
<artifactId>ojdbc14</artifactId>
<version>10.2.0.4.0</version>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
<filters>
<filter>src/main/filters/filter_${env}.properties</filter>
</filters>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.20</version>
<configuration>
<argLine>
-javaagent:"${settings.localRepository}/org/aspectj/aspectjweaver/${aspectj.version}/aspectjweaver-${aspectj.version}.jar"
</