Lock your legacy code behavior before AI touches it.
A Claude Code plugin that generates characterization tests (also known as approval tests, golden master tests, or snapshot tests) for existing code. These tests document what your code actually does — not what it should do — creating a safety net before refactoring or AI-assisted modification.
AI coding agents are powerful but dangerous with legacy code:
- 🐛 They "fix" behavior that users depend on
- 🗑️ They delete tests to make them pass (Kent Beck's warning)
- 🔀 They refactor beyond the requested scope
Characterization tests prevent all three by locking current behavior before any changes.
"When a system goes into production, it becomes its own specification." — Michael Feathers, Working Effectively with Legacy Code
/plugin install characterization-test-generator
git clone https://github.com/duybv/characterization-test-generator.git
cp -r characterization-test-generator/skills/characterize ~/.claude/skills//add-plugin characterization-test-generator
In Claude Code, invoke:
/characterize src/services/optimizer.go
Or describe what you need:
Generate characterization tests for the route optimization service before I refactor it
The skill will:
- Identify all public functions/methods in the target
- Analyze code paths, inputs, and outputs
- Generate characterization tests with realistic data
- Scrub unstable fields (timestamps, IDs, random values)
- Suggest mutations to verify test effectiveness
| Language | Test Framework | Snapshot Method |
|---|---|---|
| Go | testing |
Golden files (testdata/golden/) |
| Python | pytest |
approvaltests or manual golden files |
| TypeScript | jest |
toMatchSnapshot() |
| JavaScript | jest |
toMatchSnapshot() |
| Kotlin | JUnit | ApprovalTests |
| Java | JUnit | ApprovalTests |
Based on the Feathers Method (Michael Feathers, 2004):
1. Write test named "x" with expected = null
2. Run test → fails, revealing actual output
3. Paste actual output as expected value
4. Rename test to describe discovered behavior
5. Repeat for different inputs and code paths
This plugin automates steps 1-5 using AI:
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ AI reads code │────▶│ Generates tests │────▶│ Human reviews │
│ (understands │ │ with realistic │ │ and approves │
│ all paths) │ │ inputs + scrubbing│ │ the tests │
└─────────────────┘ └──────────────────┘ └────────┬────────┘
│
┌──────────────────┐ │
│ Safe to refactor │◀────────────┘
│ or use AI agents │
└──────────────────┘
func TestCharacterize_OptimizeRoute_BasicInput(t *testing.T) {
input := loadFixture(t, "testdata/10_stops_2_warehouses.json")
result, err := solver.Optimize(input)
require.NoError(t, err)
golden := filepath.Join("testdata", "golden", t.Name()+".json")
actual := toJSON(t, scrub(result))
if *update {
os.WriteFile(golden, actual, 0644)
return
}
expected, _ := os.ReadFile(golden)
assert.JSONEq(t, string(expected), string(actual))
}from approvaltests import verify_as_json
def test_characterize_calculate_distance():
result = calculate_distance(lat1=10.7, lon1=106.7, lat2=21.0, lon2=105.8)
verify_as_json(scrub(result))test("characterize formatAddress", () => {
const result = formatAddress({ street: "Nguyen Hue", city: "HCM" });
expect(scrub(result)).toMatchSnapshot();
});| Characterization Test | Unit Test | E2E Test | |
|---|---|---|---|
| Purpose | Document current behavior | Verify correctness | Verify user flow |
| When | Before changing legacy code | When building new features | After building features |
| Fail means | Behavior changed | Code is wrong | User flow broken |
| Written by | AI (reviewed by human) | Developer | QA/Developer |
From understandlegacycode.com:
- 📸 Snapshot — Capture what the code produces
- ✅ Coverage — Use coverage reports to find untested paths, add more inputs
- 👽 Mutations — Deliberately break code to verify tests catch it
Phase 1: PROTECT
/characterize src/services/ ← this plugin
Commit characterization tests
Phase 2: CHANGE
AI refactors code
Run characterization tests
→ All pass? ✅ Behavior preserved
→ Some fail? ⚠️ Review what changed
Phase 3: EVOLVE
Write proper unit tests (TDD) ← use superpowers/test-driven-development
Gradually replace characterization tests with intent-based tests
This plugin is based on research and practices from:
- Michael Feathers — Characterization Testing & "Working Effectively with Legacy Code" (2004)
- Kent Beck — TDD as superpower with AI agents (Pragmatic Engineer podcast)
- Emily Bache — Approval Testing talk (Arrange → Act → Print → Assert)
- Nicolas Carlo — understandlegacycode.com
- Addy Osmani — Agentic Engineering (Google Chrome)
- approvaltests.com — Multi-language approval testing library
characterization-test-generator/
├── .claude-plugin/
│ └── plugin.json # Plugin metadata
├── skills/
│ └── characterize/
│ ├── SKILL.md # Main skill instructions
│ └── references/
│ ├── theory.md # Background theory & citations
│ └── language-patterns.md # Code patterns per language
├── docs/
│ └── workflow.md # Detailed workflow guide
├── tests/ # Plugin tests
├── README.md
└── LICENSE (MIT)
- Fork the repository
- Create a branch for your improvement
- Add support for new languages or improve existing patterns
- Submit a PR
MIT — see LICENSE for details.
Built for developers who maintain legacy code in the age of AI.
If AI is going to touch your code, make sure you have a safety net first.