Token导航 LogoToken导航TokenDH.com
研究检索执行命令github未标认证来源可访问许可证需确认审计通过

fdt-refactor-mock-to-fakefdt 将模拟重构为假

Agent Skill

fdt-refactor-mock-to-fake 用于查找、检索和筛选相关信息,适合在 Codex、Claude、Cursor、Gemini CLI 中需要根据关键词、任务场景或来源线索快速定位候选结果时使用。可结合来源仓库、安装命令和原始 README 继续核验具体用法。安装前建议确认权限范围、维护状态,以及是否会触发联网、命令执行或文件读写。

总安装

218

周安装

9

GitHub Stars

81

下载量

71
CodexClaudeCursorGemini CLI

安装说明

本站只整理中文说明和来源信息,不托管安装包,也不代用户安装。

GitHub

来源数

2

许可证

unknown

最后核验

2026-05-01

来源状态

来源可访问

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:fdt-refactor-mock-to-fake(fdt 将模拟重构为假)
来源仓库:https://github.com/dagster-io/erk
仓库路径:skills/fdt-refactor-mock-to-fake
安装命令:
npx skills add https://github.com/dagster-io/erk --skill fdt-refactor-mock-to-fake
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。该命令会通过 npx skills 从第三方来源获取 Skill;本站只展示命令,不托管安装包,也不自动执行。

skills.shnpx skills
npx skills add https://github.com/dagster-io/erk --skill fdt-refactor-mock-to-fake

简介

fdt-refactor-mock-to-fake 将 unittest.mock.patch 重构为注入式假对象,提升测试真实性。

  • 适用于移除系统边界模拟、配置预定义行为并验证真实依赖交互逻辑。
  • 通过 npx skills add 命令安装,优先寻找覆盖全部 mock 的高层抽象网关。
  • 重构后应运行完整测试套件,确保假对象返回值不影响断言有效性。
  • 适用宿主包括 Codex、Claude、Cursor、Gemini CLI,接入前应确认版本、权限和运行环境要求。

SKILL.md

Refactoring Mocks to Fakes

Remove unittest.mock.patch from tests by making source code inject gateway dependencies, then configuring pre-canned fakes in tests.

Use this skill when: A test imports from unittest.mock import patch or uses @patch(...) decorators.

Key principle: Don't stop at the lowest-level matching gateway. Look for a higher-level abstraction that covers ALL the things being mocked together.


Phase 1: Audit Mock Usage

Read the test file. For each patch(...) call, record:

Mock target (fully qualified)System boundary (tool)What it simulatesReturn value configured
erk.core.fast_llm.shutil.whichClaude CLICLI availability checkNone or "/usr/bin/claude"
erk.core.fast_llm.subprocess.runClaude CLICLI execution resultCompletedProcess(returncode=0, stdout="...")

The system boundary column identifies which gateway should own this mock. Multiple mocks with the same system boundary should be covered by a single gateway.

Group mocks by test: A single test patching 2-3 things together suggests those things form a unit that should be covered by one injectable gateway.


Phase 2: Gateway Discovery (Critical)

For each mock group, find the right gateway. Do not stop at the first match.

Rule: subprocess.run is never the right gateway boundary. The gateway should be named after the *tool* being invoked (e.g., GhCli, CmuxGateway, GitGateway), not after the underlying mechanism (subprocess, shell). A gateway that just wraps subprocess.run is no better than mocking subprocess.run directly — it skips the meaningful abstraction layer.

Step 1: Identify the system boundary being tested

Ask: what is the test *actually* testing? Not "what function is being patched" but "what behavior is under test?" Think in terms of the *tool or service* being interacted with, not the Python function being called.

Examples:

  • shutil.which("claude") -> "is the Claude CLI installed?" (tool: Claude CLI)
  • subprocess.run(["claude", "--print",...]) -> "run a prompt via Claude CLI" (tool: Claude CLI)
  • Together -> "interact with the Claude CLI" -> gateway is PromptExecutor (Claude-specific), not Shell (subprocess-generic)

Step 2: Targeted gateway search

Before broad exploration, do a quick targeted search using the tool names from Phase 1:

Grep(pattern="<tool_name>", path="packages/erk-shared/src/erk_shared/gateway/")

If zero hits for a tool → no gateway exists for it. You'll need to create one (see Phase 5). If hits → read the matching gateway to assess coverage.

This takes seconds and immediately tells you whether you're in "reuse" or "create" territory.

Step 3: Check if any mock target is already covered

Before creating anything new, check if existing gateways already cover some of your mock targets. A test mocking subprocess.run(["gh", "pr", "view",...]) is already covered by LocalGitHub.get_pr() — no new gateway needed for that mock.

For each system boundary from Phase 1, ask:

  • Does an existing gateway already provide this operation?
  • Can the test use the existing fake instead of mocking subprocess?

This often eliminates half the mocks immediately, reducing the scope of new work.

Step 4: Search for existing gateways at the right abstraction level

Search from highest to lowest. A higher-level gateway is preferable because it covers multiple low-level calls as a unit.

