2026-06-30 20:45:25 +02:00

4.8 KiB

name, user-invocable, description
name user-invocable description
tdd-test-refactoring true Use during Green/Refactor phase to eliminate test duplication. Extracts shared setup into TEST_F fixtures, removes redundant expectations, and keeps tests minimal and focused. Run after tests pass but before committing.

TDD Test Refactoring Patterns

This skill complements tdd-minimalism by focusing on test code quality during the Green and Refactor phases. After implementation is minimal and passing, apply these patterns to keep the test suite clean.

Principle

Tests are code too — they accumulate duplication and cruft just like implementation. Each test should test one distinct scenario without repeating setup or expectations already covered by other tests.

Refactoring Checklist

After tests pass (Green phase), review the test file for these patterns:

1. Extract Shared Setup into TEST_F

If multiple tests construct the same object(s) with the same configuration, extract the common setup into a fixture.

Before:

TEST(VendingMachineTest, configured_machine_delivers_can) {
    VendingMachine machine;
    machine.Configure(Choice::Cola, Can::Coke);
    EXPECT_EQ(machine.Deliver(Choice::Cola), Can::Coke);
}

TEST(VendingMachineTest, multiple_choices_deliver_different_cans) {
    VendingMachine machine;
    machine.Configure(Choice::Cola, Can::Coke);
    machine.Configure(Choice::Fanta, Can::Orange);
    EXPECT_EQ(machine.Deliver(Choice::Cola), Can::Coke);
    EXPECT_EQ(machine.Deliver(Choice::Fanta), Can::Orange);
}

After:

class VendingMachineTest : public ::testing::Test {
  protected:
    void SetUp() override {
        machine.Configure(Choice::Cola, Can::Coke);
    }
    VendingMachine machine;
};

TEST_F(VendingMachineTest, configured_machine_delivers_can) {
    EXPECT_EQ(machine.Deliver(Choice::Cola), Can::Coke);
}

TEST_F(VendingMachineTest, multiple_choices_deliver_different_cans) {
    machine.Configure(Choice::Fanta, Can::Orange);
    EXPECT_EQ(machine.Deliver(Choice::Cola), Can::Coke);
    EXPECT_EQ(machine.Deliver(Choice::Fanta), Can::Orange);
}

When to keep TEST instead of TEST_F:

  • The test needs a different or empty initial state (e.g., an unconfigured machine)
  • The test's setup is entirely unique and not shared with any other test

2. Remove Redundant Expectations

If a test is a strict superset of another test (covers the same scenario plus more), the subset test may be redundant.

Check: Does the superset test already assert everything the subset test asserts?

Scenario Keep?
Superset test covers all assertions of subset Consider removing subset if it adds no unique coverage
Superset test covers subset's assertions plus additional ones Keep both if subset documents a specific edge case clearly
Tests cover different branches of the same logic Keep both — they test different paths

3. Name Tests by Scenario, Not by Order

Test names should describe what scenario they cover, not their sequence number.

Bad Good
test1, test2, test3 choiceless_machine_deliver_nothing
first_test, second_test configured_machine_delivers_can
test_feature_x multiple_choices_deliver_different_cans

4. One Logical Assertion Per Test

Each test should verify one behavior. Avoid asserting unrelated things in the same test.

When multiple assertions are OK:

  • They verify the same result from different angles (e.g., .has_value() and == expected)
  • They verify a post-condition that naturally produces multiple observable effects

When to split:

  • Assertions exercise different code paths or different methods
  • One assertion failing hides whether the other would also fail

5. Keep Tests Independent

Tests must not depend on each other's side effects. Each test (or each fixture instance) starts fresh.

  • Each TEST_F gets a fresh SetUp()
  • No shared mutable state between tests
  • No test ordering dependencies

Post-Refactoring Verification

After applying test refactoring:

  • Run the test suite — all tests still pass
  • Run clang-format — all files comply
  • No test is a complete subset of another (unless documenting a specific edge case)
  • No duplicate setup code across tests using the same initial state
  • Test names describe the scenario, not the sequence

Relationship to tdd-minimalism

Skill Focus When
tdd-minimalism Implementation code — eliminate overengineering Before writing implementation
tdd-test-refactoring Test code — eliminate duplication and cruft After tests pass, before commit

Run tdd-minimalism before implementation. Run tdd-test-refactoring after tests pass but before committing.