适合刚开始学 Java 接口自动化的人:先分清 JUnit、RestAssured、Spring 的职责,再把 GET、POST、Header、Body、JSON 断言和 Bean 注入问题串起来。


1. 先分清三个东西

1.1 JUnit:负责运行测试和判断结果

JUnit 是 Java 里常用的测试框架。

它主要负责:

  • 标记测试方法,比如 @Test
  • 运行测试用例
  • 提供断言方法,比如 assertEqualsassertNotNullassertFalse

例如:

assertEquals(200, response.getStatusCode());

意思是:

我期望状态码是 200。如果实际不是 200,这条测试就失败。


1.2 RestAssured:负责发送接口请求

RestAssured 是 Java 里常用的接口测试工具。

它主要负责:

  • 发送 GET / POST / PUT / DELETE 请求
  • 设置 Header
  • 设置 Body
  • 获取 Response
  • 解析 JSON 响应
  • 配合断言验证响应结果

典型写法是:

RestAssured
        .given()
        .when()
        .get(url)
        .then()
        .statusCode(200);

可以理解成:

given:准备请求
when:发送请求
then:验证响应

1.3 Spring:负责管理对象和依赖注入

Spring 不是接口自动化测试必须使用的东西。

它主要负责:

  • 管理 Bean
  • 依赖注入
  • 配置管理
  • 启动 Spring 容器

所以可以先这样记:

JUnit:负责跑测试
RestAssured:负责发接口请求
Spring:负责管理对象和依赖注入

2. 接口自动化一定要用 Spring 吗?

不一定。

如果你只是从接口外部发 HTTP 请求,验证返回结果,通常可以不用 Spring。

一个轻量级接口自动化项目可以这样搭:

JUnit 5 + RestAssured + Allure + 配置文件

什么时候需要 Spring?

如果你要测试 Spring 项目内部的 ServiceRepository,或者想复用项目里的 Bean,就可能需要 Spring 测试上下文。

例如:

@SpringBootTest
public class UserServiceTest {

    @Autowired
    private UserService userService;

    @Test
    public void testUser() {
        userService.getUser();
    }
}

这里 @SpringBootTest 的作用是启动 Spring 测试上下文,让 Spring 帮你创建 Bean、注入 Bean。

以前 Bean 注入失败,很可能是因为:

你以为自己在 Spring 环境里测试,
但实际只是普通 JUnit 测试。

JUnit 本身不负责 Bean 注入。

Spring 容器才负责 Bean 注入。


3. 先准备依赖

如果是 Maven 项目,可以先加入 JUnit 5、RestAssured 和 Jackson。

为了公众号里更容易看清楚,先把依赖拆成表格:

用途 groupId artifactId version scope
JUnit 5 测试框架 org.junit.jupiter junit-jupiter 5.10.2 test
RestAssured 接口测试 io.rest-assured rest-assured 5.4.0 test
JSON 序列化支持 com.fasterxml.jackson.core jackson-databind 2.17.1 test

复制到 pom.xml 里时,可以放在 <dependencies> 下面:

<dependencies>
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter</artifactId>
        <version>5.10.2</version>
        <scope>test</scope>
    </dependency>

    <dependency>
        <groupId>io.rest-assured</groupId>
        <artifactId>rest-assured</artifactId>
        <version>5.4.0</version>
        <scope>test</scope>
    </dependency>

    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.17.1</version>
        <scope>test</scope>
    </dependency>
</dependencies>

补充说明:

RestAssured 可以把 Map 或对象序列化成 JSON,但通常需要项目里有 Jackson、Gson 这类 JSON mapper。简单项目里加上 jackson-databind 最省心。


4. 普通 JUnit 测试怎么写?

如果只是测试普通 Java 方法,不需要 Spring,也不需要 RestAssured。

比如有一个类:

public class Calculator {

    public int add(int a, int b) {
        return a + b;
    }
}

测试可以这样写:

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;

public class CalculatorTest {

    @Test
    public void testAdd() {
        Calculator calculator = new Calculator();

        int result = calculator.add(1, 2);

        assertEquals(3, result);
    }
}

这个结构就是:

准备对象 → 调用方法 → 断言结果

也就是常说的:

Arrange → Act → Assert

5. RestAssured 的 GET 请求怎么写?

比如健康检查接口:

import io.restassured.RestAssured;
import io.restassured.response.Response;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;

public class HealthCheckTest {

    @Test
    public void testHealthCheck() {
        Response response = RestAssured.given()
                .relaxedHTTPSValidation()
                .accept("application/json")
                .when()
                .get("https://localhost:8443/fineract-provider/actuator/health");

        assertEquals(200, response.getStatusCode());

        String status = response.jsonPath().getString("status");
        assertEquals("UP", status);
    }
}

这段代码的意思是:

发送 GET 请求到健康检查接口;
断言 HTTP 状态码是 200;
再断言响应 JSON 里的 status 字段是 UP。

其中:

.relaxedHTTPSValidation()

意思是放宽 HTTPS 证书校验。

