mirror of
https://gitee.com/pnoker/iot-dc3.git
synced 2026-10-31 18:21:58 +08:00
171 lines
8.9 KiB
Markdown
171 lines
8.9 KiB
Markdown
---
|
||
title: 测试
|
||
---
|
||
|
||
# 测试
|
||
|
||
这页讲清 IoT DC3 后端的测试怎么分层、什么时候必须补测试、用哪条命令跑哪一层。读完你能选对测试类型、在本地把单元/集成/E2E 跑起来,并知道改一处代码该补哪种测试才算"完成"。
|
||
|
||
> 你在这里:已读过[开发概览与规范](./)、想把改动验证到可合并。写驱动的测试要点另见[驱动开发](./driver-authoring)。
|
||
|
||
## 为什么要分层
|
||
|
||
测试不是越重越好。一个断言能在毫秒级捕获的逻辑错误,没必要拉起 PostgreSQL 容器去验;而跨服务的消息契约、时序库的落库行为,靠纯 Mock 又测不出真实问题。IoT DC3 据此把测试分成三层,越往下越快、越多、越孤立,越往上越慢、越少、越接近真实链路:
|
||
|
||
- **单元测试**最多,跑得最快,验证孤立的业务逻辑,不启动 Spring 上下文。
|
||
- **集成测试**用 Testcontainers 拉起真实 PostgreSQL/TimescaleDB、RabbitMQ、MQTT,验证 DAL、gRPC、消息这些跨组件协作。
|
||
- **E2E 测试**最少,验证端到端业务链路(命令下发、事件路由、时序表操作),默认关闭,靠环境变量显式启用。
|
||
|
||
选层的判断很直接:能不碰外部依赖就用单元测试;非要真容器才能复现的(SQL、消息、gRPC 契约)才上集成;只有验证整条链路时才动 E2E。
|
||
|
||
## 三层金字塔与对应命令
|
||
|
||
下图把三层、各自的范围与启动方式、以及触发命令对齐在一起。底层执行器(Surefire/Failsafe)和门禁(`DC3_E2E`)决定了同一次 `mvn` 会跑到哪一层。
|
||
|
||
```mermaid
|
||
flowchart TB
|
||
subgraph E2E["E2E · 最少 · 最慢"]
|
||
E["dc3-e2e<br/>端到端业务链路<br/>命令下发 / 事件路由 / 时序表"]
|
||
end
|
||
subgraph IT["集成 · 中量 · 中速"]
|
||
I["*IT.java(Failsafe 执行)<br/>DAL / gRPC InProcess / 消息契约<br/>真实 PostgreSQL / RabbitMQ / MQTT"]
|
||
end
|
||
subgraph UT["单元 · 最多 · 最快"]
|
||
U["*Test.java(Surefire 执行)<br/>孤立业务逻辑<br/>不启动 Spring 上下文"]
|
||
end
|
||
U -->|"make test"| I
|
||
I -->|"make test-it(需容器运行时)"| E
|
||
E -->|"make test-e2e(DC3_E2E=true + 起栈)"| Done["合并前:make coverage 聚合 JaCoCo"]
|
||
```
|
||
|
||
::: warning 集成与 E2E 需要容器运行时
|
||
`make test-it` 和 `make test-e2e` 都通过 Testcontainers 在运行时拉起 PostgreSQL/TimescaleDB、RabbitMQ 等容器,因此本地必须有可用的容器运行时(podman 或 Docker)。E2E 在此基础上还会在共享 Docker 网络上引导一整套真实依赖。没有容器运行时时,这两条命令会失败而非跳过——`make test`(纯单元)不受影响。
|
||
:::
|
||
|
||
各层的目标、执行器与技术栈对照如下(作参考,细节以上文为准):
|
||
|
||
| 层级 | 目标 | 执行器 / 门禁 | 技术方式 |
|
||
|------|------|---------------|----------|
|
||
| 单元测试 | 快速验证孤立业务逻辑 | Surefire | JUnit 5、Mockito 5、AssertJ;Reactor `StepVerifier` 验证响应式 |
|
||
| 集成测试 | 验证真实基础设施与跨模块协作 | Failsafe;`*IT.java` | Testcontainers、gRPC InProcess、RabbitMQ harness |
|
||
| E2E 测试 | 验证端到端业务链路 | `@EnabledIfEnvironmentVariable(named = "DC3_E2E")` | `dc3-e2e`、Testcontainers(共享 Docker 网络) |
|
||
|
||
## 本地怎么跑
|
||
|
||
后端命令统一走 `iot-dc3/` 下的 Makefile。最常用的四条:
|
||
|
||
```bash
|
||
make test # 单元测试(Surefire)
|
||
make test-it # 集成测试(Failsafe + Testcontainers,需容器运行时)
|
||
make test-e2e # E2E:等价于 DC3_E2E=true mvn -pl dc3-e2e -am -Pe2e verify
|
||
make coverage # 聚合 JaCoCo 报告(dc3-coverage -am verify)
|
||
```
|
||
|
||
`make test-e2e` 本身已设置 `DC3_E2E=true` 并只在 `dc3-e2e` 模块上跑 `-Pe2e`,无需手动导出环境变量。只想跑单个模块或单个用例时,直接用 Maven:
|
||
|
||
```bash
|
||
# 指定模块的单元测试
|
||
mvn -s .mvn/settings.xml test -pl dc3-common/dc3-common-manager
|
||
|
||
# 单个测试类 / 单个方法
|
||
mvn -s .mvn/settings.xml test -pl dc3-common/dc3-common-manager -Dtest=DeviceControllerTest
|
||
mvn -s .mvn/settings.xml test -pl dc3-common/dc3-common-public -Dtest="RTest#testOkWithData"
|
||
```
|
||
|
||
`make coverage` 完成后,聚合报告落在:
|
||
|
||
```text
|
||
dc3-coverage/target/site/jacoco-aggregate/index.html
|
||
```
|
||
|
||
::: info Failsafe 为何指向 outputDirectory
|
||
父 POM 给 Failsafe 配置了 `classesDirectory=${project.build.outputDirectory}`。原因是 Spring Boot 会把模块产物 repackage 成可执行 fat jar,那个 jar 在 Failsafe classpath 上不可直接加载;让集成测试针对未重打包的普通编译类运行,才能正确加载被测类。这是改动驱动等可执行模块的集成测试时需要留意的坑。
|
||
:::
|
||
|
||
## 前端测试命令
|
||
|
||
前端(`iot-dc3-web/`)用 pnpm + Vitest(单元/接口/组件/视图)与 Playwright(E2E),与后端独立:
|
||
|
||
::: code-group
|
||
|
||
```bash [Vitest 分套]
|
||
pnpm test # 全部 Vitest 套件
|
||
pnpm test:unit # tests/unit
|
||
pnpm test:api # tests/api
|
||
pnpm test:component # tests/component
|
||
pnpm test:views # tests/views
|
||
pnpm test:guard # tests/guardrails(AI 编码护栏)
|
||
pnpm test:ci # vitest run --coverage(CI 门禁)
|
||
```
|
||
|
||
```bash [Playwright E2E]
|
||
pnpm test:e2e # headless chromium
|
||
pnpm test:e2e:headed # 可见浏览器(E2E_HEADLESS=false)
|
||
```
|
||
|
||
:::
|
||
|
||
## 什么时候必须补测试
|
||
|
||
核心约定:**改 bug 先写能复现的回归测试,再修。** 把"这个 bug 不再出现"变成一条可执行、会失败的测试,修完它变绿,才算闭环。其余变更按风险补测:
|
||
|
||
| 变更类型 | 要求 |
|
||
|----------|------|
|
||
| Bug 修复 | 先补能复现问题的回归测试,再实现修复 |
|
||
| 新功能 / 行为变化 | 补单元测试,按风险追加集成测试 |
|
||
| 重构 | 保持现有覆盖;隐式契约补 contract 测试 |
|
||
| DAL / SQL 变更 | 补 Testcontainers 测试并运行 `make test-it` |
|
||
| gRPC proto 变更 | 同步更新服务端、客户端与契约测试 |
|
||
| 纯文档 / 格式变更 | 不需要 Java 测试,做文档构建或格式检查即可 |
|
||
|
||
## 可复用的测试基础设施
|
||
|
||
`dc3-common-test` 模块沉淀了跨模块共享的容器与基类,避免每个模块各搭一套。集成测试直接复用这些单例容器和 harness:
|
||
|
||
| 工具 | 用途 |
|
||
|------|------|
|
||
| `PgTimescaleContainer` | 单例 `timescale/timescaledb-ha:pg18` 容器,数据库与时序表测试 |
|
||
| `RabbitContainer` | RabbitMQ 容器,消息发布、确认、消费测试 |
|
||
| `MqttContainer` | MQTT 容器,MQTT 驱动测试 |
|
||
| `GrpcInProcessExtension` | JUnit 5 扩展:每个测试一组 in-process gRPC server + 托管 channel |
|
||
| `RabbitTestHarness` | 测试内收发 RabbitMQ,`awaitTrue()` 基于 Awaitility |
|
||
| `FixedClockConfig` | `@TestConfiguration`,把 `Clock` bean 固定到确定时刻 |
|
||
|
||
两个契约测试基类用于守住横切约定:
|
||
|
||
- `EnumContractTest<E>`:通过 `@TestFactory` 验证枚举 `getIndex()` 唯一、`ofIndex()` 往返、常量名稳定。
|
||
- `SecretFieldContractTest`:验证 `apiKey`、`password`、`secret`、`token` 等敏感字段不从 `@ToString` 与序列化中泄漏。
|
||
|
||
::: tip 时间、随机数与等待
|
||
注入 `java.time.Clock`,不要直接 `LocalDateTime.now()`;需要固定时间用 `FixedClockConfig`。异步等待用 Awaitility,禁止裸用 `Thread.sleep`。WebFlux 用 `WebTestClient`,不用 `MockMvc`;gRPC 用 in-process channel,不打开真实 socket。
|
||
:::
|
||
|
||
## 覆盖率门禁
|
||
|
||
`make coverage` 聚合各模块的 JaCoCo 数据,由 `dc3-coverage/pom.xml` 的门槛把关:
|
||
|
||
| 指标 | 当前门槛 |
|
||
|------|----------|
|
||
| 行覆盖率 | `coverage.line.minimum = 0.20`(20%) |
|
||
| 分支覆盖率 | `coverage.branch.minimum = 0.15`(15%) |
|
||
|
||
门槛当前相对克制,便于测试体系扩展期持续推进;判定只看静态最低阈值——任一指标低于上表门槛即阻断改动,不做与历史基线的回退比较。提高门槛时,应同时提交能支撑新门槛的测试,而非只调数字。
|
||
|
||
## CI 工作流
|
||
|
||
PR 与推送在 GitHub Actions 上分三个工作流跑测试,与本地命令一一对应:
|
||
|
||
| Workflow | 触发 | 主要任务 |
|
||
|----------|------|----------|
|
||
| `ci.yml` | push / PR 到 develop、release、main | 快速编译 |
|
||
| `test.yml` | push / PR 到 develop、release、main | 单元、集成、覆盖率 |
|
||
| `e2e.yml` | push 到 develop、release、main 或手动触发 | E2E |
|
||
|
||
合并前应确认:单元与集成 job 通过;覆盖率不低于 `dc3-coverage/pom.xml` 门槛(低于即阻断);行为变更在说明里写清已验证内容与未验证风险。
|
||
|
||
## 延伸阅读
|
||
|
||
- [开发概览与规范](./) — 二次开发的整体约定与命令入口
|
||
- [驱动开发](./driver-authoring) — 派生新驱动时如何补协议层与集成测试
|
||
- [环境变量](../quickstart/environment) — 本地 Java 运行所需的依赖主机与端口
|