前语

本文将凭借 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);  
}  

看到悉数用例都执行成功,十分爽快!

使用 RestAssured 进行 API 测试

其他问题

为什么不必 Pytest

假如编码代码的人员是测验人员,那可能首选 Pytest。但本文面向的读者的 Java 研制——既写 API,也写相应的测验代码。故选型理由参阅前面 为什么不必Postman 的答复。

这也是单元测验吗

不是。运转上述测验代码,假如是测验本地接口,需求先在本地启动 Spring 容器;假如是测验线上接口,则需求先把使用部署到线上。因而,这是集成测验。

参阅资料

官方文档:github.com/rest-assure…