本地开发环境经常使用自签名证书,如果不放宽校验,可能会出现 SSL 相关错误。

但注意:

本地测试环境可以用 relaxedHTTPSValidation;
生产环境一般不建议随便跳过证书校验。

6. response.jsonPath().getString(“status”) 是什么意思?

这句:

String status = response.jsonPath().getString("status");

可以拆成三部分:

response:
接口返回的完整响应。

jsonPath():
把响应体当成 JSON 来解析。

getString("status"):
取出 JSON 里的 status 字段,并转成 String。

如果接口返回:

{
  "status": "UP"
}

那么:

response.jsonPath().getString("status")

拿到的就是:

UP

如果 JSON 是嵌套的:

{
  "user": {
    "name": "Lea",
    "age": 18
  }
}

可以这样取:

String name = response.jsonPath().getString("user.name");
int age = response.jsonPath().getInt("user.age");

如果 JSON 里有数组:

{
  "users": [
    {
      "id": 1,
      "name": "Lea"
    },
    {
      "id": 2,
      "name": "Tom"
    }
  ]
}

可以这样取:

String firstName = response.jsonPath().getString("users[0].name");
int secondId = response.jsonPath().getInt("users[1].id");

数组下标从 0 开始。


7. RestAssured 的 POST 请求怎么写?

POST 通常会带请求体。

比如登录接口要传:

{
  "username": "mifos",
  "password": "password"
}

可以用字符串写:

String requestBody = """
        {
            "username": "mifos",
            "password": "password"
        }
        """;

Response response = RestAssured.given()
        .relaxedHTTPSValidation()
        .contentType("application/json")
        .accept("application/json")
        .body(requestBody)
        .when()
        .post("https://localhost:8443/fineract-provider/api/v1/authentication");

核心是:

.contentType("application/json")
.body(requestBody)
.post(url)

意思是:

我要发送 JSON 请求体;
请求体内容是 requestBody;
请求方式是 POST。

补充一下:

Content-Type:告诉服务端,我发过去的请求体是什么格式。
Accept:告诉服务端,我希望拿到什么格式的响应。

8. POST 请求的 Body 可以放 Map 吗?

可以。

简单 JSON 很适合用 Map。

import java.util.HashMap;
import java.util.Map;

Map<String, Object> body = new HashMap<>();
body.put("username", "mifos");
body.put("password", "password");

Response response = RestAssured.given()
        .relaxedHTTPSValidation()
        .contentType("application/json")
        .accept("application/json")
        .body(body)
        .when()
        .post("https://localhost:8443/fineract-provider/api/v1/authentication");

这里:

.body(body)

RestAssured 会把 Map 作为请求体发送。

如果指定:

.contentType("application/json")

通常就会按 JSON 方式序列化并发出去。

对应 JSON 大概是:

{
  "username": "mifos",
  "password": "password"
}

如果是嵌套 JSON,也可以 Map 套 Map。

不过实际项目里,如果请求体很复杂,更推荐:

DTO 实体类
或者读取 JSON 文件

这样比在测试代码里手写一大坨 Map 更清楚。


9. 怎么断言 base64EncodedAuthenticationKey 不为空?

以 Fineract 登录接口为例,响应里可能有:

{
  "base64EncodedAuthenticationKey": "xxxxxx"
}

可以先取出来:

String base64Key = response.jsonPath().getString("base64EncodedAuthenticationKey");

然后断言:

assertNotNull(base64Key);
assertFalse(base64Key.trim().isEmpty());

这里比只写 assertNotNull 更严谨。

因为:

assertNotNull 只能判断不是 null;
但是空字符串 "" 也不是 null。

所以更稳的判断是:

不能为 null;
去掉空格后也不能为空字符串。

需要导入:

import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertFalse;

10. GET 请求怎么加 Authorization Header?

以 Fineract 为例,如果接口要求:

Header:
Authorization = Basic {base64EncodedAuthenticationKey}

RestAssured 可以这样写:

Response response = RestAssured.given()
        .relaxedHTTPSValidation()
        .header("Authorization", "Basic " + base64EncodedAuthenticationKey)
        .when()
        .get("https://localhost:8443/fineract-provider/api/v1/clients");

注意:

"Basic " + base64EncodedAuthenticationKey

Basic 后面有一个空格。

如果接口还要求租户 Header,比如 Fineract 常见的 tenant header,可以加:

.header("Fineract-Platform-TenantId", "default")

完整示例:

Response response = RestAssured.given()
        .relaxedHTTPSValidation()
        .header("Authorization", "Basic " + base64EncodedAuthenticationKey)
        .header("Fineract-Platform-TenantId", "default")
        .when()
        .get("https://localhost:8443/fineract-provider/api/v1/clients");

assertEquals(200, response.getStatusCode());

补充一个容易混的点:

标准 Basic Auth 通常是 base64(username:password)。这里使用 base64EncodedAuthenticationKey 是 Fineract 这类接口返回后的用法,具体项目要以接口文档为准。


