苍穹外卖接口测试项目(基于 Java+TestNG+OkHttp)搭建与维护指南
苍穹外卖接口测试项目(基于 Java+TestNG+OkHttp)搭建与维护指南
前言
本文记录苍穹外卖接口测试项目从「环境准备→项目搭建→问题排查→Gitee 上传→后续扩展」的完整流程,适用于接口测试新手,可直接复用代码并扩展其他接口测试(如员工管理、订单管理等)。
1. 项目背景与目标
1.1 背景
基于「苍穹外卖」后端服务,实现接口自动化测试,验证核心业务流程(如员工登录、退出、订单查询等)的正确性。
1.2 技术栈
- 开发语言:Java 21(兼容 Java 8+)
- 构建工具:Maven 3.6+
- 测试框架:TestNG(用例管理、断言、执行)
- 网络请求:OkHttp(发送 HTTP 请求,Swagger 生成的 ApiClient 底层依赖)
- 配置管理:YAML(存储环境配置、账号密码等)
- 数据解析:Gson(JSON 序列化/反序列化)
- 依赖管理:Lombok(简化实体类代码)
- 版本控制:Git + Gitee(代码托管与维护)
1.3 核心目标
- 实现员工登录接口的正向/反向测试;
- 支持多环境配置(开发、测试、生产);
- 代码可复用、易扩展(新增接口测试无需重复写基础代码);
- 代码托管到 Gitee,方便团队协作与维护。
2. 环境准备(必做)
2.1 基础环境
| 工具 | 版本要求 | 下载地址 |
|---|---|---|
| JDK | 8+(本文用 21) | Oracle JDK |
| Maven | 3.6+ | Maven 官网 |
| IDEA | 2021+ | IDEA 官网 |
| Git | 任意稳定版 | Git 官网 |
| Gitee 账号 | 注册即可 | Gitee 官网 |
2.2 环境配置验证
打开命令行执行以下命令,无报错则配置成功:
java -version # 显示 JDK 版本
mvn -v # 显示 Maven 版本
git --version # 显示 Git 版本
3. 项目搭建步骤(从零开始)
3.1 步骤 1:创建 Maven 项目
- 打开 IDEA → 「New Project」→ 选择「Maven」→ 取消勾选「Create from archetype」→ 「Next」;
- 填写项目信息:
- Name:
sky-takeout-test(项目名称); - Location:
E:\code\sky-takeout-test(项目存储路径); - GroupId:
com.sky.test(包名前缀); - ArtifactId:
sky-takeout-test; - Version:
1.0-SNAPSHOT;
- Name:
- 点击「Finish」,等待 Maven 初始化完成(右下角进度条消失)。
3.2 步骤 2:引入依赖(修改 pom.xml)
在 pom.xml 中添加以下依赖,刷新 Maven 下载(IDEA 右侧「Maven」→ 「Reload All Maven Projects」):
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.sky.test</groupId>
<artifactId>sky-takeout-test</artifactId>
<version>1.0-SNAPSHOT</version>
<!-- 依赖管理 -->
<dependencies>
<!-- TestNG 测试框架 -->
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>7.10.2</version>
<scope>test</scope>
</dependency>
<!-- OkHttp 网络请求 -->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.12.0</version>
</dependency>
<!-- Gson JSON 解析 -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
</dependency>
<!-- Lombok 简化实体类 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.36</version>
<optional>true</optional>
</dependency>
<!-- SnakeYAML 解析 YAML 配置 -->
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>2.2</version>
</dependency>
<!-- Swagger Codegen 生成 ApiClient(如果用 Swagger 文档) -->
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-codegen-cli</artifactId>
<version>3.0.46</version>
<scope>provided</scope>
</dependency>
</dependencies>
<!-- 构建配置(可选,用于指定 JDK 版本) -->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>21</source>
<target>21</target>
<encoding>UTF-8</encoding>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.36</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
</project>
3.3 步骤 3:编写配置文件(YAML)
-
在
src/main/resources目录下创建 2 个 YAML 文件:application.yml(主配置,指定激活环境);application-dev.yml(开发环境配置,存储接口地址、账号密码)。
-
配置文件内容:
application.yml:spring: profiles: active: dev # 激活开发环境(后续可切换为 test/prod)application-dev.yml:sky: api: baseUrl: http://localhost:8080 # 后端服务地址 adminPrefix: /admin # 管理员接口前缀 login: username: admin # 测试账号 password: 123456 # 测试密码
3.4 步骤 4:编写核心工具类
4.1 配置解析工具:YamlConfigLoader.java
作用:读取 YAML 配置文件,提供全局访问配置的方法。
package com.sky.test.config;
import org.yaml.snakeyaml.Yaml;
import java.io.InputStream;
import java.util.Optional;
public class YamlConfigLoader {
private static Config config;
// 静态代码块:项目启动时加载配置
static {
loadConfig();
}
private static void loadConfig() {
Yaml yaml = new Yaml();
// 1. 读取主配置,获取激活环境
String activeEnv = "dev";
try (InputStream mainStream = YamlConfigLoader.class.getClassLoader().getResourceAsStream("application.yml")) {
if (mainStream == null) throw new RuntimeException("找不到 application.yml 配置文件");
SpringProfiles springProfiles = yaml.loadAs(mainStream, SpringProfiles.class);
if (springProfiles != null && springProfiles.getSpring() != null
&& springProfiles.getSpring().getProfiles() != null) {
activeEnv = springProfiles.getSpring().getProfiles().getActive();
}
} catch (Exception e) {
throw new RuntimeException("读取主配置失败:" + e.getMessage(), e);
}
// 2. 读取激活环境的配置(如 application-dev.yml)
String envConfigFile = "application-" + activeEnv + ".yml";
try (InputStream envStream = YamlConfigLoader.class.getClassLoader().getResourceAsStream(envConfigFile)) {
if (envStream == null) throw new RuntimeException("找不到环境配置文件:" + envConfigFile);
config = yaml.loadAs(envStream, Config.class);
} catch (Exception e) {
throw new RuntimeException("读取" + activeEnv + "环境配置失败:" + e.getMessage(), e);
}
// 3. 校验配置完整性(避免空指针)
validateConfig();
}
private static void validateConfig() {
Optional.ofNullable(config).orElseThrow(() -> new RuntimeException("配置解析失败,未获取到有效数据"));
Optional.ofNullable(config.getSky()).orElseThrow(() -> new RuntimeException("配置缺少 sky 节点"));
Optional.ofNullable(config.getSky().getApi()).orElseThrow(() -> new RuntimeException("配置缺少 sky.api 节点"));
Optional.ofNullable(config.getSky().getApi().getBaseUrl()).orElseThrow(() -> new RuntimeException("配置缺少 sky.api.baseUrl"));
Optional.ofNullable(config.getSky().getLogin()).orElseThrow(() -> new RuntimeException("配置缺少 sky.login 节点"));
}
// 提供外部访问配置的方法
public static Config getConfig() {
return config;
}
// 静态内部类:对应 application.yml 中的 spring 节点
@lombok.Data
public static class SpringProfiles {
private Spring spring;
@lombok.Data
public static class Spring {
private Profiles profiles;
@lombok.Data
public static class Profiles {
private String active;
}
}
}
}
4.2 配置实体类:Config.java
作用:映射 YAML 配置文件的结构(字段名与 YAML 键名一致)。
package com.sky.test.config;
import lombok.Data;
@Data
public class Config {
private Sky sky;
@Data
public static class Sky {
private Api api;
private Login login;
@Data
public static class Api {
private String baseUrl;
private String adminPrefix;
}
@Data
public static class Login {
private String username;
private String password;
}
}
}
3.5 步骤 5:编写测试基类(BaseTest.java)
作用:复用初始化逻辑(加载配置、初始化 ApiClient),所有测试用例都继承此类。
package com.sky.test.cases.base;
import com.sky.test.ApiClient;
import com.sky.test.config.Config;
import com.sky.test.config.YamlConfigLoader;
public class BaseTest {
protected ApiClient apiClient; // Swagger 生成的 ApiClient(或直接用 OkHttpClient)
protected String BASE_URL; // 接口基础地址
protected String ADMIN_PREFIX; // 管理员接口前缀
protected String LOGIN_USERNAME; // 测试账号
protected String LOGIN_PASSWORD; // 测试密码
protected String token; // 登录成功后存储 Token(供后续接口使用)
// TestNG 注解:每个测试类执行前初始化
@org.testng.annotations.BeforeClass
public void initApiClient() {
// 1. 读取配置
Config config = YamlConfigLoader.getConfig();
BASE_URL = config.getSky().getApi().getBaseUrl();
ADMIN_PREFIX = config.getSky().getApi().getAdminPrefix();
LOGIN_USERNAME = config.getSky().getLogin().getUsername();
LOGIN_PASSWORD = config.getSky().getLogin().getPassword();
// 2. 初始化 ApiClient(Swagger 生成,或直接 new OkHttpClient())
apiClient = new ApiClient();
apiClient.setBasePath(BASE_URL + ADMIN_PREFIX); // 拼接完整接口路径
// 3. 添加默认请求头(JSON 接口必需)
apiClient.addDefaultHeader("Content-Type", "application/json");
// 打印初始化信息(验证配置是否正确)
System.out.println("=======================================");
System.out.println("=== 测试套件初始化成功 ===");
System.out.println("激活环境:dev");
System.out.println("完整接口路径:" + BASE_URL + ADMIN_PREFIX);
System.out.println("登录用户名:" + LOGIN_USERNAME);
System.out.println("=======================================");
}
}
3.6 步骤 6:编写测试用例(员工登录)
6.1 实体类(对应接口请求/响应格式)
- 请求实体:EmployeeLoginDTO.java(登录请求参数)
package com.sky.test.api.model; import lombok.Data; @Data public class EmployeeLoginDTO { private String username; // 与后端接口请求字段名一致 private String password; // 与后端接口请求字段名一致 } - 响应实体:ResultEmployeeLoginVO.java(登录响应结果)
package com.sky.test.api.model; import lombok.Data; @Data public class ResultEmployeeLoginVO { private Integer code; // 响应码(1=成功,0=失败) private String msg; // 提示信息 private EmployeeData data; // 成功后返回的用户信息(包含 Token) @Data public static class EmployeeData { private String token; // 登录 Token // 其他字段(如 id、username 等,根据后端返回补充) } }
6.2 测试用例类:EmployeeLoginTest.java
包含正向测试(正确账号密码)和反向测试(错误密码)。
package com.sky.test.cases;
import com.sky.test.api.model.EmployeeLoginDTO;
import com.sky.test.api.model.ResultEmployeeLoginVO;
import com.sky.test.cases.base.BaseTest;
import okhttp3.Call;
import okhttp3.Response;
import org.testng.Assert;
import org.testng.annotations.Test;
import java.util.HashMap;
public class EmployeeLoginTest extends BaseTest {
@Test(description = "员工登录-正向测试:正确账号密码登录成功")
public void testLoginSuccess() throws Exception {
// 1. 构建请求路径(拼接基础路径+接口路径)
String path = "/employee/login";
// 2. 构建请求体(传入测试账号密码)
EmployeeLoginDTO loginDTO = new EmployeeLoginDTO();
loginDTO.setUsername(LOGIN_USERNAME);
loginDTO.setPassword(LOGIN_PASSWORD);
// 3. 调用 ApiClient 构建请求(关键:headerParams 传空 Map,避免空指针)
Call call = apiClient.buildCall(
path,
"POST",
null,
null,
loginDTO,
new HashMap<>(), // 必须传空 Map,不能传 null
null,
new String[]{},
null
);
// 4. 执行请求并获取响应
Response response = call.execute();
String responseBody = response.body().string();
// 5. 解析响应(JSON → Java 实体类)
ResultEmployeeLoginVO loginVO = apiClient.getJSON().deserialize(responseBody, ResultEmployeeLoginVO.class);
// 6. 断言验证(根据后端实际返回调整预期值)
Assert.assertEquals(loginVO.getCode(), 1, "响应码应为 1(成功),实际为:" + loginVO.getCode());
Assert.assertNotNull(loginVO.getData(), "登录成功应返回用户信息(data 不为 null)");
Assert.assertNotNull(loginVO.getData().getToken(), "登录成功应返回 Token");
Assert.assertEquals(loginVO.getMsg(), "员工登录成功", "提示信息应为「员工登录成功」,实际为:" + loginVO.getMsg());
// 7. 存储 Token(供后续接口测试使用,如查询员工列表)
token = loginVO.getData().getToken();
System.out.println("=== 登录成功 ===");
System.out.println("Token:" + token);
System.out.println("响应体:" + responseBody);
}
@Test(description = "员工登录-反向测试:错误密码登录失败", dependsOnMethods = "testLoginSuccess")
public void testLoginFailWithWrongPwd() throws Exception {
String path = "/employee/login";
EmployeeLoginDTO loginDTO = new EmployeeLoginDTO();
loginDTO.setUsername(LOGIN_USERNAME);
loginDTO.setPassword("wrong123456"); // 错误密码
// 构建并执行请求
Call call = apiClient.buildCall(
path, "POST", null, null, loginDTO,
new HashMap<>(), null, new String[]{}, null
);
Response response = call.execute();
String responseBody = response.body().string();
ResultEmployeeLoginVO loginVO = apiClient.getJSON().deserialize(responseBody, ResultEmployeeLoginVO.class);
// 断言失败结果
Assert.assertEquals(loginVO.getCode(), 0, "响应码应为 0(失败),实际为:" + loginVO.getCode());
Assert.assertNull(loginVO.getData(), "登录失败不应返回用户信息(data 为 null)");
Assert.assertTrue(loginVO.getMsg().contains("密码错误"), "提示信息应包含「密码错误」,实际为:" + loginVO.getMsg());
System.out.println("=== 错误密码登录测试通过 ===");
System.out.println("响应体:" + responseBody);
}
}
3.7 步骤 7:执行测试用例
- 确保苍穹外卖后端服务已启动(访问
http://localhost:8080可打开页面); - 右键
EmployeeLoginTest.java→ 「Run ‘EmployeeLoginTest’」; - 查看执行结果:
- 控制台打印「登录成功」和 Token → 正向用例通过;
- 反向用例打印「错误密码登录测试通过」→ 反向用例通过;
- IDEA 底部「Run」面板显示「2 tests passed」→ 全部通过。
4. 常见问题与排查(踩坑记录)
问题 1:YAML 解析报错「Unable to find property ‘base-url’」
- 原因:YAML 键名与 Config 类字段名不一致(如 YAML 用
base-url,Config 用baseUrl); - 解决方案:统一为驼峰命名(YAML 也用
baseUrl),或使用 SnakeYAML 自带注解(复杂,不推荐)。
问题 2:Lombok 报错「NoSuchFieldError: Class com.sun.tools.javac.tree.JCTree$JCImport does not have member field ‘qualid’」
- 原因:Lombok 版本过低,不兼容 JDK 17+;
- 解决方案:升级 Lombok 到 1.18.30+(本文用 1.18.36),并开启 IDEA 注解处理器。
问题 3:headerParams 空指针「Cannot invoke ‘java.util.Map.entrySet()’」
- 原因:调用
apiClient.buildCall()时,第 6 个参数headerParams传了null,ApiClient 遍历时空指针; - 解决方案:传空 Map
new HashMap<>(),而非null。
问题 4:断言失败「提示信息应为’操作成功’,实际为’员工登录成功’」
- 原因:预期提示信息与后端实际返回不一致;
- 解决方案:查看接口实际响应(打印
responseBody),调整断言的预期值。
5. Gitee 仓库维护(代码托管与更新)
5.1 首次上传仓库
- 访问 Gitee 官网,登录账号 → 右上角「+」→ 「新建仓库」;
- 填写仓库信息:仓库名称(如
sky-takeout-test)、可见性(私有/公开),取消勾选「初始化 README」等 → 「创建仓库」; - 本地项目根目录打开终端,执行以下命令:
# 初始化本地 Git 仓库 git init # 添加所有文件到暂存区 git add . # 提交到本地仓库 git commit -m "苍穹外卖测试项目-初始化:员工登录接口测试" # 关联 Gitee 远程仓库(替换为你的仓库 HTTPS 地址) git remote add origin https://gitee.com/你的用户名/sky-takeout-test.git # 推送至 Gitee(首次推送用 -u 绑定分支) git push -u origin master - 输入 Gitee 账号密码/Token,推送成功后刷新 Gitee 仓库即可查看代码。
5.2 后续更新代码(修改/新增功能后)
# 1. 添加修改的文件到暂存区
git add .
# 2. 提交代码(备注清晰修改内容)
git commit -m "新增员工退出接口测试"
# 3. 推送到 Gitee
git push origin master
5.3 仓库分支管理(可选,多人协作时使用)
- 主分支:
master(存储稳定可运行的代码); - 开发分支:
dev(开发新功能时创建,测试通过后合并到master); - 命令示例:
# 创建并切换到 dev 分支 git checkout -b dev # 开发完成后合并到 master git checkout master git merge dev git push origin master
6. 后续扩展思路(项目优化与新增功能)
6.1 新增接口测试(如员工退出、订单查询)
- 新增接口请求/响应实体类(如
EmployeeLogoutDTO、ResultEmployeeLogoutVO); - 新建测试类
EmployeeLogoutTest.java,继承BaseTest; - 复用
token(登录成功后存储的 Token),在请求头中添加Authorization: Bearer ${token}(后端需要 Token 验证时); - 编写测试用例,执行并验证。
6.2 数据驱动测试(多组测试数据)
- 需求:用多组账号密码测试登录(正确账号、空用户名、空密码、错误账号等);
- 实现:使用 TestNG 的
@DataProvider注解,提供多组测试数据:// 示例:数据驱动登录测试 @DataProvider(name = "loginData") public Object[][] loginData() { return new Object[][]{ // {username, password, expectedCode, expectedMsg} {LOGIN_USERNAME, LOGIN_PASSWORD, 1, "员工登录成功"}, // 正确账号密码 {LOGIN_USERNAME, "", 0, "密码不能为空"}, // 空密码 {"", LOGIN_PASSWORD, 0, "用户名不能为空"}, // 空用户名 {"wrongAdmin", "123456", 0, "账号不存在"} // 错误账号 }; } // 引用数据驱动 @Test(description = "员工登录-数据驱动测试", dataProvider = "loginData") public void testLoginDataDriven(String username, String password, int expectedCode, String expectedMsg) throws Exception { // 构建请求体 EmployeeLoginDTO loginDTO = new EmployeeLoginDTO(); loginDTO.setUsername(username); loginDTO.setPassword(password); // 执行请求、解析响应、断言(逻辑复用之前的代码) Call call = apiClient.buildCall(path, "POST", null, null, loginDTO, new HashMap<>(), null, new String[]{}, null); Response response = call.execute(); String responseBody = response.body().string(); ResultEmployeeLoginVO loginVO = apiClient.getJSON().deserialize(responseBody, ResultEmployeeLoginVO.class); Assert.assertEquals(loginVO.getCode(), expectedCode); Assert.assertTrue(loginVO.getMsg().contains(expectedMsg)); }
6.3 生成测试报告
- 需求:执行完测试后,生成可视化报告(展示用例执行结果、通过率等);
- 实现:引入 TestNG 报告插件(如
testng-reportng)或 Allure 报告,配置后执行测试即可生成。
6.4 CI/CD 集成(进阶)
- 需求:提交代码到 Gitee 后,自动执行测试用例,生成报告并通知结果;
- 实现:使用 Gitee CI/CD 或 Jenkins,配置构建脚本(拉取代码、执行
mvn test、生成报告)。
总结
本项目从环境准备到测试用例执行,再到 Gitee 维护,形成了完整的接口自动化测试流程。核心优势是「代码复用性高」(BaseTest 封装初始化逻辑)、「配置灵活」(YAML 支持多环境)、「易扩展」(新增接口测试无需重复写基础代码)。
后续可根据业务需求,逐步扩展其他接口测试(如订单管理、菜品管理等),并通过数据驱动、测试报告、CI/CD 等优化,提升测试效率和项目稳定性。

浙公网安备 33010602011771号