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.