在现有框架基础上,加上groups字段对case进行分组,例如分成smoke, regression......
适用于只需要对部分case进行测试,而不需要run yaml中所有case的情况。
现有框架: 接口测试框架重构:YAML 用例驱动 + 分层设计将接口测试按职责拆分为数据、驱动、执行、断言、API 封装等独立板块, - 掘金
首先目前用的框架AdminApi只是封装了HTTP请求方法,它本身不是测试类,所以@Test不应该写在这个文件中。
其次AdminTest只有一个统一的 test_admin(Map caseData) 方法,所有用例都通过 YAML 数据进来 → 如果在这个方法上写 @Test(groups = {"smoke"}),那所有 YAML 里的用例都会变成 smoke,失去分组的意义。
所以最合适的做法是:把 group 作为 YAML 用例的一个字段
具体实现如下:
1. YAML 用例文件(新增 groups 字段)
yaml
- case: 注册管理员
method: register
groups: ["smoke"]
data:
username: "${username}"
password: "123456"
icon: "github_icon.png"
email: "${email}"
nickName: "autotest"
note: "auto register admin user"
expected:
code: 200
message: "操作成功"
data.username: "${username}"
data.icon: "github_icon.png"
data.email: "${email}"
data.nickName: "autotest"
data.note: "auto register admin user"
data.status: 1
extract:
registeredAdminId: "data.id"
- case: 管理员登录
method: login
groups: ["smoke", "regression"]
data:
username: "${system.admin.username}"
password: "${system.admin.password}"
expected:
code: 200
message: "操作成功"
extract:
adminToken: "data.token"
- case: 获取用户信息
method: getInfo
groups: ["regression"]
data:
expected:
code: 200
message: "操作成功"
data.username: "${system.admin.username}"
2. YamlDataProvider(改动核心:按 group 过滤)
java
public class YamlDataProvider {
/**
* 读 data/xx.yaml,每个 case 一行。
* yaml 顶层是 list,YamlLoader 返回 Object[][]。
*/
private static final String CASE_FILE = "/data/admin.yaml";
/** 加载 YAML 全部用例 */
@SuppressWarnings("unchecked")
private static List<Map<String, Object>> loadAllCases() {
try (InputStream in = YamlDataProvider.class.getResourceAsStream(CASE_FILE)) {
if (in == null) {
throw new IllegalStateException("找不到用例文件: " + CASE_FILE);
}
return new Yaml().load(in);
} catch (Exception e) {
throw new RuntimeException("加载 YAML 用例失败", e);
}
}
/** 按 group 过滤,返回 Object[][] */
@SuppressWarnings("unchecked")
private static Object[][] filterByGroup(String group) {
return loadAllCases().stream()
.filter(c -> {
Object groups = c.get("groups");
if (groups instanceof List) {
return ((List<String>) groups).contains(group);
}
return false;
})
.map(c -> new Object[]{c})
.toArray(Object[][]::new);
}
/** smoke 组用例 */
@DataProvider(name = "smokeCases")
public static Object[][] smokeCases() {
return filterByGroup("smoke");
}
/** regression 组用例 */
@DataProvider(name = "regressionCases")
public static Object[][] regressionCases() {
return filterByGroup("regression");
}
}
3. AdminTest(拆成 smoke / regression 两个入口)
java
public class AdminTest extends AdminBase {
@Override
protected ApiDispatcher dispatcher() {
return adminApiDispatcher;
}
@BeforeMethod(alwaysRun = true)
public void prepareRuntimeData() {
System.out.println(">>> prepareRuntimeData 执行, instance=" + this.hashCode());
context().clear(); // 每个用例重置,避免串数据
String ts = String.valueOf(System.currentTimeMillis());
context().put("username", "admin_" + ts);
context().put("email", "admin_" + ts + "@test.com");
System.out.println(">>> context after prepare: " + context());
}
/** 冒烟:只跑 YAML 里 groups 含 smoke 的用例 */
@Test(dataProvider = "smokeCases",
dataProviderClass = YamlDataProvider.class,
groups = {"smoke"})
public void test_admin_smoke(Map<String, Object> caseData) {
execute(caseData);
}
/** 回归:只跑 YAML 里 groups 含 regression 的用例 */
@Test(dataProvider = "regressionCases",
dataProviderClass = YamlDataProvider.class,
groups = {"regression"})
public void test_admin_regression(Map<String, Object> caseData) {
execute(caseData);
}
}
4. testng.xml中加上groups
xml
<test name="admin-smoke">
<groups>
<run>
<include name="smoke"/>
</run>
</groups>
<classes>
<class name="test.admin.AdminTest"/>
</classes>
</test>
以上这些可以实现分组跑case了,不过在跑的过程中碰到了两个bug.
变量未定义: email,这个来源是smoke groups的第一个case,注册功能。正常逻辑执行是在register方法执行前从AdminTest的prepareRuntimeData方法中获取email变量的值(@BeforeMethod注解),但是打印日志发现根本没执行prepareRuntimeData()。没执行的原因是testng.xml中配了只跑groups = "smoke"的方法/case,TestNG 的 group 过滤机制对@BeforeMethod也生效 ,而prepareRuntimeData()是@BeforeMethod,它没有指定groups,所以不执行。最后的解决方案是给@BeforeMethod加alwaysRun = true。
java
@BeforeMethod
public void prepareRuntimeData() {
String ts = String.valueOf(System.currentTimeMillis());
context().put("email", "admin_" + ts + "@test.com");
}
dispatcher() is null,在调用AbstractCaseTest中execute方法时用到了dispatcher()出现了这个报错。
java
public abstract class AbstractCaseTest extends ApiBase {
/** 通用执行流程 */
protected void execute(Map<String, Object> caseData) {
// 2. 分发调用
Response resp = dispatcher().dispatch(method, resolvedData);
resp.then().log().all();
实际上正常流程在AdminBase方法初始化时构造了Dispatcher,然后可以在AbstractCaseTest中调用dispatcher(),打印日志发现AdminBase中的init()方法并未被执行。原因和第一个问题一样,TestNG 的 group 过滤机制对 @BeforeClass 也生效 ,而init() 是 @BeforeClass,它没有指定 groups ,所以不执行。最后的解决方案也是给 @BeforeClass 加 alwaysRun = true。
java
public abstract class AdminBase extends AbstractCaseTest {
protected AdminApi adminApi;
protected AdminApiDispatcher adminApiDispatcher;
@BeforeClass
protected void init() {
String token = AuthApi.login();
RequestSpecification authSpec = given()
.basePath("/admin")
.header("Authorization", "Bearer " + token);
RequestSpecification noAuthSpec = given()
.basePath("/admin");
adminApi = new AdminApi(authSpec, noAuthSpec);
adminApiDispatcher = new AdminApiDispatcher(adminApi);
}
}
总结:如果在testng中限定了groups,一定要注意其他加了注解的方法是否必须要执行,对于必须执行的方法可以加上
alwaysRun = true,否则可能出现报错。