# 1. Search for existing ABCs that describe the behavior
Grep(pattern="class Fake\w+", path="packages/erk-shared/src/erk_shared/")
Grep(pattern="class Fake\w+", path="tests/fakes/")

# 2. Find gateways that mention the system call you're replacing
Grep(pattern="shutil.which|subprocess.run|is_available", path="packages/erk-shared/")

Priority order when multiple gateways match:

  1. A gateway that covers ALL mocked targets in a test -> inject this one
  2. A gateway that covers the highest-level behavior (e.g., PromptExecutor.execute_prompt rather than Shell.get_installed_tool_path)
  3. The lowest-level matching gateway as a last resort

Erk gateway locations:

  • packages/erk-shared/src/erk_shared/gateway/*/abc.py -- gateway ABCs
  • packages/erk-shared/src/erk_shared/gateway/*/fake.py -- matching fakes
  • packages/erk-shared/src/erk_shared/core/fakes.py -- fakes for service ABCs (FakePromptExecutor, FakeLlmCaller, FakeScriptWriter, etc.)
  • tests/fakes/ -- erk-specific fakes

Step 5: Verify the fake covers what you need

Read the fake's __init__ signature. Check:

  • Can you configure the pre-canned responses the test needs?
  • Does the fake record calls for assertion (calls, prompt_calls, etc.)?
  • Does the fake's is_available() return what you need?

If no fake exists at the right level, you'll need to create one (see Phase 5).


Decision Fork: Gateway Found vs. New Gateway Needed

After Phase 2, you're in one of two paths:

Path A: Existing gateway covers all mocks

  • Skip to Phase 3 (plan injection) → Phase 4 (make injectable) → Phase 6 (rewrite tests)
  • This is the fast path. Scope: modify source + rewrite tests.

Path B: No gateway exists for one or more system boundaries

  • You must create a new gateway before rewriting tests
  • Load the gateway-abc-implementation doc (docs/learned/architecture/gateway-abc-implementation.md)
  • Follow Phase 5-expanded below
  • Scope is significantly larger: new gateway files + ErkContext wiring + modify source + rewrite tests

Phase 3: Plan the injection

Identify what source code needs to change.

Where is the mocked code called from?

Read the source file being patched (e.g., erk.core.fast_llm -> src/erk/core/fast_llm.py). Find the class or function that directly calls the mocked thing.

Is there already a constructor parameter for this gateway?

  • Yes -> skip Phase 4, go to Phase 6
  • No -> plan to add a constructor parameter

What's the production wiring?

Find where the class is instantiated in production (usually src/erk/core/context.py). Plan what real implementation to wire in:

  • FallbackPromptExecutor -> ClaudeCliPromptExecutor(console=None)
  • Shell -> RealShell()
  • etc.

Phase 4: Make source code injectable

Add the gateway as a constructor parameter. Follow erk's conventions:

  • Named parameters only (def __init__(self, *, gateway: GatewayABC))
  • No default parameter values -- caller must wire it explicitly
  • Store as self._gateway

Replace direct system calls with gateway method calls:

# Before:
if shutil.which("claude") is None: ...
result = subprocess.run(["claude", "--print", ...])

# After:
if not self._prompt_executor.is_available(): ...
result = self._prompt_executor.execute_prompt(prompt, model=..., ...)

Map gateway return types to the source function's return types. If the gateway returns PromptResult(success, output, error) but the function returns LlmResponse | LlmCallFailed, add the mapping:

if not result.success:
    return LlmCallFailed(message=f"CLI failed: {result.error}")
return LlmResponse(text=result.output)

Update production wiring in context.py:

from erk.core.prompt_executor import ClaudeCliPromptExecutor
MyClass(prompt_executor=ClaudeCliPromptExecutor(console=None))

Variant: Click command injection (exec scripts)

For Click commands (especially exec scripts), use Click's context system instead of constructor injection:

@click.command(name="my-command")
@click.pass_context
def my_command(ctx: click.Context, ...) -> None:
    github = require_github(ctx)       # existing gateway from context
    cmux = require_context(ctx).cmux   # new gateway from context

Replace direct system calls with gateway method calls obtained from context. See src/erk/cli/commands/exec/scripts/AGENTS.md for the full pattern.


Phase 5: Create a new gateway (when no suitable gateway exists)

Load docs/learned/architecture/gateway-abc-implementation.md for full patterns.

Decide: 3-file or 5-file pattern

  • 3-file (abc, real, fake): For all-or-nothing operations, process replacement, or operations where dry-run/printing don't add value. Examples: Codespace, AgentLauncher.
  • 5-file (abc, real, fake, dry_run, printing): For gateways with distinct read vs mutation methods where dry-run preview is useful. Examples: Git, LocalGitHub, Graphite.

Most new gateways for mock-to-fake refactoring use the 3-file pattern.

Gateway creation checklist

  1. Create directory: packages/erk-shared/src/erk_shared/gateway/<tool_name>/
  2. __init__.py — empty
  3. abc.py — ABC with abstract methods named after tool operations (not subprocess)
  4. real.py — Production implementation using subprocess calls to the tool
  5. fake.py — Constructor-injected test data, NamedTuple call tracking, read-only properties

