Frontend-App 测试中 Backend-Service 启动/停止机制调研报告
概述
在 zhichai.graph 项目中,frontend-app 的集成测试需要与 backend-service 进行交互。目前项目采用两种策略来处理测试时的 backend-service 依赖:外部服务依赖 和 内置模拟器。本文档调研了现有的实现方式,并总结相关经验。
当前实现方式
1. 外部服务依赖模式
适用场景:简单集成测试,无需复杂业务逻辑模拟
实现特点:
- 测试假设 backend-service 已经运行
- 不主动启动/停止服务
- 依赖外部环境配置
FrontendBackendIntegrationTest.javaLoginLogoutEndToEndTest.javaFileUploadUIIntegrationTest.javaPagingIntegrationTest.java(当前编辑文件)
@SpringBootTest
@ActiveProfiles("test")
class SomeIntegrationTest {
@BeforeEach
void setUp() {
// 仅清理Redis数据
redissonClient.getKeys().flushdb();
}
@AfterEach
void tearDown() {
// 仅清理Redis数据
redissonClient.getKeys().flushdb();
}
}
2. 内置模拟器模式
适用场景:复杂业务流程测试,需要精确控制 backend 响应
实现特点:
- 测试内部启动模拟器线程
- 模拟 backend-service 的命令处理逻辑
- 测试完成后自动停止模拟器
- ComplexMultiStepWorkflowTest.java
- RolePermissionComplexIntegrationTest.java
- ServerMemberManagementComplexIntegrationTest.java
- EndToEndBusinessFlowTest.java
模拟器实现详解
核心架构
@SpringBootTest
@ActiveProfiles({"test", "dispatcher-test"})
class ComplexMultiStepWorkflowTest {
private ExecutorService backendSimulator;
private volatile boolean running = false;
@BeforeEach
void setUp() {
// 初始化ID生成器
startBackendSimulator();
}
@AfterEach
void tearDown() {
stopBackendSimulator();
}
}
启动逻辑
private void startBackendSimulator() {
running = true;
backendSimulator = Executors.newFixedThreadPool(3);
for (int i = 0; i < 3; i++) {
backendSimulator.submit(() -> {
while (running) {
try {
processOneCommand();
Thread.sleep(50); // 控制处理频率
} catch (InterruptedException e) {
break;
} catch (Exception e) {
// 忽略错误继续运行
}
}
});
}
}
命令处理逻辑
private void processOneCommand() throws InterruptedException {
// 从Redis队列获取命令
RBlockingQueue<Command> queue = redissonClient.getBlockingQueue(
RedisKeys.COMMAND_QUEUE_FRONTEND, JsonJacksonCodec.INSTANCE);
Command command = queue.poll(2, TimeUnit.SECONDS);
if (command == null) {
return;
}
// 模拟处理不同类型的命令
Map<String, Object> data = new HashMap<>();
Result.Status status = Result.Status.SUCCESS;
switch (command.getCommandType()) {
case CommandType.USER_REGISTER:
Long userId = nextId(1L);
data.put("userId", userId);
break;
case CommandType.CREATE_SERVER:
// 处理服务器创建逻辑
break;
// ... 其他命令类型
}
// 将结果写入Redis Map
RMap<String, Result> resultMap = redissonClient.getMap(
RedisKeys.COMMAND_RESULT_MAP, JsonJacksonCodec.INSTANCE);
resultMap.put(command.getCommandId(), new Result(status, data));
}
停止逻辑
private void stopBackendSimulator() {
running = false;
if (backendSimulator != null) {
backendSimulator.shutdownNow();
try {
backendSimulator.awaitTermination(3, TimeUnit.SECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
经验总结
优势
1. 测试隔离性:模拟器模式下测试完全独立,不依赖外部服务状态 2. 可控性:可以精确控制 backend 的响应行为和时序 3. 性能:模拟器启动快,无需等待真实服务启动 4. 调试友好:模拟器代码可见,便于调试和修改 5. 并发安全:每个测试可以独立运行,不互相干扰
局限性
1. 维护成本:模拟器需要与真实 backend 逻辑保持同步 2. 覆盖不全:模拟器可能无法完全模拟所有 backend 行为 3. 复杂性:增加了测试代码的复杂度 4. 误导风险:模拟器行为与真实服务不一致可能导致测试通过但生产失败
适用场景建议
| 场景 | 推荐方式 | 理由 |
|---|---|---|
| 简单UI交互测试 | 外部服务依赖 | 减少代码复杂度 |
| 复杂业务流程测试 | 内置模拟器 | 需要精确控制流程 |
| 错误处理测试 | 内置模拟器 | 便于模拟各种错误场景 |
| 性能测试 | 外部服务依赖 | 测试真实性能 |
| 端到端测试 | 外部服务依赖 | 测试完整系统 |
最佳实践
1. 模拟器代码复用:将模拟器逻辑提取为共享工具类 2. 配置化:通过配置文件控制模拟器行为 3. 日志记录:详细记录模拟器处理过程,便于调试 4. 断言验证:不仅验证前端行为,还要验证模拟器收到的命令 5. 定期同步:定期检查模拟器逻辑与真实 backend 是否一致
技术债务
1. 代码重复:4个测试文件都有相似的模拟器实现 2. 维护难度:当 backend 逻辑变化时,需要同时更新多个模拟器 3. 测试覆盖:模拟器可能遗漏某些边界情况
建议改进方案
1. 统一模拟器框架
// 建议创建统一的 BackendSimulator 类
public class BackendSimulator {
private final ExecutorService executor;
private final RedissonClient redissonClient;
private volatile boolean running = false;
public BackendSimulator(RedissonClient redissonClient) {
this.redissonClient = redissonClient;
this.executor = Executors.newFixedThreadPool(3);
}
public void start() { /* 启动逻辑 */ }
public void stop() { /* 停止逻辑 */ }
}
2. 配置驱动的响应
// 通过配置文件定义模拟器行为
@Configuration
public class BackendSimulatorConfig {
@Bean
public Map<CommandType, CommandHandler> commandHandlers() {
// 配置不同命令的处理逻辑
}
}
3. 混合模式
对于某些测试,可以考虑:
- 先尝试连接真实 backend
- 如果连接失败,回退到模拟器模式
- 通过系统属性控制模式切换
结论
当前项目在处理测试中 backend-service 依赖时采用了务实的方法:简单测试使用外部依赖,复杂测试使用内置模拟器。这种混合方式平衡了测试的实用性和维护性。
建议未来考虑重构模拟器代码,提高复用性,并建立同步机制确保模拟器与真实服务的行为一致。