将接口测试按职责拆分为数据、驱动、执行、断言、API 封装等独立板块,让项目结构更清晰,每个文件只负责单一职责,避免逻辑杂糅。
- 数据层 :
brand.yaml等用例数据,与代码分离; - 数据提供层 :
YamlDataProvider读取 YAML,转成 TestNG 可用的数据; - 执行层 :
AbstractCaseTest统一"解析 → 调用 → 断言 → 提取"流程,子类只声明差异; - API 封装层 :
BrandApi等封装具体接口请求; - 分发层 :
BrandApiDispatcher按method把用例分发到对应接口; - 断言层 :
ResponseAsserter负责响应校验; - 变量层 :
VariableResolver处理占位符替换,TestContext承载跨用例变量。
一、概括如下:
纯结构关系图

关系说明:
| 关系 | 说明 |
|---|---|
ApiBase → AbstractCaseTest → BrandBase → BrandTest |
继承链,extends 一路往下 |
BrandBase → 构造 BrandApi / BrandApiDispatcher |
BrandBase.init() 里 new |
BrandApiDispatcher → 持有 BrandApi |
构造器注入 |
YamlDataProvider → 读取 brand.yaml |
YamlLoader.load(...) |
YamlDataProvider → 给 BrandTest 提供数据 |
@DataProvider(name="brandCases") |
BrandTest → new 出 AbstractCaseTest 的 context |
基类字段初始化 |
二、详细分析和代码如下:
1. ApiBase - 全局 RestAssured 配置,通过@BeforeSuite注解让整个测试套件只执行一次
java
public class ApiBase {
@BeforeSuite(alwaysRun = true)
public void globalSetup() {
String baseUrl = ConfigReader.getProperty("api.base.url");
// 通用请求规范:baseUri、Content-Type、Accept
RequestSpecification requestSpec = new RequestSpecBuilder()
.setBaseUri(baseUrl)
.addHeader("Content-Type", "application/json")
.addHeader("Accept", "application/json")
.build();
// 通用响应规范:响应时间上限
ResponseSpecification responseSpec = new ResponseSpecBuilder()
.expectResponseTime(Matchers.lessThan(20000L))
.build();
RestAssured.requestSpecification = requestSpec;
RestAssured.responseSpecification = responseSpec;
// 可选:全局日志、编码
RestAssured.enableLoggingOfRequestAndResponseIfValidationFails();
}
}
2. AbstractCaseTest
- 规定通用执行流程:取出 case 的各个字段 --> 做变量替换 --> 根据 method 分发调用不同接口 --> 断言 --> 提取变量供后续case使用
- 继承ApiBase:在这个类中继承ApiBase是因为Java中每个类只能继承一个类,xxBase之前是要继承ApiBase的,现在把通用执行流程抽离出来,xxBase必须继承AbstractCaseTest,所以让AbstractCaseTest继承ApiBase。
java
public abstract class AbstractCaseTest extends ApiBase {
/** 子类提供自己的 dispatcher */
protected abstract ApiDispatcher dispatcher();
/** 每个子类共享的运行时上下文 */
protected final TestContext context = new TestContext();
/** 通用执行流程 */
protected void execute(Map<String, Object> caseData) {
String caseName = (String) caseData.get("case");
String method = (String) caseData.get("method");
@SuppressWarnings("unchecked")
Map<String, Object> data = (Map<String, Object>) caseData.get("data");
@SuppressWarnings("unchecked")
Map<String, Object> expected = (Map<String, Object>) caseData.get("expected");
System.out.println("====== " + caseName + " ======");
// 1. 变量替换
Map<String, Object> resolvedData = VariableResolver.resolve(data, context);
Map<String, Object> resolvedExpected = VariableResolver.resolve(expected, context);
// 2. 分发调用
Response resp = dispatcher().dispatch(method, resolvedData);
resp.then().log().all();
// 3. 断言
ResponseAsserter.assertResponse(resp, resolvedExpected);
// 4. 提取变量供后续 case 使用
extractVariables(caseData, resolvedData, resp);
}
/** 提取变量到 context */
@SuppressWarnings("unchecked")
protected void extractVariables(Map<String, Object> caseData,
Map<String, Object> resolvedData,
Response resp) {
Map<String, String> extract = (Map<String, String>) caseData.get("extract");
if (extract == null) return;
for (Map.Entry<String, String> e : extract.entrySet()) {
String path = e.getValue();
Object v = path.startsWith("req.")
? getByPath(resolvedData, path.substring(4)) // 从请求取
: resp.jsonPath().get(path); // 从响应取
context.put(e.getKey(), v);
}
}
/** 按 a.b.c 路径从嵌套 Map 取值 */
@SuppressWarnings("unchecked")
protected static Object getByPath(Map<String, Object> data, String path) {
if (data == null || path == null || path.isEmpty()) {
return null;
}
Object current = data;
for (String part : path.split("\\.")) {
if (!(current instanceof Map)) {
return null;
}
current = ((Map<String, Object>) current).get(part);
if (current == null) {
return null;
}
}
return current;
}
}
3. BrandBase - 通过@BeforeClass注解在当前测试类的第一个测试方法前执行一次,负责登录拿token + 构造BrandApi/Dispatcher
java
public abstract class BrandBase extends AbstractCaseTest {
protected BrandApi brandApi;
protected BrandApiDispatcher brandApiDispatcher;
@BeforeClass
protected void init() {
String token = AuthApi.login();
RequestSpecification authSpec = given()
.basePath("/brand")
.header("Authorization", "Bearer " + token);
brandApi = new BrandApi(authSpec);
brandApiDispatcher = new BrandApiDispatcher(brandApi);
}
}
4. BrandApi
- 包含品牌模块所有接口定义
- 约定:所有方法签名统一为 Map<String,Object> -> Response,这样 yaml 里的 method 才能通过 Dispatcher 统一调用。
java
public class BrandApi {
private final RequestSpecification authSpec;
public BrandApi(RequestSpecification authSpec) {
this.authSpec = authSpec;
}
/** 创建品牌 */
public Response create(Map<String, Object> data) {
return given().spec(authSpec)
.body(data)
.post("/create");
}
// 其他请求
5. BrandTest
- 实现 AbstractCaseTest 声明的抽象方法dispatcher
- 接收已经加载好的一条 case(一个 Map),是解析这条 case 的字段并执行
java
public class BrandTest extends BrandBase {
// 父类 BrandBase 里已有 brandApiDispatcher 字段
@Override
protected ApiDispatcher dispatcher() {
return brandApiDispatcher;
}
@Test(dataProvider = "brandCases", dataProviderClass = YamlDataProvider.class)
public void test_brand(Map<String, Object> caseData) {
execute(caseData);
}
}
6. BrandApiDispatcher - 把 yaml 里的 method 字符串,映射到 BrandApi 的具体方法
java
public class BrandApiDispatcher implements ApiDispatcher {
private final BrandApi api;
public BrandApiDispatcher(BrandApi api) {
this.api = api;
}
public Response dispatch(String method, Map<String, Object> data) {
switch (method) {
case "create": return api.create(data);
default:
throw new IllegalArgumentException("未知的 method: " + method);
}
}
}
7. YamlDataProvider
- 读 data/brand.yaml,每个 case 一行。
- yaml 顶层是 list,YamlLoader 返回 Object\[\]\[\]。
java
public class YamlDataProvider {
/**
* 读 data/brand.yaml,每个 case 一行。
* yaml 顶层是 list,YamlLoader 返回 Object[][]。
*/
@DataProvider(name = "brandCases")
public static Object[][] brandCases() {
return YamlLoader.load("data/brand.yaml");
}
}
8. brand.yaml文件
- method 对应 BrandApiDispatcher 里的 case
- data 里的 ${xxx} 会被 VariableResolver 替换
- expected 的 key 是 JsonPath,value 是期望值
- extract 把响应字段存进 context,供后续 case 用
yaml
- case: 创建品牌
method: create
data:
name: "海尔_${timestamp}"
firstLetter: "H"
sort: 5
factoryStatus: 0
showStatus: 0
logo: "https://cdn.example.com/brand/haier-logo.png"
bigPic: "https://cdn.example.com/brand/haier-big.jpg"
brandStory: "海尔创立于 1984 年,是全球领先的美好生活解决方案服务商。"
expected:
code: 200
message: "操作成功"
data: 1
extract:
name: "req.name"