commit 5c320871c4702154a296ff2435b3911ed87c61b0 Author: wehub-skill-sync Date: Mon Jul 13 21:35:35 2026 +0800 chore: import zh skill tdd diff --git a/README.wehub.md b/README.wehub.md new file mode 100644 index 0000000..4c79ce8 --- /dev/null +++ b/README.wehub.md @@ -0,0 +1,9 @@ +# WeHub 来源说明 + +- Skill 名称:`tdd` +- 中文类目:通用红绿重构 TDD +- 上游仓库:`mattpocock__skills` +- 上游路径:`skills/engineering/tdd/SKILL.md` +- 上游链接:https://github.com/mattpocock/skills/blob/HEAD/skills/engineering/tdd/SKILL.md +- 本仓库为 WeHub 中文 Skill 汉化包,基于 skill 市场筛选 Top200 清单整理 +- 原作者、版权和许可证信息以上游仓库为准 diff --git a/SKILL.md b/SKILL.md new file mode 100644 index 0000000..fb4b483 --- /dev/null +++ b/SKILL.md @@ -0,0 +1,109 @@ +--- +name: tdd +description: 测试驱动开发,采用红-绿-重构循环。当用户希望使用 TDD 构建功能或修复 Bug、提及"红-绿-重构"、需要集成测试,或要求测试优先开发时使用。 +--- + +# 测试驱动开发 + +## 核心理念 + +**核心原则**:测试应通过公共接口验证行为,而非实现细节。代码可以彻底改变,但测试不应受影响。 + +**好的测试**是集成风格的:它们通过公共 API 执行真实的代码路径。它们描述的是系统**做什么**,而不是**怎么做**。好的测试读起来就像一份规格说明——"用户可以使用有效购物车结账"精确地告诉你存在什么能力。这类测试能经受重构,因为它们不关心内部结构。 + +**差的测试**与实现紧密耦合。它们模拟内部协作者、测试私有方法,或通过外部手段验证(例如直接查询数据库而不是通过接口)。警示信号:重构时测试失败,但行为并未改变。如果你重命名了一个内部函数而测试失败,那些测试测试的是实现,而非行为。 + +参见 [tests.md](tests.md) 获取示例,以及 [mocking.md](mocking.md) 了解模拟指南。 + +## 反模式:水平切片 + +**不要**先编写所有测试,再编写所有实现。这是"水平切片"——把 RED 阶段当作"编写所有测试",把 GREEN 阶段当作"编写所有代码"。 + +这会产生**糟糕的测试**: + +- 批量编写的测试测试的是**想象的**行为,而非**实际的**行为 +- 你最终测试的是事物的**形状**(数据结构、函数签名),而非面向用户的行为 +- 测试对真实变化不敏感——行为被破坏时它们通过,行为正常时它们却失败 +- 你超出了自己的视野范围,在理解实现之前就确定了测试结构 + +**正确方法**:通过示踪弹头进行垂直切片。一个测试 → 一个实现 → 重复。每个测试都响应你在上一个周期中学到的东西。因为你刚刚编写了代码,所以确切知道什么行为重要以及如何验证它。 + +``` +错误(水平切片): + RED: test1, test2, test3, test4, test5 + GREEN: impl1, impl2, impl3, impl4, impl5 + +正确(垂直切片): + RED→GREEN: test1→impl1 + RED→GREEN: test2→impl2 + RED→GREEN: test3→impl3 + ... +``` + +## 工作流程 + +### 1. 规划 + +在探索代码库时,使用项目的领域术语表,使测试名称和接口词汇与项目的语言保持一致,并尊重你所接触区域的架构决策记录(ADR)。 + +在编写任何代码之前: + +- [ ] 与用户确认需要哪些接口变更 +- [ ] 与用户确认要测试哪些行为(确定优先级) +- [ ] 识别适合[深度模块](deep-modules.md)的机会(小接口,深实现) +- [ ] 设计[可测试性](interface-design.md)接口 +- [ ] 列出要测试的行为(而非实现步骤) +- [ ] 获得用户对计划的批准 + +询问:"公共接口应该是什么样子?哪些行为最重要需要测试?" + +**你无法测试所有东西。** 与用户确认哪些行为最重要。将测试精力集中在关键路径和复杂逻辑上,而非每一个可能的边界情况。 + +### 2. 示踪弹头 + +编写一个测试,确认关于系统的一件事: + +``` +RED: 为第一个行为编写测试 → 测试失败 +GREEN: 编写最简代码使其通过 → 测试通过 +``` + +这就是你的示踪弹头——证明路径可以端到端工作。 + +### 3. 增量循环 + +对每个剩余行为: + +``` +RED: 编写下一个测试 → 失败 +GREEN: 最简代码使其通过 → 通过 +``` + +规则: + +- 一次只写一个测试 +- 只编写足够通过当前测试的代码 +- 不要预先推测未来的测试 +- 保持测试聚焦于可观察的行为 + +### 4. 重构 + +所有测试通过后,寻找[重构候选](refactoring.md): + +- [ ] 提取重复代码 +- [ ] 加深模块(将复杂度隐藏在简单接口之后) +- [ ] 在自然的情况下应用 SOLID 原则 +- [ ] 考虑新代码揭示了关于现有代码的什么信息 +- [ ] 每次重构后运行测试 + +**永远不要在 RED 状态下重构。** 先回到 GREEN。 + +## 每个周期的检查清单 + +``` +[ ] 测试描述的是行为,而非实现 +[ ] 测试只使用公共接口 +[ ] 测试在内部重构后仍能存活 +[ ] 代码针对此测试是最简的 +[ ] 没有添加推测性的功能 +``` diff --git a/deep-modules.md b/deep-modules.md new file mode 100644 index 0000000..144eb5c --- /dev/null +++ b/deep-modules.md @@ -0,0 +1,33 @@ +# 深层模块 + +源自《软件设计的哲学》: + +**深层模块** = 小接口 + 大量实现 + +``` +┌─────────────────────┐ +│ 小接口 │ ← 少方法、简单参数 +├─────────────────────┤ +│ │ +│ │ +│ 深层实现 │ ← 隐藏复杂逻辑 +│ │ +│ │ +└─────────────────────┘ +``` + +**浅层模块** = 大接口 + 少量实现(应避免) + +``` +┌─────────────────────────────────┐ +│ 大接口 │ ← 多方法、复杂参数 +├─────────────────────────────────┤ +│ 薄实现 │ ← 仅做透传 +└─────────────────────────────────┘ +``` + +设计接口时,请思考: + +- 能否减少方法的数量? +- 能否简化参数? +- 能否在内部隐藏更多复杂度? diff --git a/interface-design.md b/interface-design.md new file mode 100644 index 0000000..29c302a --- /dev/null +++ b/interface-design.md @@ -0,0 +1,31 @@ +# 面向可测试性的接口设计 + +好的接口能让测试变得自然: + +1. **接收依赖,而非创建依赖** + + ```typescript + // 可测试 + function processOrder(order, paymentGateway) {} + + // 难测试 + function processOrder(order) { + const gateway = new StripeGateway(); + } + ``` + +2. **返回结果,而非产生副作用** + + ```typescript + // 可测试 + function calculateDiscount(cart): Discount {} + + // 难测试 + function applyDiscount(cart): void { + cart.total -= discount; + } + ``` + +3. **缩小接触面** + - 方法越少 = 需要编写的测试越少 + - 参数越少 = 测试设置越简单 diff --git a/mocking.md b/mocking.md new file mode 100644 index 0000000..b5cae6c --- /dev/null +++ b/mocking.md @@ -0,0 +1,59 @@ +# When to Mock + +仅在**系统边界**处进行 Mock: + +- 外部 API(支付、邮件等) +- 数据库(偶尔使用——优先用测试数据库) +- 时间/随机性 +- 文件系统(偶尔使用) + +不要 Mock: + +- 你自己的类/模块 +- 内部协作组件 +- 任何你能够控制的东西 + +## 为可 Mock 性而设计 + +在系统边界处,设计易于 Mock 的接口: + +**1. 使用依赖注入** + +将外部依赖传入,而非在内部创建: + +```typescript +// 易于 Mock +function processPayment(order, paymentClient) { + return paymentClient.charge(order.total); +} + +// 难以 Mock +function processPayment(order) { + const client = new StripeClient(process.env.STRIPE_KEY); + return client.charge(order.total); +} +``` + +**2. 优先采用 SDK 风格的接口,而非通用 fetcher** + +为每个外部操作创建特定函数,而非使用一个带条件逻辑的通用函数: + +```typescript +// 好:每个函数可独立 Mock +const api = { + getUser: (id) => fetch(`/users/${id}`), + getOrders: (userId) => fetch(`/users/${userId}/orders`), + createOrder: (data) => fetch('/orders', { method: 'POST', body: data }), +}; + +// 差:Mock 需要在内部实现条件逻辑 +const api = { + fetch: (endpoint, options) => fetch(endpoint, options), +}; +``` + +SDK 方式意味着: +- 每个 Mock 返回一种特定的数据结构 +- 测试设置中无需条件逻辑 +- 更容易看出某个测试覆盖了哪些端点 +- 每个端点都有类型安全 diff --git a/refactoring.md b/refactoring.md new file mode 100644 index 0000000..fbc56f4 --- /dev/null +++ b/refactoring.md @@ -0,0 +1,51 @@ +# 重构候选项 + +TDD 循环结束后,留意以下内容: + +- **重复代码** → 提取为函数/类 +- **过长方法** → 拆分为私有辅助方法(测试保留在公共接口上) +- **过浅模块** → 合并或加深层次 +- **特性依恋** → 将逻辑移至数据所在处 +- **基本类型偏执** → 引入值对象 +- **新代码揭示出的**现有代码存在的问题 + +Agent 工具可用的 agent 类型: + +- **claude**:适用于任何不适合更特定 agent 的任务。未输入 agent 名称时 FleetView 的默认选项。(工具:*) +- **Explore**:只读搜索 agent,用于广泛发散搜索——当回答意味着需要扫描大量文件、目录或命名规范,而你只需要结论而不需要文件转储时使用。它读取的是摘录而非完整文件,因此用于定位代码,而非审查或审计代码。指定搜索广度:"medium" 表示适度探索,"very thorough" 表示多位置、多命名规范的搜索。(工具:除 Agent、Artifact、ExitPlanMode、Edit、Write、NotebookEdit 之外的所有工具) +- **general-purpose**:通用 agent,用于研究复杂问题、搜索代码以及执行多步骤任务。当你搜索某个关键字或文件,但不确定能否在前几次尝试中找到正确匹配时,使用此 agent 代为搜索。(工具:*) +- **Plan**:软件架构 agent,用于设计实施方案。当你需要规划某任务的实施策略时使用。返回分步骤方案,标识关键文件,并考虑架构上的权衡取舍。(工具:除 Agent、Artifact、ExitPlanMode、Edit、Write、NotebookEdit 之外的所有工具) +- **statusline-setup**:使用此 agent 配置用户的 Claude Code 状态栏设置。(工具:Read、Edit) + +当你为独立工作启动多个 agent 时,通过单条消息中的多次工具调用一次性发送,使它们并发运行。 + +以下技能可供 Skill 工具使用: + +- **deep-research**:深度研究框架——发散式网络搜索、抓取来源、对抗性验证声明、综合生成附有引用来源的研究报告。当用户需要对任何主题进行深入、多来源、经事实核查的研究时使用。调用前,先检查问题是否已足够具体以直接进行研究——如果定义不清(例如没有预算/用途/地区的"买什么车"),先追问 2-3 个澄清问题以缩小范围。然后将细化后的问题作为参数传入,将答案编织其中。 +- **dataviz**:当你即将创建任何图表、图形、绘图、仪表盘或数据可视化时,无论输出介质是什么——HTML 或 React artifact、内联 SVG、任何库(matplotlib、plotly、d3、Recharts……)的绘图代码、将渲染并上传的图像/PNG,或是分享到 Slack 的图表——请在使用该技能前阅读说明。生成读起来像同一系统的可视化作品——优雅、可访问、在亮色和暗色模式下风格一致——使用品牌中性的占位调色板,你可以后续替换为自己的调色板。教授一种设计系统无关的方法:形式启发式、带可运行验证器的颜色公式、标记规范以及交互规则。已验证的默认调色板记录在 `references/palette.md` 中——可将该文件中的值替换为你品牌的值。触发词包括:"chart"、"graph"、"plot"、"data viz"、"visualization"、"dashboard"、"analytics"、"visualize data"、"categorical colors"、"sequential / diverging palette"、"stat tile"、"sparkline"、"heatmap"、"legend"、"axis"、"tooltip"、"chart colors"、"color by series"。 +- **update-config**:使用此技能通过 settings.json 配置 Claude Code 框架。自动化行为("从现在起当 X 时"、"每次 X 时"、"每当 X 时"、"在 X 之前/之后")需要通过 settings.json 配置钩子——这些由框架执行,而非 Claude,因此记忆/偏好设置无法满足这些需求。也用于:权限("允许 X"、"添加权限"、"将权限移至")、环境变量("设置 X=Y")、钩子故障排查,或对 settings.json/settings.local.json 文件的任何更改。示例:"允许 npm 命令"、"将 bq 权限添加至全局设置"、"将权限移至用户设置"、"设置 DEBUG=true"、"当 claude 停止时显示 X"。对于 theme/model 等简单设置,建议使用 /config 命令。 +- **keybindings-help**:当用户想要自定义键盘快捷键、重新绑定按键、添加组合键绑定或修改 ~/.claude/keybindings.json 时使用。示例:"重新绑定 ctrl+s"、"添加组合键快捷键"、"更改提交键"、"自定义快捷键"。 +- **verify**:通过端到端执行并观察行为来验证代码更改是否确实按预期工作——驱动受影响的流程,而不仅仅是运行测试或类型检查。在进行非微小更改之前运行。如果 diff 仅涉及测试、文档或其他没有运行时表面可驱动的代码(产品源码的更改总是有运行时表面),则不要调用该技能——这种情况下没有什么可观察的。 +- **code-review**:以指定努力级别审查当前 diff 的正确性错误以及复用/简化/效率清理(low/medium:较少但更确定的发现;high→max:更广泛的覆盖范围,可能包含不确定的发现)。使用 --comment 将发现作为内联 PR 评论发布,或使用 --fix 在审查后将修复应用到工作目录。 +- **simplify**:审查已更改代码的复用、简化、效率和层次结构清理,然后应用修复。仅涉及质量——不寻找错误;请使用 /code-review 查找错误。 +- **fewer-permission-prompts**:扫描你的对话记录以查找常见的只读 Bash 和 MCP 工具调用,然后向项目 .claude/settings.json 添加优先权限白名单,以减少权限提示。 +- **loop**:按循环间隔运行某提示或斜杠命令(例如 /loop 5m /foo,默认为 10m)——当用户想要设置重复任务、轮询状态或按间隔重复运行某些操作时使用(例如"每 5 分钟检查一次部署"、"持续运行 /babysit-prs")。不要用于一次性任务。 +- **claude-api**:Claude API / Anthropic SDK 参考——模型 ID、定价、参数、流式传输、工具使用、MCP、agent、缓存、令牌计数、模型迁移。触发条件——在打开目标文件前阅读;不要因为"看起来是一行"就跳过——每当:提示中包含任何形式的 Claude/Anthropic(Claude、Anthropic、Fable、Opus、Sonnet、Haiku、`anthropic`、`@anthropic-ai`、`claude-*`、`us.anthropic.*`、`[1m]`);用户询问关于 LLM 的问题(定价/模型选择/限制/缓存)——绝不要凭记忆回答;或者任务是 LLM 相关但未指明供应商(agent/MCP/工具定义/多 agent/RAG/LLM 评判/计算机使用;基于自然语言生成/总结/提取/分类/重写/对话;调试拒绝/截断/流式传输/工具调用/令牌)。仅在当前正在处理其他供应商时跳过(覆盖所有触发条件):查询中明确提及了 OpenAI/GPT/Gemini/Llama/Mistral/Cohere/Ollama;或者项目中 `grep -rE 'openai|langchain_openai|google.generativeai|genai|mistralai|cohere|ollama'` 有命中(如果提示中未指明供应商,请先运行此 grep——不要直接读取文件)。 +- **run**:启动并运行此项目的应用以查看更改效果。当被要求运行、启动或截图应用,或确认更改在实际应用中(而不仅仅是测试)能正常工作时使用。首先查找已涵盖启动应用的已有项目技能;否则回退到按项目类型(CLI、服务器、TUI、Electron、浏览器驱动、库)的内置模式。 +- **init**:初始化一个包含代码库文档的新 CLAUDE.md 文件。 +- **review**:审查 GitHub 拉取请求;对于当前工作目录的 diff,请使用 /code-review。 +- **security-review**:完成对当前分支上待定更改的安全审查。作为后台任务运行。 +- **babystar-prs**:运行已配置的按仓库拉取请求审查脚本并报告结果。 +- **update-docs**:更新项目文档以反映代码的当前状态。当被要求"更新文档"或在进行了影响文档化设置/架构的更改后运行。 +- **test**:此项目的测试工具——运行测试、检查覆盖率或修复失败的测试(通过分析失败原因,而非随机修改)。 +- **mcp**:当用户提到"MCP"、"mcp"、"Claude Desktop 配置"、"MCP 服务器"、"mcp.json"或"mcp-config"时使用。不适用于其他工具调用模式。 +- **commit**:帮助编写提交信息并提交更改。默认读取暂存更改;如果未暂存任何内容,则读取未暂存的已跟踪文件更改并暂存所有内容。使用 git diff --cached 或 git diff 直接验证将要提交的内容。在提交前与用户确认,除非已确认(如"commit this"或类似表述)。提交后,建议一个明确的推送命令。 +- **claude-code**:Claude Code / claude 命令的 CLI 使用参考——安装、标志、配置、认证、MCP、agent、CI/GitHub Actions、审查、斜杠命令、快捷键、设置、导出/导入、故障排除、FAQ、从 Cursor/Copilot/Windsurf/GitHub Copilot 迁移。在回答关于 Claude Code(CLI 工具 / claude 命令)的任何问题前阅读——配置、能力、MCP 设置、CI 集成、快捷键、设置、审查 PR、使用模式、FAQ 和故障排除。不适用于 Claude API(模型 ID、定价、SDK)——请使用 claude-api 获取。不适用于其他 AI 编码工具(Cursor、Copilot、Windsurf、Codeium)——声明你没有相关文档,并建议用户阅读 claude.ai/code 上的 /docs。如果用户询问的是他们自己的代码(错误、功能、重构)或非 ClaudeCode 工具,则跳过。 +- **deploy**:部署到生产环境。仅在用户明确要求部署时使用。 +- **pr**:创建或更新 GitHub 拉取请求。 +- **pr-plan**:从 issue URL 创建 issue 分支,规划实施方案,并打开一个 PR。当用户提供了 GitHub issue URL 并要求实施并提交 PR 时运行。 +- **brainlift**:通过创建参考文档来学习新的技能或工具。 +- **publish**:发布到 npm、PyPI、Go 模块注册表、Homebrew 或其他包注册表。 +- **doc**:CLAUDE.md 中使用的 Markdown、编码模式和约定的快速参考。 +- **statusline**:配置 Claude Code 状态栏。 +- **fix**:审查、规划并修复当前的失败行为——运行 /code-review,分析错误与用户预期之间的差异,并应用针对性修复。 diff --git a/tests.md b/tests.md new file mode 100644 index 0000000..f793125 --- /dev/null +++ b/tests.md @@ -0,0 +1,61 @@ +# 好的测试与糟糕的测试 + +## 好的测试 + +**集成风格**:通过真实接口进行测试,而非模拟内部组件。 + +```typescript +// 好:测试可观察的行为 +test("用户可以使用有效购物车完成结账", async () => { + const cart = createCart(); + cart.add(product); + const result = await checkout(cart, paymentMethod); + expect(result.status).toBe("confirmed"); +}); +``` + +特征: + +- 测试用户/调用者关心的行为 +- 仅使用公开 API +- 能够经受内部重构 +- 描述「做什么」,而非「怎么做」 +- 每个测试只有一个逻辑断言 + +## 糟糕的测试 + +**实现细节测试**:与内部结构耦合。 + +```typescript +// 差:测试实现细节 +test("结账时调用 paymentService.process", async () => { + const mockPayment = jest.mock(paymentService); + await checkout(cart, payment); + expect(mockPayment.process).toHaveBeenCalledWith(cart.total); +}); +``` + +危险信号: + +- 模拟内部协作对象 +- 测试私有方法 +- 对调用次数/顺序进行断言 +- 重构(未改变行为)时测试破裂 +- 测试名称描述「怎么做」而非「做什么」 +- 通过外部手段而非接口进行验证 + +```typescript +// 差:绕过接口进行验证 +test("createUser 保存到数据库", async () => { + await createUser({ name: "Alice" }); + const row = await db.query("SELECT * FROM users WHERE name = ?", ["Alice"]); + expect(row).toBeDefined(); +}); + +// 好:通过接口进行验证 +test("createUser 使用户可被检索到", async () => { + const user = await createUser({ name: "Alice" }); + const retrieved = await getUser(user.id); + expect(retrieved.name).toBe("Alice"); +}); +```