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_Fgets a freshSetUp() - ✅ 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.