本期内容:
什么是接口自动化测试
框架搭建
工程结构
覆盖率统计
测试报告
自动化测试在当前市场上应用非常广泛,主流有接口自动化测试和UI自动化测试。
在此基础上,加上持续集成,就能实现全自动化测试。今天主要和大家聊一聊接口自动化测试,以及我们做的事情。
目前市场上大部分项目,都是前后端分离的项目,由此产生了大量的接口。而接口自动化测试,主要是对接口进行测试。
顾名思义,接口测试是对系统或组件之间的接口进行测试,主要是校验数据的交换,传递和控制管理过程,以及相互逻辑依赖关系。
在分层测试的“金字塔”模型中,接口测试属于第二层服务集成测试范畴。相比UI层(主要是WEB或APP)自动化测试而言,接口自动化测试收益更大,且容易实现,维护成本相对较低,有着更高的投入产出比。
结合公司目前的业务迭代现状,接口自动化测试也成为了我们进行自动化测试的首选。
我们的自动化测试框架,采用的是“Java+TestNG+Maven”的方式来搭建的。
TestNG(Next Generation)是一个测试框架,它受到JUnit和NUnit的启发,而引入了许多新的创新功能,如依赖测试,分组概念,使测试更强大,更容易做到。它旨在涵盖所有类别的测试:单元,功能,端到端,集成等……
Maven是一个项目构建和管理的工具,提供了帮助管理 构建、文档、报告、依赖、scms、发布、分发的方法。可以方便的编译代码、进行依赖管理、管理二进制库等等。Maven的好处在于可以将项目过程规范化、自动化、高效化以及强大的可扩展性。利用Maven自身及其插件还可以获得代码检查报告、单元测试覆盖率、实现持续集成等等。
工程部分: 根据业务接入创建业务module,加一个基础module(包含各个业务数据库操作基类,所有模块的公共类封装)。下面会选取基础和业务模块中比较有代表性的功能进行简要说明。
维护各业务间通用的公共类、工具类、基础参数封装等。如:登录、获取token等底层基础操作。
手机号、验证码均读取各业务module的applications.properties配置文件
//手机号验证码登录的params
public static JSONObject loginparam(String xxx,String xxx) {
Header loginparam = Header.builder()
.phone(xxx)
.authCode(xxx)
.build();
JSONObject paramMap=new JSONObject();
paramMap.put("kl_data", loginparam);
return paramMap;
}
public static String getxxx() {
//获取卖家手机号
String xxx = resourceBundle.getString("xxx");
return xxx
}
我们需要同时在测试环境、预发环境接入自动化工程脚本的执行,所以需要进行环境分离处理。通过启动参数来进行动态获取。
//取域名和端口号
String baseurl ;
if(StringUtils.equals(System.getProperty("spring.profiles.xxx"), "pre")){
baseurl = resourceBundle.getString("pre.url");
}else {
baseurl = resourceBundle.getString("test.url");
}
根据启动参数,匹配获取dev/pre.txt文件
if(StringUtils.equals(System.getProperty("spring.profiles.xxx"),"pre")){
resource = new ClassPathResource("pre.txt");
log.info("-----------------------------pre 环境读取");
}else {
resource = new ClassPathResource("dev.txt");
log.info("-----------------------------dev 环境读取");
}
在case的执行过程中,由于网络、发布、断点等原因可能造成case执行失败,增加重试机制
public class RetryToRunCase implements IRetryAnalyzer{
private static int maxRetryCount = 3;//最大的重跑次数
private int retryCount = 1;
@Override
public boolean retry(ITestResult result) {
if (retryCount <= maxRetryCount) {
String message = "Running retry for '" + result.getName()
+ "' on class " + this.getClass().getName() + " Retrying " + retryCount + " times";
Reporter.setCurrentTestResult(result);
Reporter.log("RunCount=" + (retryCount + 1));
retryCount++;
return true;
}
return false;
}
}
1)配置generatorConfig.xml文件:数据库连接方式,各文件存放位置
2)生成文件:使用mybatis-generator插件
3)配置dataconfig.xml文件:用于业务module中获取数据库连接方式及各mapper文件
以业务为维度,维护该业务下的公共类、工具类、业务参数封装,以及业务接口case的编写。每个包是以开发工程里的kunlun网关的service名为测试包名,便于后期持续的维护和对照。
在一个类里面封装多组业务需要的参数,参数组封装需要考虑复用性,降低接口case代码里面的代码冗余
public class BuyerAftersaleParam {
private static ResourceBundle resourceBundle = ResourceBundle.getBundle("application", Locale.CHINA);
public static Map<String, Object> ApplyParam(Long xxx,Integer xxx){
BuyerAftersale applyParam = BuyerAftersale.builder()
.xxx(xxx)
.xxx(xxx)
.build();
Map ApplyParam = BeanUtil.beanToMap(applyParam);
return ApplyParam;
}
}
1) 接口路径
统一配置,各case脚本中直接获取进行拼接。
String uri = "";
//如果传进来的接口名称正确,就返回相应接口的url
if(name == AfterSaleUrl.xxx){
uri = resourceBundle.getString("xxx.url");
}
2) TestNG注解
上文中有提到TestNG的一个很强大的功能就是注解,注解是一种元数据,能够被脚本编译所识别,起到一定的控制作用。在case的编写过程中,TestNG的注解也给我们提供了很多方便。
3) 断言
我们使用Assert.assertEquals()方法进行断言。
出参断言:断言code和message是最简单的,也可以断言接口response中的其他重要字段。断言code和message可以使用较为灵活的方法,在定义入参时将code和message定义好,在case里,取出response中的code和message与预期的进行比较。
数据断言:通过mybatis进行数据库连接,生成相应的mapper文件,在对应的xml中添加sql,用于查询表中的数据与接口response进行比较。
<select id="selectByOrderId" resultMap="BaseResultMap" parameterType="java.lang.Long" >
select
<include refid="Base_Column_List" />
from aftersale
where orderId = #{orderId,jdbcType=BIGINT}
</select>
case实例如下:
public class xx extends xx<xx> {
private String url ="";
private String token = "";
@BeforeTest(alwaysRun = true)
public void BeforeTest() throws Exception {
setUp();
//拼接URL
url= xx;
//获取token
token = xx;
}
@DataProvider(name = "xxx")
public Object[][] param() {
return DataUtil.getTestValue("xxx");
}
@Test(dataProvider = "xxx", description = "xx",priority = 0,groups = "xx",retryAnalyzer = RetryToRunCase.class )
public void xxxTest(Long xxx,String xxx,int code,String message) throws Exception {
Map<String, String> headermap = BaseParams.getHeaderWithTokenForBusiness(token);
JSONObject paramMap = new JSONObject();
paramMap.put("xxx",xxx);
paramMap.put("xxx",xxx);
JSONObject klMap = new JSONObject();
klMap.put("kl_data",paramMap);
String httpResponse = HttpUtil.sendPostJson(url, klMap, headermap);
List<xx> xx = mapper.xx(xx);
System.out.println("test------" + xx.get(0).getReturnReason());
JSONObject response = new JSONObject(httpResponse);
JSONObject data = new JSONObject(response.get("data"));
Assert.assertEquals(response.get("code"), paramMap.put("code", code).get("code"));
Assert.assertEquals(response.get("message"), paramMap.put("message", message).get("message"));
Assert.assertEquals(data.get("description"),xx.get(0).getReturnReason());
}
}
<?xml version="1.0" encoding="UTF-8"?>
<suite name="测试环境测试脚本集" parallel="null">
<test name="测试环境测试脚本集" preserve-order="true">
<groups>
<run>
<include name = "XX0" /> //这里的groups名是跟测试case方法里面注解的group相同,才能执行该case
<include name = "XX1" />
</run>
<packages>
<package name="com.xxx*"></package>
<package name="com.xxx*"></package>
</packages>
</groups>
<listeners>
<listener class-name="org.uncommons.reportng.HTMLReporter" />
<listener class-name="org.uncommons.reportng.JUnitXMLReporter" />
<listener class-name="com.xxx.test.xxx.common.TestReport"/>
</listeners>
</test>
</suite>
以上我们的测试工程已经搭建完成,且以业务维度进行了自动化case的编写及维护。在这些基建工作做完之后,我们还需要一些结果输出来衡量我们的case是否正确且有效,于是我们接入了覆盖率统计及测试报告的自定义优化。
设计测试用例的时候,我们要考虑程序的逻辑,每个函数的输入与输出,逻辑分支代码的执行。自动化case的执行情况就需要有一定的数据指标来进行衡量,这个时候我们用代码覆盖率的结果来反向检查case覆盖是否充分完整。同时也可以用来衡量测试工作本身的有效性,提升代码质量、测试效率,减少bug的漏出,以此来提高产品的可靠性及稳定性。
我们选用基于jacoco的方式来进行覆盖率统计。jacoco是一个开源的代码覆盖率工具,针对java语言,其使用方法很灵活,可以嵌入到Ant、Maven中。
Jacoco从多种角度对代码进行了分析:
jacoco是通过修改class文件的字节码来进行代码覆盖率统计的。即,在原有class字节码中的指定位置插入探针字节码,形成新的字节码指令流。jacoco的探针实际是一个布尔值,当代码执行到探针位置时,将其置为true,该探针前面的代码会被认为执行过。
jacoco的插桩(插入探针)模式有on-the-fly和offline两种,我们全量采用offline的方式统计。
offline模式:编译时插桩,在测试前先对文件进行插桩,然后生成插过桩的class或jar包,测试插过桩 的class和jar包后,会生成动态覆盖信息到文件,最后统一对覆盖信息进行处理,并生成报告。
大致流程如下:
测试代码可以通过网关指向单台业务机器,保证每次接口接口测试用例能全覆盖
通过ant dump拉取指定的机器的exec文件,再通过ant report 得到测试报告数据
流程图如下:
结果输出如下:
除上述的覆盖率统计外,我们还接入了有效接口的覆盖率统计。
有效接口的范围定义为,grafana上统计到有调用量的接口,且为App端调用接口。将我们case覆盖到的接口list与拉到的有效接口list进行对照,输出有效接口的覆盖率。
TestNG自带的测试报告在美观度、功能自定义上相对较差,我们便选用了ReportNG来自定义生成测试报告。可以更加清晰的看到case的执行情况,失败、跳过、成功数量,及耗时等数据。
在testng.xml中添加listener:
报告输出结果如下:
我们做接口自动化的目的还在于,提升测试工作效率,减少冗余投入,保障业务质量。在自动化case维护到一定的数量之后,我们将接口自动化执行接入到了发布系统,在每次代码发布之后自动执行,并将结果发送至钉钉群内,便于测试和开发同学更加直观、及时的查看结果,发现问题,解决问题。
以上就是目前我们已经做的一些事情,还有很多需要继续完善、优化、提升的部分,我们也在持续不断的学习当中~