新增 test/PHASE2_TEST_PLAN.md,详细规划上线后测试补齐路径: **P0 优先级(2周内)**: - memory/postgres store 达标 >60% - router/health handler 达标 >60% - handlers 补齐 HandleChannel/TicketStatsHandler.Get **P1 优先级(4周内)**: - Domain 包(6个)基础测试 >30% - logging/dialog/app 提升至 >75% **Phase 2 目标**:整体覆盖率从 62.6% → >70% Ref: PRODUCTION_PHASE1_STATUS.md §8 测试覆盖率
11 KiB
Phase 2 测试质量提升规划
生成时间:2026-05-01 09:00 GMT+8 负责人:宰相(小龙团队 QA subagent) 项目:ai-customer-service 依据:PRODUCTION_PHASE1_STATUS.md、TEST_COVERAGE_REPORT.md
一、当前质量基线(Phase 1 已达标)
1.1 整体状态
| 指标 | 当前值 | Phase 1 目标 | Phase 2 目标 |
|---|---|---|---|
| 整体覆盖率 | 62.6% | >60% ✅ | >70% |
| Build + vet + tests | ✅ 全通过 | ✅ 必须 | ✅ 必须 |
| Phase 1 核心包 | 4/5 >60% | >60% ✅ | >70% |
| E2E 测试 | 100% | >60% ✅ | 100% ✅ |
| Integration 测试 | 100% | >60% ✅ | 100% ✅ |
1.2 各包覆盖率现状
| 包 | 覆盖率 | 状态 | Phase 2 目标 |
|---|---|---|---|
internal/service/reply |
100% | ✅ | 保持 |
internal/service/handoff |
100% | ✅ | 保持 |
test/e2e |
100% | ✅ | 保持 |
test/integration |
100% | ✅ | 保持 |
internal/service/dialog |
88.5% | ✅ | >90% |
internal/platform/httpx |
84.3% | ✅ | >85% |
internal/service/intent |
80.8% | ✅ | >85% |
internal/http/handlers |
78.4% | ✅ | >85% |
internal/app |
74.2% | ✅ | >80% |
internal/config |
70.6% | ✅ | >75% |
internal/store/memory |
59.1% | ⚠️ | >70% |
internal/store/postgres |
43.1% | ⚠️ | >60% |
internal/http (router) |
41.3% | ⚠️ | >60% |
internal/platform/health |
38.1% | ⚠️ | >60% |
| Domain 包(6个) | 0% | ❌ | >30% |
cmd/ai-customer-service |
0% | ❌ | 测试可选 |
internal/platform/logging |
0% | ❌ | >40% |
二、Phase 2 测试补齐优先级
P0 — 必须补齐(上线后 2 周内)
| 优先级 | 包 | 当前覆盖率 | 目标覆盖率 | 关键缺失 |
|---|---|---|---|---|
| P0-1 | internal/store/memory |
59.1% | >70% | ListAll(0%)、GetStats(0%) |
| P0-2 | internal/store/postgres |
43.1% | >60% | Assign(0%)、Resolve(0%)、Close(0%) |
| P0-3 | internal/http (router) |
41.3% | >60% | writeMethodNotAllowed(0%)、webhook channel 路由 |
| P0-4 | internal/platform/health |
38.1% | >60% | IsReady(0%)、dependency check 边界 |
| P0-5 | internal/http/handlers |
78.4% | >85% | HandleChannel(0%)、TicketStatsHandler.Get(0%) |
P1 — 强烈建议补齐(上线后 4 周内)
| 优先级 | 包 | 当前覆盖率 | 目标覆盖率 | 关键缺失 |
|---|---|---|---|---|
| P1-1 | Domain 包 (6个) | 0% | >30% | 所有 domain 包无测试文件 |
| P1-2 | internal/platform/logging |
0% | >40% | Logger 初始化未覆盖 |
| P1-3 | internal/service/dialog |
88.5% | >90% | Process 边界场景补全 |
| P1-4 | internal/app |
74.2% | >80% | Shutdown 错误处理分支 |
P2 — 可选(长期优化)
| 优先级 | 包 | 当前覆盖率 | 目标覆盖率 | 说明 |
|---|---|---|---|---|
| P2-1 | cmd/ai-customer-service |
0% | 测试可选 | main 函数测试意义有限 |
| P2-2 | internal/http/handlers |
78.4% | >90% | clientIP(66.7%) 边界场景 |
三、具体补齐方案
3.1 P0-1: internal/store/memory 测试补齐
当前缺失:
ListAll()— 0% 覆盖(无测试调用)GetStats()— 0% 覆盖(无测试调用)
补齐方案:
在 internal/store/memory/ticket_store_test.go 中新增:
func TestTicketStore_ListAll(t *testing.T) {
store := NewTicketStore()
ctx := context.Background()
// Create 3 tickets
store.Create(ctx, &ticket.Ticket{ID: "t1", Status: ticket.StatusOpen})
store.Create(ctx, &ticket.Ticket{ID: "t2", Status: ticket.StatusResolved})
store.Create(ctx, &ticket.Ticket{ID: "t3", Status: ticket.StatusClosed})
// ListAll should return all 3
all, err := store.ListAll(ctx)
if err != nil || len(all) != 3 {
t.Fatalf("ListAll() = %d tickets, want 3", len(all))
}
}
func TestTicketStore_GetStats(t *testing.T) {
store := NewTicketStore()
ctx := context.Background()
// Create tickets with different statuses and channels
store.Create(ctx, &ticket.Ticket{ID: "t1", Status: ticket.StatusOpen, Priority: ticket.PriorityP1})
store.Create(ctx, &ticket.Ticket{ID: "t2", Status: ticket.StatusResolved, Priority: ticket.PriorityP2})
stats, err := store.GetStats(ctx)
if err != nil {
t.Fatalf("GetStats() error = %v", err)
}
if stats.Total != 2 {
t.Fatalf("stats.Total = %d, want 2", stats.Total)
}
if stats.Open != 1 || stats.Resolved != 1 {
t.Fatalf("stats Open/Resolved = %d/%d, want 1/1", stats.Open, stats.Resolved)
}
}
预期提升: 59.1% → >70%
3.2 P0-2: internal/store/postgres 测试补齐
当前缺失:
Assign()— 0%(未覆盖)Resolve()— 0%(未覆盖)Close()— 0%(未覆盖)
补齐方案:
在 internal/store/postgres/store_test.go 中新增 workflow 操作测试(需 sqlmock):
func TestTicketWorkflowStore_Assign(t *testing.T) {
db, mock, err := sqlmock.New()
if err != nil {
t.Fatalf("sqlmock.New() error = %v", err)
}
defer db.Close()
auditStore := NewAuditStore(db)
workflowStore := NewTicketWorkflowStore(db, auditStore)
// Mock UPDATE query
mock.ExpectExec("UPDATE tickets SET").
WithArgs("agent1", sqlmock.AnyArg(), "t1").
WillReturnResult(sqlmock.NewResult(0, 1))
// Mock audit insert
mock.ExpectExec("INSERT INTO audit").
WillReturnResult(sqlmock.NewResult(1, 1))
err = workflowStore.Assign(context.Background(), "t1", "agent1", "admin", "127.0.0.1", time.Now())
if err != nil {
t.Fatalf("Assign() error = %v", err)
}
}
预期提升: 43.1% → >60%
3.3 P0-3: internal/http (router) 测试补齐
当前缺失:
writeMethodNotAllowed()— 0%(从未调用)- Webhook channel 路由未测
补齐方案:
在 internal/http/router_test.go 中新增:
func TestRouter_WriteMethodNotAllowed_Called(t *testing.T) {
// Test that unknown methods on known paths call writeMethodNotAllowed
probe := health.NewProbe()
h := handlers.NewHealthHandler(probe)
ticketHandler := &handlers.TicketHandler{}
router := NewRouter(RouterDeps{Health: h, Tickets: ticketHandler})
// POST to /tickets (only GET allowed) should trigger writeMethodNotAllowed
req := httptest.NewRequest(http.MethodPost, "/api/v1/customer-service/tickets", nil)
rr := httptest.NewRecorder()
router.ServeHTTP(rr, req)
if rr.Code != http.StatusMethodNotAllowed {
t.Errorf("POST /tickets = %d, want 405", rr.Code)
}
}
预期提升: 41.3% → >60%
3.4 P0-4: internal/platform/health 测试补齐
当前缺失:
IsReady()— 0%(未测试)- dependency check 边界条件
补齐方案:
在 internal/platform/health/health_test.go 中新增:
func TestProbe_IsReady_AfterSetReady(t *testing.T) {
probe := NewProbe()
probe.SetReady(true)
if !probe.IsReady() {
t.Error("IsReady() = false, want true after SetReady(true)")
}
}
func TestDependency_Evaluate_FailsWhenCheckFails(t *testing.T) {
dep := Dependency{
Name: "test",
Check: func() error { return fmt.Errorf("check failed") },
}
err := dep.Evaluate()
if err == nil {
t.Error("Evaluate() = nil, want error when Check fails")
}
}
预期提升: 38.1% → >60%
3.5 P0-5: internal/http/handlers 测试补齐
当前缺失:
HandleChannel()— 0%(未测试)TicketStatsHandler.Get()— 0%(集成测试未覆盖 handler 本身)
补齐方案:
HandleChannel 测试:
在 internal/http/handlers/webhook_handler_test.go 中新增:
func TestWebhookHandler_HandleChannel_OverridesBodyChannel(t *testing.T) {
h := newTestWebhookHandler(nil)
payload := `{"message_id":"m1","channel":"wrong","open_id":"u1","content":"hi"}`
req := httptest.NewRequest(http.MethodPost, "/webhook/correct", bytes.NewBufferString(payload))
resp := httptest.NewRecorder()
// Call HandleChannel with "correct" — should override "wrong" in body
h.HandleChannel(resp, req, "correct")
if resp.Code != http.StatusOK {
t.Fatalf("status = %d, want 200", resp.Code)
}
// Verify response contains channel="correct"
}
TicketStatsHandler.Get 测试:
在 internal/http/handlers/ticket_stats_handler_test.go(新建)中:
func TestTicketStatsHandler_Get_Success(t *testing.T) {
mockService := &mockTicketStatsService{
stats: ticketstats.Stats{Total: 100, Open: 30},
}
handler := NewTicketStatsHandler(mockService, nil)
req := httptest.NewRequest(http.MethodGet, "/stats", nil)
resp := httptest.NewRecorder()
handler.Get(resp, req)
if resp.Code != http.StatusOK {
t.Fatalf("status = %d, want 200", resp.Code)
}
}
预期提升: 78.4% → >85%
3.6 P1-1: Domain 包测试补齐
当前缺失:
6 个 domain 包(audit、intent、message、session、ticket、ticketstats)全部无测试文件。
补齐方案:
为每个 domain 包创建基础测试,覆盖结构体构造和边界条件:
// internal/domain/ticket/ticket_test.go
func TestTicket_NewTicket(t *testing.T) {
ticket := &Ticket{
ID: "t1",
Status: StatusOpen,
Priority: PriorityP1,
}
if ticket.ID != "t1" {
t.Errorf("ticket.ID = %s, want t1", ticket.ID)
}
}
func TestTicket_ValidPriorities(t *testing.T) {
validPriorities := []Priority{PriorityP1, PriorityP2, PriorityP3}
for _, p := range validPriorities {
if p == "" {
t.Errorf("priority %q is empty", p)
}
}
}
预期提升: 0% → >30%(每包 3-5 个基础测试)
四、执行时间表(建议)
| 阶段 | 时间 | 优先级 | 预期成果 |
|---|---|---|---|
| Week 1 | 上线后第 1 周 | P0-1 ~ P0-3 | memory + postgres + router 达标 |
| Week 2 | 上线后第 2 周 | P0-4 ~ P0-5 | health + handlers 达标 |
| Week 3 | 上线后第 3 周 | P1-1 | Domain 包基础测试补齐 |
| Week 4 | 上线后第 4 周 | P1-2 ~ P1-4 | 整体覆盖率 >70% |
| Long-term | 上线后 2 个月 | P2 | 覆盖率 >80%(可选) |
五、质量门禁(Phase 2)
| 指标 | Phase 1(当前) | Phase 2 目标 |
|---|---|---|
| 整体覆盖率 | 62.6% ✅ | >70% |
| 核心包覆盖率 | 4/5 >60% | 全部 >70% |
| Domain 包覆盖率 | 0% | >30% |
| Build + vet + tests | ✅ 全通过 | ✅ 全通过 |
| P0 测试补齐 | — | Week 2 完成 |
六、风险与依赖
| 风险 | 缓解措施 |
|---|---|
| PostgreSQL 测试需要 sqlmock | Week 1 引入 sqlmock 依赖 |
| Domain 包测试意义有限 | 仅测试关键边界和构造逻辑 |
| 灰度阶段发现新 bug 需补测 | 预留 Week 3 缓冲时间 |
本文档由宰相(小龙团队 QA subagent)生成 | 2026-05-01 09:00 GMT+8