test-driven-development/SKILL.md
在实现任何功能或修复bug时使用,必须在编写实现代码之前使用
npx skillsauth add spursgo/real-self-skills test-driven-developmentInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
先写测试。观察它失败。编写最少的代码使其通过。
核心原则: 如果你没有看到测试失败,你就不知道它是否测试了正确的东西。
严禁先写代码再补测试。遵循步骤才能获得TDD的价值:
本文档使用 C++ + Google Test 作为示例语言和测试框架。
总是使用:
有限例外(需获得明确批准):
警告: 严禁任何"这次跳过TDD"的想法。
没有先编写失败的测试,就不许编写生产代码
一旦发现违反顺序(先写了代码):
唯一正确的做法: 先写测试,看它失败,再实现代码。
flowchart TD
red["RED<br/><br/>编写失败的测试<br/><small>运行并确认正确失败</small>"]
green["GREEN<br/><br/>编写最小化代码<br/><small>刚好使测试通过</small>"]
refactor["REFACTOR<br/><br/>清理代码<br/><small>保持所有测试通过</small>"]
red -->|"✓ 看到预期的<br/>失败消息"| green
green -->|"✓ 所有测试<br/>都通过"| refactor
refactor -->|"✓ 继续下一个<br/>功能"| red
style red fill:#ffcccc,stroke:#cc0000,stroke-width:2px
style green fill:#ccffcc,stroke:#00cc00,stroke-width:2px
style refactor fill:#ccccff,stroke:#0000cc,stroke-width:2px
编写一个最小的测试来展示应该发生什么。
<Good> ```cpp // retry_operation_test.cpp #include <gtest/gtest.h> #include <functional> #include <stdexcept> #include "retry_operation.h"TEST(RetryOperationTest, RetriesFailedOperations3Times) { int attempts = 0; auto operation = &attempts -> std::string { attempts++; if (attempts < 3) { throw std::runtime_error("fail"); } return "success"; }; // 注:此时 retryOperation() 是一个空的实现,运行测试会失败,然后进入 GREEN 阶段 实现最小化代码! std::string result = retryOperation(operation);
EXPECT_EQ(result, "success"); EXPECT_EQ(attempts, 3); }
清晰的名称,测试真实行为,单一职责
</Good>
<Bad>
```cpp
// retry_operation_test.cpp (不推荐的写法)
#include <gtest/gtest.h>
#include <gmock/gmock.h>
TEST(RetryOperationTest, RetryWorks) {
MockFunction<std::string()> mockOperation;
EXPECT_CALL(mockOperation, Call())
.WillOnce(Throw(std::runtime_error("fail")))
.WillOnce(Throw(std::runtime_error("fail")))
.WillOnce(Return("success"));
retryOperation(mockOperation.AsStdFunction());
// 只验证调用次数,没有验证实际结果
}
模糊的名称,测试的是mock而不是代码 </Bad>
要求:
强制要求。绝不跳过。
./build/tests/retry_operation_test
# 或使用 ctest
ctest -R RetryOperationTest
确认:
测试通过? 你在测试已存在的行为。修复测试。 测试报错? 修复错误,重新运行直到它正确失败。
要严格遵守以下完整的 RED 阶段流程:
flowchart TD
start[编写测试] --> build[构建测试]
build --> compile{编译成功?}
compile -->|否| fix1[修复编译错误]
fix1 --> build
compile -->|是| run[运行测试]
run --> result{测试结果?}
result -->|通过| fix2[测试写错了!<br/>修改测试]
fix2 --> run
result -->|报错| fix3[修复运行时错误]
fix3 --> run
result -->|失败| verify{失败原因<br/>正确?}
verify -->|否| fix4[修改测试或代码]
fix4 --> run
verify -->|是| done[RED阶段完成<br/>进入GREEN阶段]
style done fill:#ccffcc
style fix2 fill:#ffcccc
style fix3 fill:#ffcccc
编写最简单的代码来通过测试。
<Good> ```cpp // retry_operation.h #pragma once #include <functional> #include <stdexcept>template<typename T> T retryOperation(std::function<T()> fn) { for (int i = 0; i < 3; i++) { try { return fn(); } catch (const std::exception& e) { if (i == 2) throw; } } throw std::runtime_error("unreachable"); }
刚好够用以通过测试
</Good>
<Bad>
```cpp
// retry_operation.h (过度设计的版本)
#pragma once
#include <functional>
#include <chrono>
enum class BackoffStrategy { Linear, Exponential };
struct RetryOptions {
int maxRetries = 3;
BackoffStrategy backoff = BackoffStrategy::Exponential;
std::function<void(int)> onRetry = nullptr;
std::chrono::milliseconds delayMs{100};
};
template<typename T>
T retryOperation(
std::function<T()> fn,
const RetryOptions& options = RetryOptions{}
) {
// YAGNI - 你不需要它
// 过早添加了配置选项、回退策略、回调等功能
}
过度设计 </Bad>
不要添加功能、重构其他代码或超出测试范围"改进"代码。
强制要求。
./build/tests/retry_operation_test
# 或运行所有测试
ctest
确认:
测试失败? 修复功能代码,而不是测试代码。
其他测试失败? 立即修复。
仅在绿色状态后:
保持测试绿色。不要添加行为。
为下一个功能编写下一个正确失败的测试。
TEST(EmailValidatorTest, RejectsMissingAtSymbol) { EXPECT_FALSE(validateEmail("userexample.com")); }
TEST(EmailValidatorTest, RejectsMissingDomain) { EXPECT_FALSE(validateEmail("user@")); }
每个测试专注于一个失败场景,失败时容易定位问题
</Good>
<Bad>
```cpp
// 坏 - 一个测试验证多个行为
TEST(EmailValidatorTest, ValidatesEmailAndDomainAndWhitespace) {
EXPECT_FALSE(validateEmail("")); // 空邮箱
EXPECT_FALSE(validateEmail("user@")); // 缺少域名
EXPECT_FALSE(validateEmail(" [email protected] ")); // 有空格
}
测试失败时无法快速判断是哪个场景出错了 </Bad>
判断标准: 测试名称中有"和"(And)或"及"?考虑拆分成多个测试。
// 坏 - 名称模糊,无法理解测试内容
TEST(MyTest, Test1)
TEST(UserServiceTest, TestUser)
TEST(AuthServiceTest, Works)
必须阅读代码才能理解测试目的 </Bad>
命名公式: 动词 + 具体场景 + 预期结果
RejectsEmptyEmail = Rejects(动词)+ EmptyEmail(场景)LocksAccountAfter5FailedAttempts = Locks(动词)+ AccountAfter5FailedAttempts(场景)std::string content = reader.read();
EXPECT_EQ(content, "expected content"); }
测试清楚展示了:如何创建对象、如何调用方法、期望什么结果
</Good>
<Bad>
```cpp
// 坏 - 隐藏实现细节,难以理解
TEST(FileReaderTest, Test) {
auto r = createTestReader(); // 不知道怎么创建
auto result = processData(r); // 不知道做了什么
EXPECT_TRUE(checkResult(result)); // 不知道验证什么
}
过度抽象,看不出实际的使用方式 </Bad>
原则: 测试应该像文档一样,展示最简单直接的使用方式
// Good - 使用 RAII 自动清理
TEST(FileTest, WritesDataCorrectly) {
{
File file("test.txt"); // 构造函数打开文件
file.write("data");
} // 析构函数自动关闭文件
File file("test.txt");
EXPECT_EQ(file.read(), "data");
}
// Bad - 手动管理,可能泄漏
TEST(FileTest, WritesDataCorrectly) {
File* file = new File("test.txt");
file->write("data");
// 忘记 delete!测试失败会导致内存泄漏
}
class DatabaseTest : public ::testing::Test {
protected:
void SetUp() override {
// 每个测试前执行
db = std::make_unique<Database>(":memory:");
db->initialize();
}
void TearDown() override {
// 每个测试后执行(通常不需要,RAII 会处理)
}
std::unique_ptr<Database> db;
};
TEST_F(DatabaseTest, InsertsUserCorrectly) {
User user{1, "John"};
db->insert(user);
EXPECT_EQ(db->getUser(1).name, "John");
}
// 使用正确的断言宏
EXPECT_EQ(actual, expected); // 相等
EXPECT_NE(actual, expected); // 不相等
EXPECT_LT(actual, expected); // 小于
EXPECT_TRUE(condition); // 布尔值
EXPECT_THROW(expr, ExcType); // 抛出特定异常
EXPECT_NO_THROW(expr); // 不抛出异常
// EXPECT vs ASSERT
EXPECT_EQ(x, y); // 失败后继续执行
ASSERT_EQ(x, y); // 失败后立即停止(用于致命错误)
// 定义接口
class IDatabase {
public:
virtual ~IDatabase() = default;
virtual User getUser(int id) = 0;
virtual void saveUser(const User& user) = 0;
};
// Mock 实现
class MockDatabase : public IDatabase {
public:
MOCK_METHOD(User, getUser, (int id), (override));
MOCK_METHOD(void, saveUser, (const User& user), (override));
};
// 测试中使用
TEST(UserServiceTest, FetchesUserFromDatabase) {
// 1. 创建 Mock 对象
MockDatabase mockDb;
// 2. 设置期望:当调用 getUser(1) 时,返回指定的 User 对象
EXPECT_CALL(mockDb, getUser(1))
.WillOnce(Return(User{1, "John"}));
// 3. 将 Mock 注入到被测试的服务中 (依赖注入)
UserService service(&mockDb);
// 4. 执行被测代码
User user = service.getUser(1);
// 5. 验证结果
EXPECT_EQ(user.name, "John");
}
在标记工作完成之前:
无法勾选所有框?你跳过了TDD。重新开始。
| 问题 | 解决方案 | |------|---------| | 不知道如何测试 | 编写期望的API。先编写断言。询问你的人类伙伴。 | | 测试太复杂 | 设计太复杂。简化接口。 | | 必须mock所有东西 | 代码耦合度太高。使用依赖注入。 | | 测试设置巨大 | 提取辅助函数。仍然复杂?简化设计。 |
发现bug?编写重现它的失败测试。遵循TDD循环。测试证明修复并防止回归。
绝不要在没有测试的情况下修复bug。
在编写或修改测试、添加mock、或想要在生产代码中添加仅用于测试的方法时,阅读@references/testing-anti-patterns.md以避免常见陷阱:
tools
当用户明确使用该skill时触发
development
对 C++ Windows 客户端项目进行深度代码审查。涵盖安全性、资源管理、字符串编码、 内部公共库使用合规性、可测试性设计、并发安全、Google C++ 规范、Windows 平台特有问题 及性能等维度。适用于 PR 审查、模块上线前检查、技术债务梳理等场景。
development
深度分析代码库的核心业务流程、前后端API接口交互、数据格式规范以及C++类的功能实现细节。适用于梳理业务逻辑、理解接口契约、生成接口文档时使用。
development
Maintainer-only workflow for handling GitHub Secret Scanning alerts on OpenClaw. Use when Codex needs to triage, redact, clean up, and resolve secret leakage found in issue comments, issue bodies, PR comments, or other GitHub content.