08 — 云融(YunRong)· 测试计划文档
前置文档:05-模块详细设计文档、06-接口设计文档
设计思路:00-设计思路文档
版本:v0.1
状态:已发布
最后更新:2025-01
1. 测试策略概览
1.1 测试金字塔
┌─────────┐
│ E2E │ 5% — 完整用户场景 (Mock Server + 真实 GUI)
│ 端到端 │
┌┴─────────┴┐
│ Integration│ 20% — 模块间交互 (网络↔数据↔业务)
│ 集成测试 │
┌┴───────────┴┐
│ Unit Test │ 75% — 独立类/函数 (纯逻辑、无 IO)
│ 单元测试 │
└───────────────┘
| 层级 | 覆盖目标 | 运行时间 | 执行频率 |
|---|
| 单元测试 | 所有纯逻辑类 ≥ 70% 行覆盖 | < 10s | 每次 commit |
| 集成测试 | 核心数据流路径 100% 覆盖 | < 60s | 每次 push |
| 端到端测试 | 核心用户场景 100% 覆盖 | < 5min | 每次 PR / 每日 |
1.2 测试框架选型
| 框架 | 用途 | 选择理由 |
|---|
| Google Test (gtest) | C++ 单元/集成测试 | 业界标准,断言丰富,与 CMake 深度集成 |
| Google Mock (gmock) | Mock 对象生成 | 与 gtest 配套,自动生成 Mock 类 |
| Qt Test | GUI 组件测试 | Qt 原生,可模拟鼠标/键盘事件 |
| Mock Server (自研) | 网络集成测试 | 已在 06-API设计文档 §6 中设计 |
1.3 编译与运行
# tests/CMakeLists.txt
enable_testing()
# Google Test
find_package(GTest REQUIRED)
# 单元测试可执行文件
add_executable(enterprise_tests
unit/test_protocol.cpp
unit/test_reconnect_strategy.cpp
unit/test_message_model.cpp
unit/test_sync_manager.cpp
unit/test_config_manager.cpp
# ...
integration/test_ws_reconnect.cpp
integration/test_file_transfer.cpp
integration/test_offline_sync.cpp
# ...
)
target_link_libraries(enterprise_tests PRIVATE
enterprise_core
GTest::gtest
GTest::gmock
Qt6::Test
)
# 注册到 CTest
add_test(NAME enterprise_tests COMMAND enterprise_tests)
运行:
# 全量
ctest --test-dir build -j4 --output-on-failure
# 仅单元测试
./build/tests/enterprise_tests --gtest_filter='Unit*'
# 带覆盖率
cmake -DCMAKE_BUILD_TYPE=Debug -DENABLE_COVERAGE=ON ..
cmake --build . -j4
ctest --test-dir build
lcov --capture --directory build --output-file coverage.info
genhtml coverage.info --output-directory coverage_report
2. 单元测试设计
2.1 测试目标清单
按优先级排序
| 优先级 | 目标类 | 理由 | 预估用例数 |
|---|
| 🔴 P0 | Protocol | 纯函数、协议正确性是系统基石 | 15+ |
| 🔴 P0 | ReconnectStrategy | 纯算法,边界条件多 | 8+ |
| 🔴 P0 | RateLimiter | 令牌桶算法,并发安全 | 6+ |
| 🟡 P1 | SyncManager | 同步逻辑复杂,易出错 | 10+ (需 Mock IDataStore) |
| 🟡 P1 | IMService | 核心业务逻辑 | 8+ (需 Mock IDataStore + ConnMgr) |
| 🟡 P1 | MessageListModel | Qt Model 角色映射 | 5+ |
| 🟢 P2 | ConfigManager | 线程安全单例 | 4+ |
| 🟢 P2 | MessageBus | 无锁队列 | 3+ |
| 🟢 P2 | SqliteStore | 需要真实 SQLite 文件 | 按需 |
2.2 典型单元测试示例
Protocol 测试(纯函数、无依赖、最高优先级)
// tests/unit/test_protocol.cpp
#include <gtest/gtest.h>
#include "net/protocol.h"
using namespace core::net;
class ProtocolTest : public ::testing::Test {
protected:
Protocol proto_;
};
// ---- JSON 编码 ----
TEST_F(ProtocolTest, EncodeAuth_CreatesValidJson) {
auto json = proto_.encodeAuth("test_token_abc", 1);
auto frame = nlohmann::json::parse(json);
EXPECT_EQ(frame["ver"], 1);
EXPECT_EQ(frame["type"], "auth");
EXPECT_EQ(frame["seq"], 1);
EXPECT_EQ(frame["payload"]["token"], "test_token_abc");
EXPECT_TRUE(frame["ts"].is_number());
}
TEST_F(ProtocolTest, EncodeMessage_HandlesAllContentTypes) {
model::Message msg;
msg.convId = 2001;
msg.senderId = 1001;
msg.contentType = 0; // text
msg.contentBody = R"({"type":"text","text":"hello"})";
auto json = proto_.encodeMessage(msg, 42);
auto frame = nlohmann::json::parse(json);
EXPECT_EQ(frame["type"], "msg");
EXPECT_EQ(frame["seq"], 42);
EXPECT_EQ(frame["payload"]["conv_id"], 2001);
}
// ---- JSON 解码 ----
TEST_F(ProtocolTest, Decode_ValidMessageFrame_ReturnsCorrectType) {
std::string json = R"({
"ver": 1, "type": "msg", "seq": 100, "ts": 1704067200000,
"payload": { "msg_id": "abc", "conv_id": 2001, "sender_id": 1002,
"content_type": 0, "content_body": {"type":"text","text":"hi"} }
})";
auto result = proto_.decode(json);
EXPECT_EQ(result.type, Protocol::DecodedType::Message);
EXPECT_EQ(result.seq, 100);
}
TEST_F(ProtocolTest, Decode_AckFrame_ReturnsCorrectType) {
std::string json = R"({
"ver": 1, "type": "ack", "seq": 0, "ts": 1704067200000,
"payload": { "ack_seq": 42, "status": "ok" }
})";
auto result = proto_.decode(json);
EXPECT_EQ(result.type, Protocol::DecodedType::Ack);
EXPECT_EQ(result.payload["ack_seq"], 42);
}
// ---- 校验 ----
TEST_F(ProtocolTest, Validate_MissingTypeField_ReturnsError) {
std::string json = R"({"ver": 1, "seq": 1, "ts": 1, "payload": {}})";
auto error = proto_.validate(nlohmann::json::parse(json));
EXPECT_TRUE(error.has_value());
}
TEST_F(ProtocolTest, Validate_UnknownFrameType_ReturnsError) {
std::string json = R"({"ver": 1, "type": "unknown_xyz", "seq": 1, "ts": 1, "payload": {}})";
auto error = proto_.validate(nlohmann::json::parse(json));
EXPECT_TRUE(error.has_value());
}
// ---- 二进制帧 ----
TEST_F(ProtocolTest, EncodeFileChunk_ProducesCorrectLayout) {
uint8_t data[] = {0xAA, 0xBB, 0xCC};
auto frame = proto_.encodeFileChunk(5, 5242880, data, 3);
// Total Length (4B) + Type (1B) + Chunk Index (2B) + Offset (4B) + Data (3B)
EXPECT_EQ(frame.size(), 4 + 1 + 2 + 4 + 3);
EXPECT_EQ(frame[4], 0x02); // Type = FILE_CHUNK
EXPECT_EQ(frame[5], 0x00); // Chunk Index high byte (5 = 0x0005)
EXPECT_EQ(frame[6], 0x05); // Chunk Index low byte
}
// ---- 序列号提取 ----
TEST_F(ProtocolTest, ExtractSeq_MultipleFrames_HandlesCorrectly) {
auto frame1 = nlohmann::json::parse(R"({"seq": 1})");
auto frame2 = nlohmann::json::parse(R"({"seq": 0})");
auto frame3 = nlohmann::json::parse(R"({"seq": 2147483648})");
EXPECT_EQ(Protocol::extractSeq(frame1), 1);
EXPECT_EQ(Protocol::extractSeq(frame2), 0);
EXPECT_EQ(Protocol::extractSeq(frame3), 2147483648);
}
ReconnectStrategy 测试(纯算法、边界值覆盖)
// tests/unit/test_reconnect_strategy.cpp
#include <gtest/gtest.h>
#include "net/reconnect_strategy.h"
using namespace core::net;
TEST(ReconnectStrategyTest, DelaysFollowExponentialBackoff) {
ReconnectStrategy strategy;
// attempt 1: 1s
EXPECT_EQ(strategy.nextDelay(), std::chrono::milliseconds(1000));
// attempt 2: 2s
EXPECT_EQ(strategy.nextDelay(), std::chrono::milliseconds(2000));
// attempt 3: 4s
EXPECT_EQ(strategy.nextDelay(), std::chrono::milliseconds(4000));
// attempt 4: 8s
EXPECT_EQ(strategy.nextDelay(), std::chrono::milliseconds(8000));
// attempt 5: 30s (transition)
EXPECT_EQ(strategy.nextDelay(), std::chrono::milliseconds(30000));
// attempt 6: 60s (cap)
EXPECT_EQ(strategy.nextDelay(), std::chrono::milliseconds(60000));
}
TEST(ReconnectStrategyTest, DelayCapsAt60Seconds) {
ReconnectStrategy strategy;
for (int i = 0; i < 5; i++) strategy.nextDelay(); // skip to attempt 6
for (int i = 0; i < 10; i++) {
EXPECT_EQ(strategy.nextDelay(), std::chrono::milliseconds(60000));
}
}
TEST(ReconnectStrategyTest, ShouldRetry_ReturnsFalseAfterMaxRetries) {
ReconnectStrategy strategy;
for (int i = 0; i < ReconnectStrategy::MAX_RETRIES; i++) {
ASSERT_TRUE(strategy.shouldRetry());
strategy.nextDelay();
}
EXPECT_FALSE(strategy.shouldRetry());
}
TEST(ReconnectStrategyTest, Reset_RestoresInitialState) {
ReconnectStrategy strategy;
strategy.nextDelay();
strategy.nextDelay();
EXPECT_EQ(strategy.attempt(), 2);
strategy.reset();
EXPECT_EQ(strategy.attempt(), 0);
EXPECT_EQ(strategy.nextDelay(), std::chrono::milliseconds(1000));
}
ConfigManager 测试(线程安全单例)
// tests/unit/test_config_manager.cpp
#include <gtest/gtest.h>
#include <thread>
#include <vector>
#include "infra/config_mgr.h"
using namespace core::infra;
TEST(ConfigManagerTest, SingletonReturnsSameInstance) {
auto& a = ConfigManager::instance();
auto& b = ConfigManager::instance();
EXPECT_EQ(&a, &b);
}
TEST(ConfigManagerTest, SetAndGet_String) {
auto& cfg = ConfigManager::instance();
cfg.setRaw("test.key", "hello");
auto val = cfg.getRaw("test.key");
ASSERT_TRUE(val.has_value());
EXPECT_EQ(*val, "hello");
}
TEST(ConfigManagerTest, GetMissingKey_ReturnsNullopt) {
auto& cfg = ConfigManager::instance();
auto val = cfg.getRaw("nonexistent.key.xyz");
EXPECT_FALSE(val.has_value());
}
TEST(ConfigManagerTest, TemplateGet_WithDefault) {
auto& cfg = ConfigManager::instance();
cfg.setRaw("int.key", "42");
EXPECT_EQ(cfg.get<int>("int.key", 0), 42);
EXPECT_EQ(cfg.get<int>("missing.key", 99), 99);
}
TEST(ConfigManagerTest, ThreadSafety_ConcurrentReadWrite) {
auto& cfg = ConfigManager::instance();
cfg.setRaw("ts.key", "start");
std::vector<std::thread> threads;
for (int i = 0; i < 10; i++) {
threads.emplace_back([i, &cfg] {
for (int j = 0; j < 100; j++) {
cfg.setRaw("ts.key", std::to_string(i * 100 + j));
auto val = cfg.getRaw("ts.key");
ASSERT_TRUE(val.has_value()); // 永远不应该得到 nullopt
}
});
}
for (auto& t : threads) t.join();
// 无崩溃、无数据竞争 = 通过
}
3. 集成测试设计
3.1 Mock 策略
被测对象 (SUT) 外部依赖 Mock 方式
──────────────────────────────────────────────────────────
IMService → IDataStore MockDataStore (gmock)
→ ConnectionManager MockConnectionManager (手写)
SyncManager → IDataStore (local) SqliteStore (真实,:memory:)
→ WebSocket sender lambda 捕获
ConnectionManager → IConnection (WS) MockConnection (gmock)
→ Protocol 真实 Protocol (无副作用)
FileService → ConnectionManager MockConnectionManager
→ IDataStore MockDataStore
3.2 核心集成测试场景
场景 1:消息发送 → ACK → 状态更新
GIVEN: MockConnectionManager (捕获 sendText 调用)
MockDataStore (捕获 storeMessage 调用)
WHEN: IMService::sendTextMessage(convId=2001, text="hello")
THEN: 1. storeMessage 被调用 1 次 (synced=0)
2. sendText 被调用 1 次 (含正确 JSON)
WHEN: 模拟收到 ACK (seq 对应)
THEN: 3. markMessageSynced 被调用 1 次
4. updateMessageStatus → Sent
场景 2:WebSocket 断线 → 重连 → 增量同步
GIVEN: 真实 Mock Server (WS port 18001)
SqliteStore (内存模式,含 50 条预置消息)
WHEN: 建立 WS 连接,auth 成功
收到 30 条消息后 Mock Server 主动断开连接
THEN: 1. ConnectionManager 状态 → Reconnecting
2. ReconnectStrategy 按指数退避重试
3. 重连成功后发送 sync(last_seq=30)
4. 收到 sync_data (seq 31-50)
5. SqliteStore 中消息完整 (50 条,无重复)
6. 状态 → Connected
场景 3:离线队列重放
GIVEN: SqliteStore 中有 3 条未同步消息 (synced=0)
offline_queue 中有 3 条对应操作
MockConnectionManager (捕获 sendText)
WHEN: SyncManager::replayOfflineQueue()
THEN: 1. sendText 被调用 3 次
2. 每次成功后 → markOfflineOpDone
3. 全部完成后 → 队列为空
WHEN: 其中第 2 条 sendText 失败 (Mock 返回 false)
THEN: 4. 该条 retry_count++
5. retry_count < 3 → 保留在队列
6. retry_count >= 3 → markOfflineOpFailed
场景 4:文件分块上传 + 断点续传
GIVEN: Mock Server (文件端口 18003)
本地文件 test.bin (5MB, SHA-256 已知)
WHEN: FileService::uploadFile("test.bin", chunkSize=1MB)
THEN: 1. POST /files/check → { exists: false }
2. 分配 upload_id
3. POST chunk 0-4 依次发送
4. 每发送 1 块 → progress 回调触发
5. POST /files/upload/{id}/complete → { url, hash_verified: true }
WHEN: 上传 chunk 2 时 Mock Server 返回 500
THEN: 6. chunk 2 标记失败、自动重试
7. 重试成功后继续 chunk 3, 4
WHEN: 上传中途断开网络
恢复后调用 resumeTransfer
THEN: 8. 从 completed_chunks bitmap 恢复
9. 只发送未完成的块
10. 最终 5 块全部完成
4. 端到端测试设计
4.1 E2E 场景清单
| 编号 | 场景 | Mock Server 场景文件 | 预期结果 |
|---|
| E2E-01 | 登录 → 加载会话列表 → 发送消息 → 收到回复 | normal_chat.json | 消息气泡正确显示,状态从”发送中”→“已送达” |
| E2E-02 | 接收 200 条离线消息 → 分批同步 | offline_sync.json | 消息分批出现,最后一条后显示”同步完成” |
| E2E-03 | 发送消息时断网 → 恢复 → 消息自动重发 | network_loss.json | 气泡先显示”发送失败”,恢复后自动变为”已送达” |
| E2E-04 | 拖拽文件到聊天窗口 → 上传 → 自动发送文件消息 | file_upload.json | 进度条完整,上传完成后文件卡片出现在聊天中 |
| E2E-05 | 收到新任务通知 → 点击跳转 → 处理任务 | task_notify.json | Toast 弹出 → 点击 → 切换到任务视图 → 可处理 |
| E2E-06 | 切换暗色主题 → 所有窗口即时更新 | N/A (纯本地) | 列表、气泡、控件全部切换为暗色 |
4.2 E2E 测试基础设施
// tests/e2e/e2e_test_fixture.h
class E2ETestFixture : public ::testing::Test {
protected:
void SetUp() override {
// 1. 启动 Mock Server 子进程
mockServer_ = std::make_unique<QProcess>();
mockServer_->start("./mock_server",
{"--scenario", scenarioPath_, "--ws-port", "18001",
"--http-port", "18002", "--file-port", "18003"});
ASSERT_TRUE(mockServer_->waitForStarted(5000));
// 2. 初始化应用核心
threadPool_ = std::make_shared<ThreadPool>(2);
store_ = std::make_shared<SqliteStore>(":memory:");
net_ = std::make_shared<ConnectionManager>(*threadPool_);
imService_ = std::make_shared<IMService>(net_, store_);
// 3. 显示测试窗口
mainWin_ = std::make_unique<MainWindow>();
mainWin_->setServices(imService_, fileService_, taskService_);
mainWin_->show();
}
void TearDown() override {
mainWin_->close();
mockServer_->terminate();
mockServer_->waitForFinished(3000);
}
// 辅助方法
void loginAs(const std::string& username) {
// 通过 REST API 登录
// 建立 WS 连接
// 等待 auth_ok
}
void waitForSignal(QObject* obj, const char* signal, int timeoutMs = 2000) {
QSignalSpy spy(obj, signal);
ASSERT_TRUE(spy.wait(timeoutMs));
}
std::string scenarioPath_;
std::unique_ptr<QProcess> mockServer_;
std::shared_ptr<ThreadPool> threadPool_;
std::shared_ptr<IDataStore> store_;
std::shared_ptr<ConnectionManager> net_;
std::shared_ptr<IMService> imService_;
std::unique_ptr<MainWindow> mainWin_;
};
5. 性能测试
5.1 性能基准
| 指标 | 基准值 | 测试方法 | 严重阈值 |
|---|
| 消息发送延迟 | < 200ms | 时间戳打点,1000 条取 P99 | > 500ms |
| 消息吞吐量 | ≥ 100 条/s | 压力测试 10s | < 50 条/s |
| 10000 条消息列表滚动 | 60fps | QElapsedTimer + frame counter | < 30fps |
| SQLite 批量插入 (200条) | < 50ms | 事务包裹 | > 200ms |
| 消息搜索 (FTS5, 10万条) | < 5ms | 计时 10 次取平均 | > 50ms |
| 内存占用 (空闲) | < 150 MB | 持续运行 1h 采样 | > 300 MB |
| 冷启动时间 | < 3s | QElapsedTimer | > 5s |
5.2 性能测试代码骨架
// tests/performance/perf_messages.cpp
TEST(PerformanceTest, MessageSendLatency_P99Under200ms) {
MockServer server("normal_chat.json");
ConnectionManager net(threadPool);
std::vector<double> latencies;
latencies.reserve(1000);
for (int i = 0; i < 1000; i++) {
auto start = std::chrono::high_resolution_clock::now();
net.sendMessage(makeTestMsg(i), [&](bool ok) {
auto end = std::chrono::high_resolution_clock::now();
latencies.push_back(
std::chrono::duration<double, std::milli>(end - start).count()
);
});
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
std::sort(latencies.begin(), latencies.end());
double p99 = latencies[static_cast<size_t>(latencies.size() * 0.99)];
EXPECT_LT(p99, 200.0) << "P99 latency: " << p99 << "ms";
}
6. CI/CD 集成
6.1 GitHub Actions 流水线
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [develop, main]
pull_request:
branches: [main]
jobs:
build-and-test:
strategy:
matrix:
os: [ubuntu-22.04, windows-2022]
build_type: [Debug, Release]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- name: Install Dependencies (Linux)
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y qt6-base-dev qt6-charts-dev \
qt6-websockets-dev libsqlite3-dev \
libgtest-dev nlohmann-json3-dev libspdlog-dev
- name: Install Dependencies (Windows)
if: runner.os == 'Windows'
run: |
choco install qt6 gtest nlohmann-json spdlog
- name: Configure CMake
run: cmake -B build -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DBUILD_TESTS=ON
- name: Build
run: cmake --build build -j$(nproc)
- name: Unit Tests
run: ctest --test-dir build -L unit --output-on-failure -j4
- name: Integration Tests
run: ctest --test-dir build -L integration --output-on-failure
- name: Coverage (Debug only)
if: matrix.build_type == 'Debug' && runner.os == 'Linux'
run: |
lcov --capture --directory build --output-file coverage.info
lcov --remove coverage.info '/usr/*' '*/tests/*' '*/thirdparty/*' \
--output-file coverage_filtered.info
genhtml coverage_filtered.info --output-directory coverage_report
- name: Upload Coverage
if: matrix.build_type == 'Debug' && runner.os == 'Linux'
uses: actions/upload-artifact@v4
with:
name: coverage-report
path: coverage_report/
6.2 测试标签
# CMakeLists.txt 中按标签分类
add_test(NAME Protocol.Unit COMMAND enterprise_tests --gtest_filter='Protocol*')
set_tests_properties(Protocol.Unit PROPERTIES LABELS "unit")
add_test(NAME WSReconnect.Integration COMMAND enterprise_tests --gtest_filter='Integration*WS*')
set_tests_properties(WSReconnect.Integration PROPERTIES LABELS "integration")
add_test(NAME Messages.E2E COMMAND enterprise_tests --gtest_filter='E2E*Message*')
set_tests_properties(Messages.E2E PROPERTIES LABELS "e2e")
add_test(NAME MessageSend.Perf COMMAND enterprise_tests --gtest_filter='Perf*Message*')
set_tests_properties(MessageSend.Perf PROPERTIES LABELS "performance")
7. 覆盖率目标
| 模块 | 行覆盖率目标 | 分支覆盖率目标 | 说明 |
|---|
net/protocol | 90% | 85% | 纯函数,高覆盖率可实现 |
net/reconnect_strategy | 95% | 90% | 简单算法,边界值全覆盖 |
net/connection_mgr | 65% | 55% | 异步回调多,部分路径难触达 |
data/sync_mgr | 70% | 60% | 需 Mock 辅助 |
service/im_service | 70% | 60% | 核心业务逻辑 |
infra/config_mgr | 85% | 75% | 单例 + 线程安全 |
gui/* | 不强制 | — | GUI 用人工测试为主 |
| 整体核心库 (core/) | ≥ 70% | ≥ 60% | |
8. 测试数据管理
8.1 测试数据生成
// tests/common/test_data.h
namespace test {
// 生成可预测的测试消息
model::Message makeTestMessage(int64_t convId, int64_t senderId, int index) {
model::Message msg;
msg.msgId = fmt::format("test_msg_{:06d}", index);
msg.convId = convId;
msg.senderId = senderId;
msg.senderName = fmt::format("User_{}", senderId);
msg.contentType = 0;
msg.contentBody = fmt::format(R"({{"type":"text","text":"test message {}"}})", index);
msg.timestamp = 1704067200000 + index * 1000;
msg.seq = index;
return msg;
}
// 生成批量测试数据
std::vector<model::Message> makeTestMessages(int count, int64_t convId = 2001) {
std::vector<model::Message> msgs;
msgs.reserve(count);
for (int i = 0; i < count; i++) {
msgs.push_back(makeTestMessage(convId, 1002, i));
}
return msgs;
}
} // namespace test
8.2 Mock Server 场景文件管理
tests/scenarios/
├── normal_chat.json # 正常聊天
├── offline_sync.json # 离线消息同步 (200 条分批)
├── network_loss.json # 网络丢包 + 重连
├── file_upload.json # 文件上传 + 秒传
├── file_upload_chunk_loss.json # 分块上传丢包 (断点续传)
├── task_notify.json # 任务通知推送
├── error_injection.json # 综合错误注入
└── stress_1000msg.json # 压力测试 (1000 条消息)
9. 缺陷管理约定
9.1 严重级别
| 级别 | 定义 | 响应时间 |
|---|
| P0-致命 | 崩溃、数据丢失、消息不可达 | 立即修复 |
| P1-严重 | 核心功能不可用(无法登录、无法发送消息) | 24h 内 |
| P2-一般 | 非核心功能异常(统计图表错误、导出格式问题) | 本周内 |
| P3-轻微 | UI 错位、文案错误、体验瑕疵 | 下个迭代 |
9.2 测试报告模板
## 测试报告 — [版本号] — [日期]
### 概要
- 总用例数: 120
- 通过: 115
- 失败: 3
- 跳过: 2
- 通过率: 95.8%
### 失败用例
| 用例 | 级别 | 失败原因 | 关联 Issue |
|------|------|----------|-----------|
| ReconnectStrategy.DelayCaps | P2 | attempt 6 返回 30s 而非 60s | #142 |
| FileUpload.ChunkRetry | P1 | 重试 3 次后未标记失败 | #143 |
### 覆盖率
- 行覆盖率: 72.3% (目标 70% ✅)
- 分支覆盖率: 61.1% (目标 60% ✅)
附录 A — 快速参考
| 命令 | 用途 |
|---|
ctest --test-dir build -j4 | 运行全部测试 |
ctest --test-dir build -L unit | 仅单元测试 |
ctest --test-dir build -R Protocol | 运行匹配 “Protocol” 的测试 |
./build/tests/enterprise_tests --gtest_list_tests | 列出所有测试 |
./build/tests/enterprise_tests --gtest_filter='*Reconnect*' | 运行过滤后的测试 |
./build/tests/enterprise_tests --gtest_repeat=10 | 重复运行 10 次(排查偶发问题) |
附录 B — 文档修订记录
| 版本 | 日期 | 作者 | 变更说明 |
|---|
| v0.1 | 2025-01 | — | 初稿:测试金字塔、单元/集成/E2E/性能测试策略、CI 流水线、覆盖率目标 |