
测试专项 - 接口自动化测试
概述
接口自动化的价值:
- 线上巡检
- 提升测试效率/ 测试左移
- CICD 门禁 / 质量兜底
一条接口自动化的成本 = 编写成本 + 调试成本 + 维护成本。
一条接口自动化的收益 = 线上问题发现 + 测试人日下降 + 测试阶段问题发现
TestNG 编写实践
以 TestNG 为例,一条接口自动化基本包含 数据准备 -> 参数构造 -> 接口请求 -> 断言 -> 数据清理
官方文档:https://testng.org/
testsuite
通常.xml 测试集与自动化任务是一一对应关系,每个测试集里包含不同的 case,适用于不同任务/环境场景,比如线上巡检通常为最核心的集合,测试阶段准出的范围会更大些,线上会屏蔽掉部分用例等等。
Test Groups
可以为每个用例设置分组,例如 P0 P1,然后指定
@DataProvider 数据驱动
优势:测试逻辑与测试数据分离 便于维护;复用性 提升效率
//data.java
//创建
@DataProvider(name = "dataset1")
public Object[][] createData() {
return new Object[][] {
{ "PersonName1", "beijing" },
{ "PersonName2", "shanghai"},
};
}
//case
//使用
@Test(dataProvider = "dataset1")
public void verifyData1(String n1, Integer n2) {
System.out.println(n1 + " " + n2);
}
Interceptors for Data Providers 数据驱动再加工
数据驱动是全场景的参数组合,不同的测试场景拿不同的数据子集,可以用这个
//官方例子
public class SampleDataInterceptor implements IDataProviderInterceptor {
@Override
public Iterator<Object[]> intercept(
Iterator<Object[]> original, IDataProviderMethod dataProviderMethod,
ITestNGMethod method, ITestContext iTestContext) {
// The test method would have custom attributes. From them look for a custom attribute
// whose name is "filter". It's value would the fully qualified class name that
// can be instantiated using reflection and then used to filter the data provider
// provided data set.
Optional<String> found = Arrays.stream(method.getAttributes())
.filter(it -> "filter".equalsIgnoreCase(it.name()))
.flatMap(it -> Arrays.stream(it.values()))
.findFirst();
if (found.isPresent()) {
String clazzName = found.get();
Predicate<Object> predicate = predicate(clazzName);
Spliterator<Object[]> split = Spliterators.spliteratorUnknownSize(original, Spliterator.ORDERED);
return StreamSupport.stream(split,false)
.filter(predicate)
.collect(Collectors.toList())
.iterator();
}
return original;
}
private static Predicate<Object> predicate(String clazzName) {
try {
return (Predicate<Object>) Class.forName(clazzName).getDeclaredConstructor().newInstance();
} catch (Exception e) {
return input -> true;
}
}
}
Parallel 并发执行
<suite name="My suite" parallel="methods" thread-count="5">
......
</suite>
可以让 suite 集合内的用例并发执行
失败自动重试
提升用例稳定性,排除环境不稳定导致的失败(线上慎用)
import org.testng.IRetryAnalyzer;
import org.testng.ITestResult;
public class MyRetry implements IRetryAnalyzer {
private int retryCount = 0;
private static final int maxRetryCount = 3;
@Override
public boolean retry(ITestResult result) {
if (retryCount < maxRetryCount) {
retryCount++;
return true;
}
return false;
}
}
import org.testng.Assert;
import org.testng.annotations.Test;
public class TestclassSample {
@Test(retryAnalyzer = MyRetry.class)
public void test2() {
Assert.fail();
}
}
TestNG Listener 监听器
TestNG 预置了一批 Listener 接口,我们只需添加接口的实现类并加入 testNG 即可在不同的时刻调用到。使用 Listener 可以通过 xml、java 代码或者 serviceLoader 方式实现
IAnnotationTransformer
IResultListener2
IConfigurationListener
IDataProviderListener
IExecutionListener
IExecutionVisualiser
在做接口框架二次改造时十分有用,比如可以上报一些接口/用例的信息,度量相关数据使用,也可以输出通用格式的日志或执行结果
@dependsOnMethods 设置依赖/优先级
如果两个用例有依赖关系,可以使用@dependsOnMethods,比如创建订单->提交支付,需要先下单生成订单号,再用订单号去支付,可以结合 ITestContext 使用(共享数据)。
常用注解
@BeforeSuite
@AfterSuite
@BeforeTest
@AfterTest
@BeforeGroups
@AfterGroups
@BeforeClass
@AfterClass
@BeforeMethod
@AfterMethod
@DataProvider
@Listeners
@Parameters
@Test
自动化遇到的问题
失败归因
自动化程序的健壮性可以避免一些误报,提升整体的稳定性,也降低维护成本
- 环境问题:测试环境不稳定、依赖的接口不稳定、网络波动 => 适当增加重试机制、mock 外部系统
- 并发测试引起的冲突 => 数据/用户隔离,事务(先写后查)
- 数据失效 => 动态数据源、适度复用
- 逻辑/接口变更 => 泛型调用,拉取最新 schema;定期梳理
链路请求
@dependsOnMethods + Context 共享数据,第三方 Mock
写接口
线上尽可能避免带有写操作的 case(除非可以类似代码染色 读写影子数据库,但也不太建议),这超出了自动化职责,同时会引入脏数据。
数据构造
核心数据动态生成,这其中涉及的数据库查询可增加重试次数 保障数据获取正常;非核心数据适度写死,降低程序复杂度。
更有效的断言
- 数据库匹配
- 相关接口匹配
- 核心字段校验
- schema 类型校验
编写策略
框架选型
常用的有 PHPUnit、TestNG、pytest、robotframework 等,最好可以适配公司以及 QA 团队技术栈。
用例范围
维护自动化用例是有成本的,优先核心接口 + 核心场景。
稳定运行后再增加核心接口 + 非核心场景(数据驱动,不额外增加逻辑)以及非核心接口 + 主场景的用例。
降低成本
自动化测试作为提效手段,如果成本 > 收益就得不偿失了
- 编写 + 调试成本:引入被测系统 pom,复用方法与参数;泛型调用 简化代码;模板化
- 维护成本:同「失败归因」
度量指标
- 计划成功率/异常构建率
- 核心用例通过率
- 线上 bug 发现数/占比
- MTTR
- 接口覆盖率
- 行覆盖率