前语
本文将凭借 RestAssured 工具,向大家介绍怎么进行 API 测验,然后在团队中敞开接口自动化之路。
本文的示例代码运用的是 Java 言语。虽然本文的首要读者是 Java 研制人员,但道理是相通的,其他言语的研制人员也能从中获益。
What
什么是 API 测验?简略来说,能够认为是针对 Controller 层的测验,但不是 Mock,而是会真实地处理恳求,与数据库或外部服务进行交互。
Why
为什么要做 API 测验呢?
考虑有过这样的场景:
- 加一个新功能,自测没问题,结果被测验人员发现一个旧模块出了问题,感到措手不及
- 后端写好了接口,前端还没开发好界面,所以感觉不便利自测,由于没有界面,只好催前端快去做页面
API 测验便是来解决上述问题的。做 API 测验的原因有:
- 必要性:做回归测验,避免增加新功能时损坏旧功能。
- 便利性:便利本地调试,不必部署到线上,依靠界面去测验。
- 财物化:让测验用例变成财物,与团队同享。
当然,要做好 API 测验,还要接受这样的认知: 接口自动化测验并不仅仅是测验人员工作,研制人员也有职责把它做好。 不然,研制人员难免会觉得这不关我的事, 然后不愿意写这种代码。 主张研制人员从以下便利考虑其好处,进步行动的积极性:
- 减少堵塞,接口自测不再依靠前端
- 进步效率,本地就能自测,不必把使用部署到线上环境
- 进步质量,减少部署到研制环境、前端一调用接口就 500 的状况
为什么不必Postman
Postman 确实是契合直觉的接口调试的第一选项。 但留意,调试不等于测验。
Postman 在实践过程中,最大的问题在于,无法将测验用例有效地财物化:
- 你会在 Postman 里写断语吗?很少吧,你其实是在用肉眼去查看接口成功与否,这实质还是手工测验
- 你的 Postman 数据能与团队同享吗?不能吧,大多数人的 Postman 数据是在本地的,也不会去付费创立一个团队以同享数据
- 你的 Postman 数据在有版别办理吗?没有吧,大多数人的 Postman 数据是与源代码分离的,不利于维护与办理
别的,假如要与 CI 结合,Postman 的数据更适合运用 Node.js 的 Newman。
考虑源代码是 Java,运用 RestAssured,编写 API 测验代码用同一种言语,能够减少运用者的心智担负较轻;而且与源代码放在同一个 Git 仓库中,易于办理。
因而,我仍然会运用 Postman,但更多是把它使用在出现线上问题时,直接仿制一个 cURL 用来复现、排查问题的状况。
装置
下面将介绍怎么用 Maven 装置 RestAssured。
仿制以下内容到 pom.xml 即可。
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<version>5.3.0</version>
<scope>test</scope>
<exclusions>
<exclusion>
<artifactId>json-path</artifactId>
<groupId>io.rest-assured</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>json-path</artifactId>
<version>5.3.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>xml-path</artifactId>
<version>5.3.0</version>
<scope>test</scope>
</dependency>
装置完成后,重启 Spring 容器。
假如装置依靠不成功,能够进行以下查看:
- 显式指定 json-path 与 xml-path 的版别,并扫除其他测验包(如 sping-boot-starter-test) 对 json-path 的依靠
- 声明放在 JUnit 前面
快速上手
语法结构为: given()、when()、then()
given() // 设置恳求信息
.log().body() // 输出恳求日志
.when()
.get() // 发送恳求
.then()
.log().body() // 输出呼应日志
.statusCode(200) // 断语呼应
;
通用设置
以下代码可直接仿制到 Java 测验类中。
private RequestSpecification requestSpec;
// @BeforeEach // JUnit5
@Before // JUnit4
public void init(){
// 假如是本地调试 domain 便是 localhost
RestAssured.baseURI="http://your-domain.com:port/context-path";
// 设置恳求头
RequestSpecBuilder builder=new RequestSpecBuilder();
// 也能够改成调用登录接口,动态获取 token
String token=System.getenv("TOKEN");
builder.addHeader("Authorization",token); // jwt
// 在 give().spec() 中运用即可
requestSpec=builder.build();
}
恳求示例
下面是一个较完好的示例,包含了:
- 设置恳求头
- 设置恳求体
- 设置query
- 判别呼应体的数据结构
@Test
public void test(){
Workflow workflow = new Workflow();
workflow.setWorkflowId(1643167159934930966L);
workflow.setWorkflowName("flow");
List<Workflow> body = new ArrayList<>();
body.add(workflow);
given()
.spec(requestSpec)
.queryParam("query","value")
.body(JSON.toJSONString(body))
.log().body()
.when()
.post("/api/v1/your-api?t=1")
.then()
.log().body()
.statusCode(200)
.assertThat().body("code",equalTo("0"))
;
}
提醒,在运转测验代码前,需求做两件事:
- 必定保证 Web 服务已恳求,由于这不是 Mock,而是会发送真实的恳求。
- 正确装备了环境变量 TOKEN。假如运用 IDEA,能够修改运转装备,在环境变量里注入相似代码:TOKEN=Bearer xxx
接口依靠
有时在恳求接口 B 之前,需求恳求接口 A,所以就产生了接口依靠:B 依靠了 A。
此时能够运用 extract() 及 path() 获取恳求 A 回来的数据。
@Test
public void test(){
// 发送第一个恳求
List<Map<String, String>>workflowList = getWorkflowList();
if (workflowList.isEmpty()) {
System.out.println("workflowList empty, test not execute");
return;
}
// 回来的数据结构是个 Map
// 也能够是 Map<String, Object>,这取决于你实践的数据结构
Map<String, String> target = workflowList.get(0);
WorkflowRunVO workflow = new WorkflowRunVO();
workflow.setWorkflowId(Long.valueOf(target.get("workflowId")));
workflow.setWorkflowName(target.get("name"));
List<WorkflowRunVO> body = new ArrayList<>();
body.add(workflow);
// 在第二个恳求中断语
given()
.spec(requestSpec)
.body(JSON.toJSONString(body))
.log().body()
.when()
.post("/api/v1/workflows")
.then()
.statusCode(200)
.assertThat().body("code",equalTo("0")) // org.hamcrest.Matchers.equalTo
.log().body();
}
private List<Map<String, String>>getWorkflowList(){
return given()
.spec(requestSpec)
.when()
.get("/api/v1/workflows")
.then()
.statusCode(200)
.extract()
.path("payload.content");
}
上传示例
RestAssured 很强壮,还能处理上传与下载的恳求,简直让人“爱了爱了”。 下面是具体的示例:
@Test
public void upload(){
// 需求本地有文件
File file = new File("src/test/fixtures/txt-success");
getImportResp(file)
.assertThat().body("code",org.hamcrest.Matchers.equalTo("0"))
.assertThat().body("payload",equalTo(true))
;
}
private ValidatableResponse getImportResp(File file){
return given()
.spec(requestSpec)
.multiPart(file)
.when()
.post("/api/v1/upload")
.then()
.statusCode(200);
}
假如想在传文件的基础上,还传其他参数,能够这样写:
private ValidatableResponse getImportResp(File file) {
return given()
.spec(requestSpec)
.multiPart("file", file, "application/json")
.multiPart("extraParam", "value")
.when()
.post("/v1/upload")
.then()
.statusCode(200);
}
为对应的前端恳求代码为:
import axios from 'axios';
function getImportResp(file) {
const formData = new FormData();
formData.append('file', file, 'application/json');
formData.append('extraParam', 'value');
return axios.post('/v1/upload', formData)
.then(response => {
return response;
})
.catch(error => {
throw error;
});
}
下载示例
@Test
public void download(){
Map<String, Object> license = getLicenseList().get(0);
if(Objects.isNull(license))return;
// 由于设置的恳求头跟默许的不一样,所以单独设置
RequestSpecBuilder builder = new RequestSpecBuilder();
String token=System.getenv("TOKEN");
builder.addQueryParam("token",token.replace("Bearer ",""));
builder.addHeader("Content-Type","application/json;charset=UTF-8");
requestSpec=builder.build();
String result = given()
.spec(requestSpec)
.log().body()
.when()
.get("/api/v1/download/"+license.get("id"))
.then()
.statusCode(200)
.extract()
.response()
.asString() // 获取输出流打印的字符串
;
System.out.println(result);
Assert.assertEquals(5,result.split("\n").length);
}
看到悉数用例都执行成功,十分爽快!
其他问题
为什么不必 Pytest
假如编码代码的人员是测验人员,那可能首选 Pytest。但本文面向的读者的 Java 研制——既写 API,也写相应的测验代码。故选型理由参阅前面 为什么不必Postman 的答复。
这也是单元测验吗
不是。运转上述测验代码,假如是测验本地接口,需求先在本地启动 Spring 容器;假如是测验线上接口,则需求先把使用部署到线上。因而,这是集成测验。
参阅资料
官方文档:github.com/rest-assure…