본문으로 건너뛰기
cccskills
무료GitHub에 공개

characterization-test-generator

Generates tests that capture current behavior of existing code before refactoring. Use when you need a safety net before AI-assisted refactoring or modifying legacy code.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

설치 방법 보기

포함된 파일(1)

  • SKILL.md7.7 KB

SKILL.md(원문)

설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.

Characterization Test Generator

Generate tests that document what existing code actually does — not what it should do. These tests capture current behavior so you can refactor with confidence, especially when using AI to modify code.

When This Skill Activates

Use this skill when the user:

  • Says "I need to refactor this" or "help me refactor"
  • Wants to "add tests before changing code"
  • Asks for "characterization tests" or "golden master tests"
  • Says "I want to safely modify this class/module"
  • Mentions "legacy code" or "untested code"
  • Wants AI to refactor but is worried about breaking things

Why This Matters for AI-Generated Code

AI refactoring is powerful but risky:

  • AI doesn't remember why code works a certain way
  • AI may "improve" code and break subtle behavior
  • Characterization tests freeze current behavior as a regression suite
  • If any test fails after refactoring, you know something changed

Process

Phase 1: Discover Code Under Test

Glob: **/*.swift (in the target module)
Grep: "class |struct |enum |protocol |func " (public API surface)

Identify:

  • Public types and their public methods
  • Input types and return types
  • Side effects (network, disk, UserDefaults, notifications)
  • Dependencies (injected or created internally)
  • State mutations (properties that change)

Phase 2: Classify Behavior

For each public method, classify:

CategoryExampleTest Strategy
Pure computationcalculate(a, b) -> cInput/output pairs
State mutationaddItem(_:) modifies arrayBefore/after state checks
Async operationfetchData() async -> [Item]Mock dependencies, verify results
Side effectsave() writes to diskVerify mock was called
Event emissiondelegate?.didUpdate()Capture delegate calls
Error pathThrows on invalid inputVerify correct error type

Phase 3: Generate Tests

Test Naming Convention

Characterization tests should be clearly labeled:

import Testing
@testable import YourApp

@Suite("Characterization: ItemManager")
struct ItemManagerCharacterizationTests {
    // Tests document CURRENT behavior, not ideal behavior
}

Template: Pure Computation

@Test("current behavior: calculates total with tax")
func calculatesTotal() {
    let calculator = PriceCalculator()

    let result = calculator.total(subtotal: 100.0, taxRate: 0.08)

    // Document actual behavior — even if the rounding seems wrong
    #expect(result == 108.0)
}

Template: State Mutation

@Test("current behavior: addItem appends and sorts by date")
func addItemSortsbyDate() {
    let manager = ItemManager()
    let older = Item(title: "A", date: .distantPast)
    let newer = Item(title: "B", date: .now)

    manager.addItem(older)
    manager.addItem(newer)

    // Document actual ordering behavior
    #expect(manager.items.first?.title == "A")
    #expect(manager.items.last?.title == "B")
    #expect(manager.items.count == 2)
}

Template: Async with Dependencies

@Test("current behavior: loadItems returns cached data when offline")
func loadItemsOffline() async throws {
    let mockNetwork = MockNetworkClient(shouldFail: true)
    let mockCache = MockCache(items: [Item.sample])
    let service = ItemService(network: mockNetwork, cache: mockCache)

    let items = try await service.loadItems()

    // Document: falls back to cache when network fails
    #expect(items.count == 1)
    #expect(items.first?.title == Item.sample.title)
}

Template: Side Effects

@Test("current behavior: save writes to UserDefaults")
func saveWritesToDefaults() {
    let defaults = MockUserDefaults()
    let settings = SettingsManager(defaults: defaults)

    settings.setTheme(.dark)
    settings.save()

    // Document: saves as string, not as enum raw value
    #expect(defaults.lastSetValue as? String == "dark")
    #expect(defaults.lastSetKey == "app_theme")
}

Template: Error Paths

@Test("current behavior: throws on empty title")
func throwsOnEmptyTitle() {
    let validator = ItemValidator()

    #expect(throws: ValidationError.emptyField("title")) {
        try validator.validate(Item(title: "", date: .now))
    }
}

Template: Edge Cases

@Test("current behavior: handles nil optional gracefully")
func handlesNilOptional() {
    let parser = DataParser()

    let result = parser.parse(data: nil)

    // Document: returns empty array on nil, doesn't crash
    #expect(result.isEmpty)
}

@Test("current behavior: handles empty collection")
func handlesEmptyCollection() {
    let aggregator = StatsAggregator()

    let stats = aggregator.compute(values: [])

    // Document: returns zeroes, not NaN or crash
    #expect(stats.average == 0.0)
    #expect(stats.count == 0)
}

Phase 4: Fill in Actual Values

This is the key step. For each test:

  1. Read the source code to understand what the method actually returns
  2. Trace the logic for the given inputs
  3. Write the assertion with the actual value
// ❌ Wrong — this is what you WANT it to do
#expect(result == expectedCorrectValue)

// ✅ Right — this is what it ACTUALLY does
#expect(result == actualCurrentValue)  // Note: off-by-one, but current behavior

Add comments for surprising behavior:

// CHARACTERIZATION: This returns 11 not 10 due to inclusive range.
// Don't "fix" this until intentionally changing behavior.
#expect(range.count == 11)

Phase 5: Verify and Lock

  1. Run all characterization tests — they must ALL pass
  2. If any fail, adjust assertions to match actual behavior
  3. Tag them:
@Suite("Characterization: ItemManager")
@Tag(.characterization)
struct ItemManagerCharacterizationTests { ... }

// Define the tag
extension Tag {
    @Tag static var characterization: Self
}
  1. Run selectively:
# Run only characterization tests
xcodebuild test -scheme YourApp \
  -only-testing "YourAppTests/ItemManagerCharacterizationTests"

Output Format

## Characterization Tests Generated

**Module**: [Module name]
**Classes tested**: [List]
**Tests generated**: [Count]

### Coverage Summary

| Class | Methods Covered | Edge Cases | Notes |
|-------|----------------|------------|-------|
| ItemManager | 5/7 | 3 | 2 private methods skipped |
| PriceCalculator | 3/3 | 2 | All public API covered |

### Files Created
- `Tests/CharacterizationTests/ItemManagerCharacterizationTests.swift`
- `Tests/CharacterizationTests/PriceCalculatorCharacterizationTests.swift`

### Surprising Behaviors Found
- `PriceCalculator.total()` rounds DOWN, not to nearest cent
- `ItemManager.sort()` is unstable — equal dates may reorder

### Ready to Refactor
All [X] characterization tests passing. Safe to refactor with AI.
Run `xcodebuild test` after each change to verify no behavior changed.

When NOT to Use This

  • Code is already well-tested (check coverage first)
  • You're deleting the code entirely (no need to characterize)
  • The code is trivially simple (single-line computed properties)
  • You want to change the behavior (use tdd-bug-fix instead)

References

  • Michael Feathers, Working Effectively with Legacy Code — coined "characterization test"
  • generators/test-generator/ — for standard test generation (not characterization)
  • testing/tdd-refactor-guard/ — pre-refactor checklist that uses these tests

리뷰

아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.

댓글

로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.

아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.

보내면 바로 공개됩니다. 이메일 주소나 전화번호 같은 개인 정보는 쓰지 마세요. URL은 2개까지입니다.

0 / 1,000자

이 양식은 reCAPTCHA로 보호되며 Google 개인정보처리방침 및 서비스 약관이 적용됩니다.

같은 저장소의 스킬

Run a structured accessibility audit on an iOS/macOS app — automated XCUITest audits, Accessibility Inspector, manual VoiceOver/Dynamic Type passes, and App Store Accessibility Nutrition Label evaluation. Use before release, when preparing Nutrition Label declarations, or for EU Accessibility Act compliance.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

rshankras/claude-code-apple-skills8132026년 7월 24일 업데이트

Generate accessibility infrastructure for VoiceOver, Dynamic Type, and accessibility features. Use when improving app accessibility, adding accessibility labels and hints, or auditing compliance.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

rshankras/claude-code-apple-skills8132026년 7월 24일 업데이트

Generates an Apple-compliant account deletion flow with multi-step confirmation UI, optional data export, configurable grace period, Keychain cleanup, and server-side deletion request. Use when user needs account deletion, right-to-delete, or Apple App Review compliance for account removal.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

rshankras/claude-code-apple-skills8132026년 7월 24일 업데이트

Privacy-preserving ad measurement with AdAttributionKit (SKAdNetwork's successor) — install and re-engagement attribution, conversion-value strategy under crowd anonymity, and end-to-end postback testing. Use when running paid acquisition beyond Apple Ads, measuring re-engagement campaigns, designing conversion values, or migrating from SKAdNetwork.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

rshankras/claude-code-apple-skills8132026년 7월 24일 업데이트

alarmkit

무료

AlarmKit integration for scheduling alarms and timers with custom UI, Live Activities, and snooze support. Use when implementing alarm or timer features in iOS 26+ apps.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

rshankras/claude-code-apple-skills8132026년 7월 24일 업데이트

Interpret app metrics and make data-driven decisions. Covers DAU/MAU, retention, LTV, ARPU, App Store Connect analytics, AARRR funnel analysis, cohort analysis, and diagnostic decision trees. Use when user wants to understand their metrics, diagnose problems, or build a data-driven growth plan.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

rshankras/claude-code-apple-skills8132026년 7월 24일 업데이트

rshankras의 스킬 모두 보기

이 스킬의 문제 신고