Skip to content

Red-Green-Refactor Workflow

When to Use

Every time you implement a new feature or fix a bug using TDD. This is the core TDD workflow.

Steps

  1. RED - Write a Failing Test
  2. Think about the next small piece of behavior you need
  3. Write a test that describes that behavior
  4. Run the test and verify it fails for the right reason
  5. A test that passes without any implementation means your test is broken
  6. The right reason is that the behavior is absent. A failure you produced by breaking or reverting working code is a different signal (see What a Failing Test Proves)
// RED: Test for parsing a user's full name
describe('UserNameParser', () => {
  it('splits full name into first and last', () => {
    const parser = new UserNameParser();
    const result = parser.parse('John Doe');
    expect(result).toEqual({ first: 'John', last: 'Doe' });
  });
});
// Output: UserNameParser is not defined
  1. GREEN - Make the Test Pass (Quickly)
  2. Write the simplest code that makes the test pass
  3. Ignore code quality at this step - just make it work
  4. Hard-coding values is fine if it passes the test
  5. "Commit whatever sins necessary" - Kent Beck
// GREEN: Simplest implementation that passes
class UserNameParser {
  parse(fullName) {
    const parts = fullName.split(' ');
    return { first: parts[0], last: parts[1] };
  }
}
// Output: Test passes
  1. REFACTOR - Improve the Code
  2. Clean up duplication
  3. Improve names, structure, clarity
  4. Extract methods/functions if needed
  5. Tests must stay green throughout refactoring
  6. If a test fails during refactor, you broke something - undo and try again

REFACTOR when an agent implements

REFACTOR is where agents grow code and quietly drop guards. Run it as a separate step, under the same constraints as GREEN plus two of its own: no new tests and no changed assertions, and a diff-size budget.

The frozen tests are the only proof the refactor preserved behavior. A guard, branch, or edge case with no test pinning it can be removed here and the suite will not notice. A mutation run at the end of the feature is the outer check for that - see What TDD Covers.

// WRONG - this is not a refactor. Read to the end of the block.
class UserNameParser {
  parse(fullName) {
    if (!fullName || typeof fullName !== 'string') {
      throw new Error('Invalid name');
    }
    const parts = fullName.trim().split(/\s+/); // Handle multiple spaces
    return {
      first: parts[0],
      last: parts.slice(1).join(' ')  // Handle middle names
    };
  }
}
// Wait - we just changed behavior without a test!
// This is wrong. Undo and write tests for edge cases first.
  1. Repeat - Add Next Test
  2. Once tests are green and code is clean, return to RED
  3. Write the next failing test for the next small behavior
  4. Repeat the cycle

Decision Points

At this step... If... Then...
RED Test passes without implementation Your test doesn't actually test new behavior - rewrite it
RED Test fails to compile That's fine - compilation failures are failures; move to GREEN
RED The test only fails once you break or revert working code That is mutation verification, not RED. The assertion was authored from the implementation, so it cannot constrain the design (see What a Failing Test Proves)
GREEN Implementation seems too simple/dumb That's fine - refactor will improve it, or next test will force better design
GREEN You're tempted to add extra features Stop - add only enough to pass this test; write another test for new features
REFACTOR A test fails Default: you broke something - revert and refactor more carefully. Only judge the test brittle if you can name the implementation detail it asserts on (see Refactoring with Confidence)
REFACTOR Code smells obvious but fix requires new behavior Stop refactoring; write a new test for that behavior first

Common Mistakes

  • Skipping RED phase by writing code first - You lose the design benefit of thinking through behavior first
  • Producing RED by breaking working code, or by running a code-first test against an old tree - The test does fail, and the run order does look correct, but the assertion came from the implementation and can only ratify it (see What a Failing Test Proves)
  • Writing complex tests that test multiple behaviors - Each test should verify one thing; break into multiple tests
  • Editing a test in GREEN to make it pass - GREEN is for production code only. If the test itself is wrong, stop, return to RED, and change it there deliberately (see Changing Existing Tests)
  • Spending too long on GREEN trying to write perfect code - Green phase is about speed; refactor is about quality
  • Forgetting to refactor - The most common mistake; leads to messy code despite test coverage
  • Refactoring without running tests - Always run tests after each small refactor step
  • Adding features during REFACTOR - Refactoring should only improve structure, never change behavior

See Also