ErkContext wiring checklist

  1. Add <tool>: <ToolABC> field to ErkContext dataclass (packages/erk-shared/src/erk_shared/context/context.py)
  2. Add <tool> parameter to ErkContext.for_test() with default Fake<Tool>(...)
  3. Wire Real<Tool>() in production context factory (src/erk/core/context.py, near other Real* instantiations)

Error handling decision

  • All callers terminate on failure → raise RuntimeError
  • Some callers branch on error → return discriminated union (see Non-Ideal State Decision Checklist in gateway-abc-implementation.md)

Phase 6: Rewrite the tests

For each test that used patch:

  1. Remove from unittest.mock import patch (and any from subprocess import CompletedProcess)
  2. Construct the fake with pre-canned responses
  3. Pass it to the class under test via the new constructor parameter
  4. Replace mock assertions (mock_run.assert_called_once()) with fake property checks (assert len(executor.prompt_calls) == 1)

Pattern:

# Before:
def test_falls_back_to_cli(monkeypatch):
    monkeypatch.delenv("ANTHROPIC_API_KEY", raising=False)
    fake_result = CompletedProcess(args=[], returncode=0, stdout="my-slug\n", stderr="")
    with (
        patch("erk.core.fast_llm.shutil.which", return_value="/usr/bin/claude"),
        patch("erk.core.fast_llm.subprocess.run", return_value=fake_result) as mock_run,
    ):
        result = AnthropicLlmCaller().call("test prompt", system_prompt="sys", max_tokens=50)
    assert isinstance(result, LlmResponse)
    mock_run.assert_called_once()

# After:
def test_falls_back_to_cli(monkeypatch):
    monkeypatch.delenv("ANTHROPIC_API_KEY", raising=False)
    executor = FakePromptExecutor(
        prompt_results=[PromptResult(success=True, output="my-slug", error=None)]
    )
    caller = AnthropicLlmCaller(prompt_executor=executor)
    result = caller.call("test prompt", system_prompt="sys", max_tokens=50)
    assert isinstance(result, LlmResponse)
    assert result.text == "my-slug"
    assert len(executor.prompt_calls) == 1
    assert executor.prompt_calls[0].prompt == "test prompt"

Note: monkeypatch.delenv is a pytest fixture, not mock.patch -- it's fine to keep.


Phase 7: Verify

Run the affected test file:

uv run pytest <test_file> -v

Then lint and type-check the modified source files.


Common pitfalls

Pitfall 1: Matching the wrong gateway level If shutil.which is mocked, the obvious match is Shell.get_installed_tool_path(). But if subprocess.run is also mocked in the same test, the real abstraction is something that covers BOTH -- often PromptExecutor or a similar higher-level gateway.

Pitfall 2: monkeypatch.delenv vs mock.patch monkeypatch.delenv("ANTHROPIC_API_KEY") is a pytest builtin, not mock.patch. Keep it -- it's acceptable and doesn't need replacement.

Pitfall 3: Forgetting to update production wiring After adding a constructor parameter, always update context.py (or wherever the class is instantiated). The type checker will catch this but only if you run it.

Pitfall 4: One test, multiple patch contexts Multiple patch() calls in one test is a red flag that something needs to be at a higher abstraction level. A single fake should replace all of them.

Pitfall 5: Creating a subprocess-level gateway If you find yourself designing a gateway called ShellRunner, SubprocessGateway, or CommandRunner, stop. That's still mocking at the wrong level. The gateway must be specific to the *tool* being called:

  • subprocess.run(["gh",...])LocalGitHub or a GhCli gateway
  • subprocess.run(["cmux",...])CmuxGateway
  • subprocess.run(["claude",...])PromptExecutor
  • subprocess.run(["git",...])Git gateway

Name the gateway after what it represents, not how it executes.

适合场景

01

用户想查找某类 Agent Skill 时

02

需要根据任务场景推荐可安装能力包时

03

需要对比不同来源的安装命令和来源信息时

能力概览

能力 1

按任务关键词查找相关 Skills

能力 2

展示可复制的安装命令

能力 3

保留来源站点、仓库和原始说明,方便继续核验

能力 4

展示第三方安全扫描或审计结果

安装后应在对应宿主中按原始 README 的触发条件使用;具体调用方式请以来源页面和 README 为准。

平台分布

Codex

32.75%
按下载量换算23

Claude

30.6%
按下载量换算22

Cursor

18.05%
按下载量换算13

Gemini CLI

9.6%
按下载量换算7

安全审计

Gen Agent Trust Hub

通过

Socket

通过

Snyk

通过

权限和风险

执行命令

安装流程涉及命令执行,可能通过 npx skills add https://github.com/dagster-io/erk --skill fdt-refactor-mock-to-fake 联网下载 Skill 或依赖。用户安装前应确认命令来源、仓库内容和执行环境。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。当前只有一个来源,正式发布前建议补源仓库或其他目录站核验。

来源信息

继续浏览同类 Skills