feat(k8s): M1 SyncCluster 增加 metrics 采集
后端: - SyncCluster 中调 GetNodeMetrics() 获取 metrics-server 数据 - 节点 INSERT 增加 cpu_usage/memory_usage 百分比字段 - 新增 parseCpuNanoToMilli / parseMemKiToMi 辅助函数 - 采集 Pod metrics(为 M2 准备) 前端: - KubernetesClusterDetail 删除硬编码 resourceData mock - 改为从节点 metrics 实时计算集群 CPU/内存使用率 - 图表 Y 轴固定 0-100 域,底部显示百分比数值 新增文档: - ROADMAP.md / TDD_PLAN.md / PROJECT_EVALUATION.md
This commit is contained in:
1 parent
3b112ee813
commit
b3d2011243
5 files changed
+289
-729
No files matched your search
+58
-358
@@ -1,385 +1,85 @@
|
||||
# AIOps 智能运维平台 — TDD 开发计划
|
||||
# TDD 验证计划 — K8s 管理功能
|
||||
|
||||
> 版本: v1.0
|
||||
> 创建日期: 2026-06-21
|
||||
> 当前覆盖率: **0%** → 目标覆盖率: **≥ 50%** (v0.3.0)
|
||||
> 关联文档: [PROJECT_EVALUATION.md](./PROJECT_EVALUATION.md) | [ROADMAP.md](./ROADMAP.md)
|
||||
> 每个模块实现前先写验证用例,实现后必须跑通,不能只编译通过。
|
||||
|
||||
---
|
||||
## 通用验证流程
|
||||
|
||||
## 一、TDD 原则
|
||||
```bash
|
||||
# 1. 重建后端镜像
|
||||
cd /srv/aiops && docker-compose up -d --build backend
|
||||
|
||||
### Red-Green-Refactor 循环
|
||||
# 2. 等待 healthy
|
||||
sleep 10 && docker ps --format '{{.Names}} {{.Status}}' | grep aiops-backend
|
||||
|
||||
```
|
||||
┌──────────┐ ┌──────────┐ ┌──────────┐
|
||||
│ 🔴 RED │ ──► │ 🟢 GREEN │ ──► │ 🔵 REFACT│
|
||||
│ 写失败测试 │ │ 最快实现 │ │ 消除重复 │
|
||||
└──────────┘ └──────────┘ └──────────┘
|
||||
▲ │
|
||||
└────────────────────────────────────┘
|
||||
循环
|
||||
```
|
||||
# 3. 获取 JWT Token
|
||||
TOKEN=$(curl -s http://127.0.0.1:8080/api/v1/auth/login \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"username":"admin","password":"CHANGE_ME"}' | jq -r '.data.token')
|
||||
|
||||
**三条核心规则:**
|
||||
|
||||
1. **没有测试就不写产品代码** — 先写一个失败的测试,再写实现
|
||||
2. **每次只写刚好能通过的最少代码** — 不过度设计
|
||||
3. **测试通过后立即重构** — 消除重复,提升可读性
|
||||
|
||||
### 测试金字塔
|
||||
|
||||
```
|
||||
╱╲
|
||||
╱E2E╲ ← 少量 (Playwright, ~5%)
|
||||
╱──────╲
|
||||
╱ 集成测试 ╲ ← 适量 (httptest + DB, ~25%)
|
||||
╱────────────╲
|
||||
╱ 单元测试 ╲ ← 大量 (纯函数/逻辑, ~70%)
|
||||
╱──────────────────╲
|
||||
# 4. 验证 API
|
||||
curl -s http://127.0.0.1:8080/api/v1/kubernetes-clusters \
|
||||
-H "Authorization: Bearer $TOKEN" | jq '.'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 二、测试框架选型
|
||||
## M1: SyncCluster metrics 采集
|
||||
|
||||
### 后端 (Go)
|
||||
### 验证用例
|
||||
|
||||
| 工具 | 用途 | 引入方式 |
|
||||
|------|------|---------|
|
||||
| `testing` (标准库) | 测试运行器 | 内置 |
|
||||
| `testify/assert` | 断言库 | `go get github.com/stretchr/testify/assert` |
|
||||
| `testify/require` | 致命断言 | 同上 |
|
||||
| `testify/mock` | Mock 框架 | 同上 |
|
||||
| `net/http/httptest` | HTTP handler 测试 | 内置 |
|
||||
| `gorm.io/driver/sqlite` :memory: | 内存数据库测试 | 已有依赖 |
|
||||
| `swaggo/swag` (可选) | API 文档 | `go get github.com/swaggo/swag/cmd/swag` |
|
||||
```bash
|
||||
# U1: 同步前 — 确认 DB 里 cpu_usage 为 0
|
||||
docker exec aiops-mysql mysql -uroot -p"$MYSQL_ROOT_PASSWORD" aiops \
|
||||
-e "SELECT name, cpu_usage, memory_usage FROM kubernetes_nodes WHERE cluster_id=1"
|
||||
|
||||
### 前端 (React)
|
||||
# U2: 触发同步
|
||||
curl -X POST http://127.0.0.1:8080/api/v1/kubernetes-clusters/1/sync \
|
||||
-H "Authorization: Bearer $TOKEN"
|
||||
|
||||
| 工具 | 用途 | 引入方式 |
|
||||
|------|------|---------|
|
||||
| `vitest` | 测试运行器 | `npm i -D vitest` |
|
||||
| `@testing-library/react` | 组件测试 | `npm i -D @testing-library/react` |
|
||||
| `@testing-library/jest-dom` | DOM 断言 | `npm i -D @testing-library/jest-dom` |
|
||||
| `@testing-library/user-event` | 用户交互模拟 | `npm i -D @testing-library/user-event` |
|
||||
| `msw` | API Mock | `npm i -D msw` |
|
||||
| `@playwright/test` | E2E 测试 | `npm i -D @playwright/test` |
|
||||
# U3: 同步后 — 确认 cpu_usage/memory_usage 非零
|
||||
docker exec aiops-mysql mysql -uroot -p"$MYSQL_ROOT_PASSWORD" aiops \
|
||||
-e "SELECT name, cpu_usage, memory_usage FROM kubernetes_nodes WHERE cluster_id=1"
|
||||
# 期望: cpu_usage > 0 AND memory_usage > 0
|
||||
|
||||
---
|
||||
# U4: 集群详情页 API — 无硬编码
|
||||
curl -s http://127.0.0.1:8080/api/v1/kubernetes-clusters/1 \
|
||||
-H "Authorization: Bearer $TOKEN" | jq '.data.node_count, .data.pod_count'
|
||||
# 期望: node_count=2, pod_count>0
|
||||
|
||||
## 三、后端逐模块测试计划
|
||||
|
||||
### 阶段 1: 核心模块 (第 1-2 周) — 目标覆盖率 40%
|
||||
|
||||
#### 1.1 认证模块 (`user.go`, `user_admin.go`)
|
||||
|
||||
| 测试文件 | 测试函数 | 覆盖场景 | 优先级 |
|
||||
|---------|---------|---------|--------|
|
||||
| `user_test.go` | `TestLogin_Success` | 正确用户名密码 → 200 + JWT | P0 |
|
||||
| | `TestLogin_WrongPassword` | 错误密码 → 401 | P0 |
|
||||
| | `TestLogin_UserNotFound` | 不存在用户 → 401 | P0 |
|
||||
| | `TestRegister_Success` | 正常注册 → 201 | P0 |
|
||||
| | `TestRegister_DuplicateUsername` | 重复用户名 → 409 | P0 |
|
||||
| | `TestGetProfile_Authenticated` | 带 JWT → 200 + 用户信息 | P0 |
|
||||
| | `TestGetProfile_NoAuth` | 无 JWT → 401 | P0 |
|
||||
| | `TestChangePassword_Success` | 正确旧密码 → 200 | P1 |
|
||||
| | `TestChangePassword_WrongOld` | 错误旧密码 → 400 | P1 |
|
||||
| | `TestJWTAuth_ValidToken` | 有效 JWT → 放行 | P0 |
|
||||
| | `TestJWTAuth_ExpiredToken` | 过期 JWT → 401 | P0 |
|
||||
| | `TestJWTAuth_MalformedToken` | 畸形 JWT → 401 | P1 |
|
||||
|
||||
#### 1.2 Kubernetes 模块 (`kubernetes.go`) — 最高优先级
|
||||
|
||||
| 测试文件 | 测试函数 | 覆盖场景 | 优先级 |
|
||||
|---------|---------|---------|--------|
|
||||
| `kubernetes_cluster_test.go` | `TestListClusters_Empty` | 空列表 → 200 + [] | P0 |
|
||||
| | `TestListClusters_With3Records` | 3 条记录 → 200 + 3 items | P0 |
|
||||
| | `TestCreateCluster_ValidInput` | 合法 Name/Code → 201 | P0 |
|
||||
| | `TestCreateCluster_InvalidName` | Name 含中文/空格 → 400 | P0 |
|
||||
| | `TestCreateCluster_DuplicateCode` | 重复 Code → 409 | P0 |
|
||||
| | `TestGetCluster_Found` | 存在的 ID → 200 | P0 |
|
||||
| | `TestGetCluster_NotFound` | 不存在的 ID → 404 | P0 |
|
||||
| | `TestUpdateCluster_Success` | 更新名称 → 200 | P1 |
|
||||
| | `TestDeleteCluster_Success` | 删除 → 200 | P1 |
|
||||
| | `TestDeleteCluster_NotFound` | 删除不存在 → 404 | P1 |
|
||||
| | `TestSyncCluster_Success` | 同步集群资源 → 200 + 统计 | P0 |
|
||||
| | `TestResolveClusterCode_Valid` | code 解析 → cluster ID | P0 |
|
||||
| | `TestResolveClusterCode_NotFound` | 无效 code → 错误 | P0 |
|
||||
| `kubernetes_workload_test.go` | `TestListDeployments_NS` | 按命名空间过滤 → 200 | P0 |
|
||||
| | `TestScaleDeployment_Up` | 扩容 1→3 → 200 + replicas=3 | P0 |
|
||||
| | `TestScaleDeployment_Down` | 缩容 3→1 → 200 | P1 |
|
||||
| | `TestRestartDeployment_Success` | 滚动重启 → 200 | P0 |
|
||||
| | `TestUpdateDeploymentImage_Success` | 更新镜像 → 200 | P1 |
|
||||
| | `TestListHPAs_NS` | HPA 列表 → 200 | P1 |
|
||||
| | `TestCreateHPA_Valid` | 创建 HPA → 201 | P1 |
|
||||
| | `TestDeleteHPA_Success` | 删除 HPA → 200 | P2 |
|
||||
| | `TestListCronJobs_NS` | CronJob 列表 → 200 | P1 |
|
||||
| | `TestCreateCronJob_Valid` | 创建 CronJob → 201 | P1 |
|
||||
| | `TestSuspendCronJob_Success` | 挂起 → 200 | P2 |
|
||||
| | `TestTriggerCronJob_Success` | 手动触发 → 200 | P2 |
|
||||
|
||||
> **注意:** K8s 测试需要 Mock `kubernetesclient.Client` 接口,不依赖真实集群。使用 `testify/mock` 或接口抽象。
|
||||
|
||||
#### 1.3 CI/CD Pipeline 模块 (`pipeline.go`, `pipeline_executor.go`)
|
||||
|
||||
| 测试文件 | 测试函数 | 覆盖场景 | 优先级 |
|
||||
|---------|---------|---------|--------|
|
||||
| `pipeline_test.go` | `TestCreatePipeline_Valid` | 合法配置 → 201 | P0 |
|
||||
| | `TestCreatePipeline_DuplicateCode` | 重复 code → 409 | P0 |
|
||||
| | `TestListPipelines_Pagination` | 分页 → 正确总数+页 | P1 |
|
||||
| | `TestGetPipeline_Found` | 查询 → 200 | P0 |
|
||||
| | `TestUpdatePipeline_Success` | 更新 → 200 | P1 |
|
||||
| | `TestDeletePipeline_Success` | 删除 → 200 | P1 |
|
||||
| | `TestWebhookTrigger_Valid` | Webhook 触发 → 200 + run | P0 |
|
||||
| `pipeline_executor_test.go` | `TestExecutePipeline_AllStepsPass` | 全步骤通过 → success | P0 |
|
||||
| | `TestExecutePipeline_StepFail` | 步骤失败 → failed | P0 |
|
||||
| | `TestExecutePipeline_Conditional` | 条件步骤 → skip/execute | P1 |
|
||||
|
||||
### 阶段 2: 监控/告警模块 (第 3 周) — 目标覆盖率 50%
|
||||
|
||||
#### 2.1 告警引擎 (`alert_engine.go`, `alert_rules.go`)
|
||||
|
||||
| 测试文件 | 测试函数 | 覆盖场景 | 优先级 |
|
||||
|---------|---------|---------|--------|
|
||||
| `alert_engine_test.go` | `TestEvaluateRule_ThresholdExceeded` | CPU > 90% → 触发 | P0 |
|
||||
| | `TestEvaluateRule_ThresholdNormal` | CPU < 90% → 不触发 | P0 |
|
||||
| | `TestEvaluateRule_OfflineDetection` | Agent 离线 → 触发 | P0 |
|
||||
| | `TestEvaluateRule_SilencedPeriod` | 静默期内 → 不触发 | P1 |
|
||||
| | `TestSendAlert_AllChannels` | 通知全渠道 → 计数正确 | P1 |
|
||||
| `alert_rules_test.go` | `TestCreateAlertRule_Valid` | 创建规则 → 201 | P1 |
|
||||
| | `TestListAlertRules_BySeverity` | 按级别过滤 → 200 | P1 |
|
||||
| | `TestUpdateAlertRule_Toggle` | 启用/禁用 → 200 | P2 |
|
||||
|
||||
#### 2.2 服务器管理 (`server.go`)
|
||||
|
||||
| 测试文件 | 测试函数 | 覆盖场景 | 优先级 |
|
||||
|---------|---------|---------|--------|
|
||||
| `server_test.go` | `TestListServers_Pagination` | 分页 → 正确 | P1 |
|
||||
| | `TestCreateServer_Valid` | 合法 → 201 | P1 |
|
||||
| | `TestBatchCreateServers_3Items` | 批量创建 3 条 → 201 | P1 |
|
||||
| | `TestBatchDeleteServers_3IDs` | 批量删除 → 200 | P2 |
|
||||
| | `TestGetServerStats_Aggregation` | 统计聚合 → 正确数字 | P2 |
|
||||
|
||||
#### 2.3 云资源 (`cloud_accounts.go`, `cloud_sync.go`)
|
||||
|
||||
| 测试文件 | 测试函数 | 覆盖场景 | 优先级 |
|
||||
|---------|---------|---------|--------|
|
||||
| `cloud_accounts_test.go` | `TestCreateCloudAccount_Valid` | 合法 → 201 | P1 |
|
||||
| | `TestCreateCloudAccount_InvalidAK` | 空 AK → 400 | P2 |
|
||||
| | `TestListCloudAccounts_ByPlatform` | 按平台过滤 → 200 | P2 |
|
||||
| `cloud_sync_test.go` | `TestDiscoverInstances_MockProvider` | Mock 云 API → 正确实例 | P2 |
|
||||
|
||||
### 阶段 3: 补全覆盖 (第 4 周) — 目标覆盖率 50%+
|
||||
|
||||
#### 3.1 Agent / 自动化 / 审批
|
||||
|
||||
| 测试文件 | 测试函数 | 覆盖场景 | 优先级 |
|
||||
|---------|---------|---------|--------|
|
||||
| `agent_test.go` | `TestRegisterAgent_New` | 新注册 → 201 | P1 |
|
||||
| | `TestAgentHeartbeat_Update` | 心跳 → 更新时间 | P1 |
|
||||
| | `TestAssignTask_AvailableAgent` | 分配任务 → 200 | P2 |
|
||||
| `automation_cron_test.go` | `TestCreateCronJob_Valid` | 创建定时任务 → 201 | P1 |
|
||||
| | `TestListCronJobs_FilterByStatus` | 按状态过滤 → 200 | P2 |
|
||||
| `approval_test.go` | `TestCreateApprovalRequest_Valid` | 创建审批 → 201 | P1 |
|
||||
| | `TestApproveRequest_Success` | 审批通过 → 200 | P1 |
|
||||
| | `TestRejectRequest_WithReason` | 审批拒绝 → 200 | P2 |
|
||||
| `audit_test.go` | `TestListAuditLogs_Pagination` | 审计日志分页 → 200 | P2 |
|
||||
| | `TestExportAuditLogs_CSV` | CSV 导出 → 200 + text/csv | P2 |
|
||||
|
||||
---
|
||||
|
||||
## 四、前端测试计划
|
||||
|
||||
### 阶段 1: 基建 + 核心组件 (第 2-3 周)
|
||||
|
||||
#### 4.1 单元测试 (Vitest)
|
||||
|
||||
| 测试文件 | 覆盖场景 | 优先级 |
|
||||
|---------|---------|--------|
|
||||
| `api.test.ts` | API 函数参数构造/响应解析 Mock | P1 |
|
||||
| `auth/LoginForm.test.tsx` | 表单验证/提交/错误展示 | P0 |
|
||||
| `kubernetes/KubernetesClusters.test.tsx` | 集群列表渲染/创建对话框 | P0 |
|
||||
| `kubernetes/DeploymentDetail.test.tsx` | Deployment 详情渲染/操作按钮 | P1 |
|
||||
| `server/ServerManager.test.tsx` | 服务器列表/搜索/分页 | P1 |
|
||||
| `dashboard/StatsCard.test.tsx` | 统计卡片数据渲染 | P2 |
|
||||
|
||||
#### 4.2 E2E 测试 (Playwright) — 冒烟流程
|
||||
|
||||
| 测试文件 | 覆盖流程 | 优先级 |
|
||||
|---------|---------|--------|
|
||||
| `e2e/login.spec.ts` | 登录 → 跳转仪表盘 → 验证菜单 | P0 |
|
||||
| `e2e/server-list.spec.ts` | 服务器列表 → 搜索 → 查看详情 → WebShell | P1 |
|
||||
| `e2e/k8s-cluster.spec.ts` | K8s 集群 → 工作负载 → Deployment 详情 → 日志 | P1 |
|
||||
|
||||
---
|
||||
|
||||
## 五、测试执行策略
|
||||
|
||||
### 三级执行体系
|
||||
|
||||
| 级别 | 触发时机 | 执行内容 | 耗时目标 |
|
||||
|------|---------|---------|---------|
|
||||
| **日常开发** | `git commit` (pre-push hook) | 后端单元测试 + lint | < 30s |
|
||||
| **PR 检查** | PR 创建/更新 (CI) | 全量测试 + lint + build + 覆盖率 | < 5min |
|
||||
| **发布门禁** | tag/release (CI) | 全量 + E2E + 安全扫描 + 3 种部署验证 | < 15min |
|
||||
|
||||
### Makefile 集成
|
||||
|
||||
```makefile
|
||||
# 后端测试
|
||||
test:
|
||||
cd backend && go test ./... -v -count=1
|
||||
|
||||
test-cover:
|
||||
cd backend && go test ./... -coverprofile=coverage.out -covermode=atomic
|
||||
go tool cover -html=coverage.out -o coverage.html
|
||||
|
||||
test-short:
|
||||
cd backend && go test ./... -v -short -count=1
|
||||
|
||||
# 前端测试
|
||||
test-fe:
|
||||
cd frontend && npx vitest run
|
||||
|
||||
test-fe-watch:
|
||||
cd frontend && npx vitest
|
||||
|
||||
test-e2e:
|
||||
cd frontend && npx playwright test
|
||||
|
||||
# 全量测试
|
||||
test-all: test-cover test-fe test-e2e
|
||||
@echo "✅ All tests passed"
|
||||
|
||||
# lint
|
||||
lint:
|
||||
cd backend && golangci-lint run
|
||||
cd frontend && npx eslint src --max-warnings 0
|
||||
# U5: 前端构建无报错
|
||||
cd /srv/aiops/frontend && npm run build 2>&1 | tail -5
|
||||
# 期望: vite build 成功
|
||||
```
|
||||
|
||||
### CI 流水线 (`.gitea/workflows/ci.yml`)
|
||||
### 通过标准
|
||||
- [x] U1: 同步前 cpu_usage=0
|
||||
- [x] U2: 同步 API 200
|
||||
- [x] U3: 同步后 cpu_usage>0
|
||||
- [x] U4: 集群计数正确
|
||||
- [x] U5: 前端构建成功
|
||||
|
||||
```yaml
|
||||
name: CI
|
||||
on: [push, pull_request]
|
||||
---
|
||||
|
||||
jobs:
|
||||
backend-test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.24'
|
||||
- run: cd backend && go vet ./...
|
||||
- run: cd backend && golangci-lint run
|
||||
- run: cd backend && go test ./... -coverprofile=coverage.out -race
|
||||
- run: cd backend && go tool cover -func=coverage.out
|
||||
## M2: Pod Metrics
|
||||
|
||||
frontend-test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
- run: cd frontend && npm ci
|
||||
- run: cd frontend && npx vitest run --coverage
|
||||
- run: cd frontend && npx tsc --noEmit
|
||||
|
||||
build:
|
||||
needs: [backend-test, frontend-test]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- run: make build
|
||||
### 验证用例
|
||||
```bash
|
||||
# 同步后 Pod 列表含 CPU/内存
|
||||
curl -s "http://127.0.0.1:8080/api/v1/kubernetes-clusters/1/pods" \
|
||||
-H "Authorization: Bearer $TOKEN" | jq '.data[0]'
|
||||
# 期望: 含 cpu_usage_m / mem_usage_mi 字段
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 六、覆盖率里程碑
|
||||
|
||||
| 版本 | 后端覆盖率 | 前端覆盖率 | E2E 覆盖 | 说明 |
|
||||
|------|-----------|-----------|---------|------|
|
||||
| **当前** | **0%** | **0%** | **0%** | 零起点 |
|
||||
| v0.3.0 Sprint 1 | 15% | 5% | 0% | 核心模块首批测试 |
|
||||
| v0.3.0 Sprint 2 | 30% | 10% | 0% | K8s 全覆盖 + 重构 |
|
||||
| v0.3.0 Sprint 3 | 45% | 15% | 3 条 | 监控/告警 + E2E 冒烟 |
|
||||
| **v0.3.0 发布** | **≥ 50%** | **≥ 15%** | **3 条** | 质量加固验收 |
|
||||
| v0.4.0 | ≥ 65% | ≥ 30% | 8 条 | 企业级特性 + 测试增强 |
|
||||
| v1.0.0 | ≥ 75% | ≥ 50% | 20 条 | 智能化 + 全量覆盖 |
|
||||
|
||||
### 覆盖率豁免规则
|
||||
|
||||
以下代码允许低覆盖率:
|
||||
- `main.go` / `cmd/` — 入口文件,由 E2E 覆盖
|
||||
- `scripts/` — 种子数据/Mock 脚本
|
||||
- WebSocket handler — 由集成测试覆盖
|
||||
- `pkg/crypto` — 安全敏感,由专项安全测试覆盖
|
||||
|
||||
---
|
||||
|
||||
## 七、Mock 策略
|
||||
|
||||
### 后端 Mock 层次
|
||||
## M3: Pod 文件上传
|
||||
|
||||
### 验证用例
|
||||
```bash
|
||||
# 上传文件
|
||||
echo "test content" > /tmp/test-upload.txt
|
||||
curl -X POST "http://127.0.0.1:8080/api/v1/kubernetes/1/namespace/devops/pods/aiops-xxx/files/upload?path=/tmp&container=aiops" \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-F "file=@/tmp/test-upload.txt"
|
||||
# 期望: 200 + 文件出现在列表中
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ HTTP Handler Test │ ← httptest.NewServer
|
||||
├─────────────────────────────────────┤
|
||||
│ Service / Business │ ← 接口注入 Mock
|
||||
├─────────────────────────────────────┤
|
||||
│ Data Access (GORM) │ ← SQLite :memory: 真实 DB
|
||||
├─────────────────────────────────────┤
|
||||
│ External (K8s/Prometheus/SSH) │ ← Interface Mock
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**原则:**
|
||||
- **数据库层**: 使用 SQLite `:memory:` 真实测试,不用 Mock (GORM 兼容)
|
||||
- **外部服务** (K8s/Prometheus/Zabbix): 抽象为 interface,用 `testify/mock` 替换
|
||||
- **HTTP handler**: 使用 `httptest` 发起真实 HTTP 请求测试
|
||||
|
||||
### K8s Client Mock 示例
|
||||
|
||||
```go
|
||||
// 定义 interface (如果没有的话)
|
||||
type K8sClientInterface interface {
|
||||
ListDeployments(namespace string) ([]appsv1.Deployment, error)
|
||||
ScaleDeployment(name, namespace string, replicas int32) error
|
||||
// ...
|
||||
}
|
||||
|
||||
// Mock 实现
|
||||
type MockK8sClient struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (m *MockK8sClient) ListDeployments(ns string) ([]appsv1.Deployment, error) {
|
||||
args := m.Called(ns)
|
||||
return args.Get(0).([]appsv1.Deployment), args.Error(1)
|
||||
}
|
||||
|
||||
// 测试中使用
|
||||
func TestListDeployments_NS(t *testing.T) {
|
||||
mockClient := new(MockK8sClient)
|
||||
mockClient.On("ListDeployments", "default").Return([]appsv1.Deployment{...}, nil)
|
||||
|
||||
handler := &KubernetesHandler{k8sClient: mockClient, db: testDB}
|
||||
// ... HTTP 测试
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 八、常见陷阱
|
||||
|
||||
1. **不要 Mock 数据库** — 用 SQLite `:memory:` 真实测试 GORM 逻辑,Mock DB 会漏掉 SQL 错误
|
||||
2. **测试必须独立** — 每个测试用例使用独立 DB 实例或事务回滚,避免数据污染
|
||||
3. **K8s 测试不连真实集群** — 通过 interface Mock,CI 环境没有 K8s
|
||||
4. **WebSocket 测试用 dialer** — 不依赖浏览器,用 `gorilla/websocket` dialer 模拟
|
||||
5. **前端测试不依赖网络** — 用 MSW 拦截所有 API 请求
|
||||
6. **测试命名清晰** — `Test<函数>_<场景>` 格式,如 `TestLogin_WrongPassword`
|
||||
7. **覆盖率不是目的** — 50% 覆盖核心逻辑 > 90% 覆盖 getter/setter
|
||||
8. **先写最难的测试** — K8s handler 99 个函数,从 cluster CRUD 开始(逻辑最核心)
|
||||
Reference in new issue
Block a user