ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

PentestGPT 仓库中的测试驱动开发(TDD)实战指南:以行为为中心的 Red-Green-Refactor 工作流

PentestGPT 仓库中的测试驱动开发(TDD)实战指南:以行为为中心的 Red-Green-Refactor 工作流 PentestGPT 仓库中的测试驱动开发TDD实战指南以行为为中心的 Red-Green-Refactor 工作流【免费下载链接】PentestGPTAutomated Penetration Testing Agentic Framework Powered by Large Language Models项目地址: https://gitcode.com/GitHub_Trending/pe/PentestGPT本文是 PentestGPT 仓库内建 TDD 技能.agents/skills/tdd/的完整解读与实战扩展。它面向在本仓库基于 LLM 的自动化渗透测试 Agent 框架中编写、重构与评审测试的开发者与 AI 编码 Agent覆盖测试验证行为而非实现的核心哲学、水平切片反模式、纵向切片工作流Red-Green-Refactor、Mock 边界与可测性设计并结合仓库中pentestgpt_agent的 pytest 配置与真实测试用例说明如何在 AI Agent 项目中落地。读完本文你将掌握一套可复制的行为级测试方法论并能直接对照 PentestGPT 现有测试判断其好坏。核心哲学测试验证行为而非实现TDD 技能文档开篇即亮明立场核心原则测试应该通过公开接口public interfaces验证行为而不是验证实现细节。代码可以彻底改变但测试不应该跟着变。这句话是整套方法论的地基可以从两个维度拆解好测试是集成风格integration-style的它们通过公开 API 走真实的代码路径描述的是系统做什么what而不是怎么做how。一个形如 user can checkout with valid cart 的测试名读起来就像一份规格说明——它精确告诉你系统具备什么能力。这类测试能扛住重构因为它们根本不关心内部结构。坏测试与实现耦合它们 mock 内部协作者、测试私有方法或者绕过接口用外部手段验证比如直接查数据库而不是走接口。判定的警告信号是你重构了内部实现、行为没有任何变化但测试挂了。如果你只是重命名一个内部函数测试就失败那么这些测试测的是实现不是行为。判断一个测试好坏可以对照tests.md中给出的正反例。好的测试示例原文档 TypeScript 原文// GOOD: Tests observable behavior test(user can checkout with valid cart, async () { const cart createCart(); cart.add(product); const result await checkout(cart, paymentMethod); expect(result.status).toBe(confirmed); });坏测试的典型特征tests.mdMock 内部协作者测试私有方法断言调用次数/调用顺序行为不变、仅重构就挂测试名描述的是 HOW 而不是 WHAT绕过接口、通过外部手段验证另一个经典反例是绕过接口查库tests.md// BAD: Bypasses interface to verify test(createUser saves to database, async () { await createUser({ name: Alice }); const row await db.query(SELECT * FROM users WHERE name ?, [Alice]); expect(row).toBeDefined(); }); // GOOD: Verifies through interface test(createUser makes user retrievable, async () { const user await createUser({ name: Alice }); const retrieved await getUser(user.id); expect(retrieved.name).toBe(Alice); });注意好版本的差别它只通过公开接口createUser/getUser来回验证数据库怎么存是实现的自由。在 AI Agent 项目中这个原则尤其重要——因为你调用的 LLM 后端、Provider 适配器随时可能换实现测试必须钉在行为上。反模式水平切片Horizontal Slices原文档明确警告一种最常见的 TDD 误用——先把所有测试写完再写全部实现。这就是水平切片把 RED 阶段理解为写所有测试把 GREEN 阶段理解为写所有代码。这会产出糟糕的测试原因有四批量写出的测试测的是想象的行为而不是实际的行为你最终在测东西的形状数据结构、函数签名而不是用户可见的行为测试对真实变化变得迟钝——行为坏了测试却通过行为正常测试反而失败你跑出了车灯照射范围outrun your headlights在理解实现之前就锁定了测试结构。正确做法是纵向切片 曳光弹tracer bullets一个测试 → 一个实现 → 重复。每个测试都回应你在上一轮循环中学到的东西。因为刚写完代码你确切知道什么行为是重要的、该怎么验证它。原文档给出了直观对照WRONG (horizontal): RED: test1, test2, test3, test4, test5 GREEN: impl1, impl2, impl3, impl4, impl5 RIGHT (vertical): RED→GREEN: test1→impl1 RED→GREEN: test2→impl2 RED→GREEN: test3→impl3 ...工作流四步 Red-Green-Refactor第 1 步规划Planning动手写任何代码之前先做侦察与对齐。技能文档特别要求探索代码库时先读CONTEXT.md如果存在让测试命名和接口词汇与项目的领域语言保持一致同时尊重你正在改动的区域中的 ADR。PentestGPT 仓库里pentestgpt_agent/CONTEXT.md就是活的范例——它定义了 Supervisor、Executor、Memory Kernel、Decision Cycle、Task、Attempt、Evidence、Observation 等一整套领域词汇测试里出现的discover、TEST、RunSpec、commit_plan等标识符都来自这份词汇表。规划阶段必须完成的对齐清单原文档与用户确认需要什么接口变更与用户确认要测试哪些行为排优先级识别深模块机会小接口、深实现——运行/codebase-design技能获取词汇与可测试性检查列出要测试的行为不是实现步骤让用户批准计划同时记住一句告诫你不可能测所有东西。与用户确认哪些行为最重要把测试精力集中在关键路径和复杂逻辑上而不是穷举每个边界情形。规划时问一句公开接口应该长什么样哪些行为最值得测第 2 步曳光弹Tracer Bullet写一个只确认一件事的测试RED: Write test for first behavior → test fails GREEN: Write minimal code to pass → test passes这是你的曳光弹——证明整条路径端到端是通的。曳光弹的价值在于尽早暴露集成问题接口形状、依赖注入方式、环境假设而不是等到写完全部测试才发现地基塌了。第 3 步增量循环Incremental Loop对剩下的每个行为重复RED: Write next test → fails GREEN: Minimal code to pass → passes四条铁律原文档一次只写一个测试只写足够通过当前测试的代码不要提前臆测未来的测试让测试始终聚焦在可观察的行为上第 4 步重构Refactor所有测试通过后对照refactoring.md的重构候选清单原文档提取重复代码Duplication → Extract function/class加深模块把复杂度藏到简单接口后面在自然之处应用 SOLID 原则思考新代码揭示了旧代码的什么问题Existing code the new code reveals as problematic每步重构后运行测试重构候选中还包括长方法拆成私有辅助函数但测试仍然留在公开接口上、浅模块合并或加深、**特性依恋Feature envy**把逻辑移到数据所在处、**原始类型偏执Primitive obsession**引入值对象。铁律绝不能在 RED 状态下重构。先回到 GREEN 再说。每周期检查清单每个 Red-Green 周期结束时对照这份清单原文档[ ] Test describes behavior, not implementation [ ] Test uses public interface only [ ] Test would survive internal refactor [ ] Code is minimal for this test [ ] No speculative features added何时 Mock只在系统边界mocking.md给出了一条清晰的 Mock 纪律只在这些系统边界 mock外部 API支付、邮件等数据库有时——优先用测试数据库时间/随机性文件系统有时不要 mock你自己的类/模块内部协作者任何你掌控的东西这条纪律与测试通过公开接口一脉相承mock 是让你绕过外部不确定性的手段不是让你绕开自己的实现的捷径。为可 mock 性而设计在系统边界处要把接口设计得易于 mock原文档给出两条原则1. 用依赖注入而不是在内部创建依赖// Easy to mock function processPayment(order, paymentClient) { return paymentClient.charge(order.total); } // Hard to mock function processPayment(order) { const client new StripeClient(process.env.STRIPE_KEY); return client.charge(order.total); }2. 优先 SDK 风格接口而不是通用 fetcher// GOOD: Each function is independently mockable const api { getUser: (id) fetch(/users/${id}), getOrders: (userId) fetch(/users/${userId}/orders), createOrder: (data) fetch(/orders, { method: POST, body: data }), }; // BAD: Mocking requires conditional logic inside the mock const api { fetch: (endpoint, options) fetch(endpoint, options), };SDK 风格的好处原文档每个 mock 只返回一种特定形状、测试设置里没有条件逻辑、更容易看出一个测试在调用哪些端点、每个端点都有类型安全。这条原则在 PentestGPT 的unified_agent/层有直接印证——它用UnifiedAgent的stream()方法把 Claude Code / Codex 的事件流归一化测试侧只需要为这个端口提供一个实现stream()的假后端即可见下文 test_loop 示例。深模块与可测试性来自 /codebase-design 的共享词汇TDD 技能的规划阶段会调用/codebase-design技能获取词汇其完整定义见.agents/skills/codebase-design/SKILL.md。这套词汇与 TDD 的配合关系非常紧密Module模块任何拥有接口与实现的东西刻意与规模无关函数、类、包、跨层切片皆可。Interface接口调用者必须知道的一切——类型签名、不变量、排序约束、错误模式、所需配置、性能特征。注意它比 TypeScript 的interface关键字宽得多。Depth深度调用者或测试每学一单位接口就能触发的行为量。深模块小接口大量实现浅模块大接口薄实现要避免。Seam接缝Michael Feathers不编辑某处就能改变行为的位置即模块接口所在处。Adapter适配器在接缝处满足接口的具体事物描述的是角色而非内容。三条对测试至关重要的原则SKILL.md深度是接口的属性不是实现的属性深模块内部可以由小的、可 mock 的、可替换的部分组成——只是它们不属于接口。接口即测试面The interface is the test surface调用者和测试穿过同一个接缝。如果你想越过接口去测说明模块形状不对。一个适配器意味着假想的接缝两个适配器才是真实的接缝除非确实有东西跨接缝变化否则别引入接缝。三条可测试性设计准则SKILL.md接受依赖不要创建依赖processOrder(order, paymentGateway)可测内部new StripeGateway()难测返回结果不要产生副作用calculateDiscount(cart): Discount可测applyDiscount(cart): void难测小表面积——方法越少、参数越少测试越少、测试设置越简单。DEEPENING.md还给出了依赖分类与测试策略进程内依赖总是可加深本地可替身依赖如测试数据库用替身测试远程但自有的依赖在接缝处定义端口port生产用 HTTP/gRPC 适配器、测试用内存适配器真正的外部第三方服务才需要 mock 适配器。测试策略上要替换而不是分层replace, dont layer一旦深模块接口层的测试存在旧的浅模块单测就成为废料应该删除。在 PentestGPT 仓库中落地pytest 配置与真实测试佐证TDD 技能文档本身是方法论仓库则提供了完整的落地上下文。PentestGPT 的测试基础设施由根目录 pyproject.toml 中的 pytest 配置驱动[tool.pytest.ini_options] minversion 7.0 addopts [ --strict-markers, --strict-config, ] testpaths [tests] pythonpath [.] asyncio_mode auto markers [ unit: Unit tests (fast, no external dependencies), integration: Integration tests (may use mocks), docker: requires a local Docker daemon and may build or start containers, slow: Slow tests (skip with -m not slow), live: Live smoke tests that require backend auth and may spend tokens, ]注意--strict-markers强制所有 marker 都在此声明asyncio_mode auto允许在异步 Agent 代码上直接写 pytest 测试。marker 体系本身就是在为行为分级服务unit快速无外部依赖、integration允许使用 mock、live需要真实后端凭证并消耗 token——这与 TDD 技能在边界处 mock的纪律完全同构。行为级测试范例test_memory.pypentestgpt_agent/tests/test_memory.py是通过公开接口验证行为的绝佳范本。它把MemoryKernelSQLite 记忆内核当黑盒通过create_run/open_run/commit_plan/commit_execution/snapshot这些公开接口驱动断言的是领域行为而非内部结构。例如test_open_run_reuses_only_an_identical_persisted_spec用相同RunSpec重开运行必须复用改了 goal 则必须抛RunSpecMismatchError——测试名描述的是WHAT运行规格的复用规则而不是MemoryKernel 内部怎么比对。test_run_spec_memory_inputs_are_bounded参数化测试验证 goal 超过 4000 字符、allowed_targets 超过 16 个时抛出ValueError——这是从 CONTEXT.md 检索策略/边界不变量落下来的行为契约。test_progress_task_can_be_selected_again_and_completed完整走一遍PROGRESS 尝试→再次选择→DONE 完成的决策周期最后断言{task.id: task.status}映射——断言的是任务状态机行为而非实现。test_progress_cannot_reopen_a_task_past_its_attempt_budget验证超过尝试预算后任务被标记FAILED、过渡记录里带上attempt_limit_reached与evidence_unresolved细节——这直接对应 CONTEXT.md 中语义进展受任务总尝试预算约束的不变量。这些测试无一 mockMemoryKernel的内部协作者全部通过公开 API 断言可观察结果因此即使内核内部重构比如换一种 SQLite 表结构只要行为不变测试就不需要动。集成风格测试范例test_loop.py 的脚本化后端pentestgpt_agent/tests/test_loop.py展示了在系统边界处提供假适配器的集成式测试。它定义了TwoDecisionSupervisorBackend只实现name属性和stream(prompt, opts)方法用脚本化的SupervisorDecision数据流驱动PentestLoop——这正是 mocking.md 的SDK 风格接口 每个 mock 返回特定形状在 Python 端的等价物。测试通过 loop.py 的PentestLoop公开入口跑完整循环class PentestLoop: def __init__( self, *, memory: MemoryKernel, supervisor: Supervisor, executor: Executor, traces: TraceStore, max_decisions: int 20, max_supervisor_attempts: int 2, ) - None:注意构造函数的形状本身就是 TDD 技能与 codebase-design 原则的产物memory/supervisor/executor/traces全部由外部注入接受依赖不创建依赖测试因此可以传入内存MemoryKernel、脚本化 Supervisor 后端和TraceStore替身而不需要任何网络或 LLM 调用max_decisions20、max_supervisor_attempts2则把循环行为约束成可预测的有限状态机。测试名如test_loop...系列描述的也是循环会怎么做决策-执行-重试-完成而不是某个内部函数怎么被调用。与领域语言对齐CONTEXT.md 的作用技能文档规划阶段要求读 CONTEXT.md 使测试命名与接口词汇匹配项目领域语言。在pentestgpt_agent下CONTEXT.md 提供了完整的领域词汇与不变量Supervisor提出并选择一个任务、Executor执行租约任务、Memory Kernel确定性 SQLite 权威、Decision Cycle、Task、Attempt、Evidence目标输出、Observation有界精确切片、Transition追加式规范账本……仓库测试中的标识符TaskKind.DISCOVER、ExecutionOutcome.PROGRESS、RunStatus.RUNNING、compile_plan、commit_execution全部使用这套词汇使得测试读起来像规格说明成为可能。这也是 TDD 技能与仓库实际协作方式的最佳示范先有领域词汇表再有行为级测试然后才是实现。结语把 TDD 技能用起来的三个抓手把整套技能收敛成可在 PentestGPT 仓库中立刻执行的三个抓手测试永远钉在行为上先看被测模块的公开接口如果测试需要 mock 内部协作者、调私有方法、或者碰数据库/文件系统才能写出来先停下来问接口设计是否合理而不是硬写。用纵向切片代替批量一个测试、一段最小实现、一次重构循环往复借助max_decisions之类的构造参数把被测系统收敛成有限、可预测的状态机测试才写得干净。把 CONTEXT.md 当测试词汇表新增测试前先翻领域词汇文件让测试名和断言用领域语言描述行为——这样测试既是回归保护也是活的规格文档。再回到那份每周期检查清单作为收尾的自检测试描述的是行为而不是实现吗只用公开接口吗重构内部实现后测试还活着吗代码对当前测试是否最小有没有加投机性功能五个问题都答是你就是在正确地做 TDD。【免费下载链接】PentestGPTAutomated Penetration Testing Agentic Framework Powered by Large Language Models项目地址: https://gitcode.com/GitHub_Trending/pe/PentestGPT创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表