11. 登录后再调用接口的完整示例

import io.restassured.RestAssured;
import io.restassured.response.Response;
import org.junit.jupiter.api.Test;

import java.util.HashMap;
import java.util.Map;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;

public class FineractApiTest {

    @Test
    public void testLoginAndGetClients() {
        Map<String, Object> loginBody = new HashMap<>();
        loginBody.put("username", "mifos");
        loginBody.put("password", "password");

        Response loginResponse = RestAssured.given()
                .relaxedHTTPSValidation()
                .contentType("application/json")
                .accept("application/json")
                .body(loginBody)
                .when()
                .post("https://localhost:8443/fineract-provider/api/v1/authentication");

        assertEquals(200, loginResponse.getStatusCode());

        String base64EncodedAuthenticationKey =
                loginResponse.jsonPath().getString("base64EncodedAuthenticationKey");

        assertNotNull(base64EncodedAuthenticationKey);
        assertFalse(base64EncodedAuthenticationKey.trim().isEmpty());

        Response clientsResponse = RestAssured.given()
                .relaxedHTTPSValidation()
                .header("Authorization", "Basic " + base64EncodedAuthenticationKey)
                .header("Fineract-Platform-TenantId", "default")
                .accept("application/json")
                .when()
                .get("https://localhost:8443/fineract-provider/api/v1/clients");

        assertEquals(200, clientsResponse.getStatusCode());
    }
}

这段代码的流程是:

1. 调登录接口
2. 从登录响应里取 base64EncodedAuthenticationKey
3. 判断 key 不为空
4. 把 key 拼到 Authorization Header 里
5. 再调需要认证的 GET 接口
6. 断言状态码是 200

12. Java 访问修饰符:private、protected、同包

Java 里 privateprotectedpublic 是访问修饰符。

它们控制:

这个属性或方法,能被谁访问?

常见规则:

修饰符 本类 同包 子类 其他包非子类
private 可以 不可以 不可以 不可以
默认不写 可以 可以 不可以 不可以
protected 可以 可以 可以 不可以
public 可以 可以 可以 可以

严谨一点说:

protected 在跨包子类场景下有一些访问细节限制。入门阶段先记成“本类、同包、子类可访问”即可,实际写代码时再结合具体继承关系判断。


13. 什么叫同一个包?

看 Java 文件第一行:

package com.lea.demo;

如果两个类的 package 完全一样,就是同一个包。

比如:

package com.lea.demo;

public class User {
}

和:

package com.lea.demo;

public class UserService {
}

它们都是:

package com.lea.demo;

所以是同包。

注意:

package com.lea.demo;

和:

package com.lea.demo.user;

不是同一个包。

com.lea.demo.user 是子包,但 Java 不认为它和 com.lea.demo 是同包。


14. private 怎么理解?

private String name;

意思是:

这个字段只有当前类自己能访问。

外部类不能直接访问。

通常用 private + getter/setter

public class User {

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

这样做叫封装。

核心目的:

不让外部随便改内部数据;
而是通过方法提供一个可控入口。

15. protected 怎么理解?

protected String baseUrl;

意思是:

本类可以用;
同包可以用;
子类可以用。

在测试框架里,protected 经常出现在 BaseTest 里。

比如:

public class BaseApiTest {

    protected String baseUrl = "https://localhost:8443";

    protected String getToken() {
        return "token";
    }
}

子类测试类可以继承使用:

import org.junit.jupiter.api.Test;

public class ClientApiTest extends BaseApiTest {

    @Test
    public void testClient() {
        System.out.println(baseUrl);
        System.out.println(getToken());
    }
}

这种场景用 protected 比较合理:

这些能力不想完全 public;
但希望子类测试类可以复用。

16. 最后串成一条线

今天最重要的点:

  1. JUnit 是测试框架,负责运行测试和断言。
  2. RestAssured 是接口测试工具,负责发 HTTP 请求和解析响应。
  3. Spring 不是接口自动化必须的,只有需要 Bean 注入或复用 Spring 容器时才需要。
  4. 普通 Java 单元测试直接 new 对象即可。
  5. 如果要 @Autowired,测试类通常需要跑在 Spring 测试环境里。
  6. GET 请求用 .get(url)
  7. POST 请求用 .post(url),请求体放 .body(...)
  8. JSON 请求体建议写 .contentType("application/json")
  9. Header 用 .header("key", "value")
  10. Basic Authorization 写法是:
.header("Authorization", "Basic " + base64EncodedAuthenticationKey)
  1. JSON 字段提取写法是:
response.jsonPath().getString("字段名")
  1. 不为空断言推荐写:
assertNotNull(value);
assertFalse(value.trim().isEmpty());

最核心的一句话:

接口自动化测试 = 发请求 → 拿响应 → 提取字段 → 做断言 → 生成报告

把 JUnit、RestAssured、Spring Bean 注入、Header、Body、JSON 断言这几个点串起来之后,接口自动化就不再是一堆零散 API,而是一条清楚的测试流程。

更多推荐