In a TDD team, developers write tests before code, which raises a practical question: what's left for QA? Unit tests don't replace integration tests, E2E verification, or the edge cases developers didn't think to specify. This article covers how QA fits into TDD workflows, what acceptance test-driven development (ATDD) means in practice, and why shifting QA work earlier (not eliminating it) is what TDD actually requires.

What TDD actually is

The TDD cycle has three steps:

1. Red: Write a failing test for the behavior you want

2. Green: Write the minimum code to make the test pass

3. Refactor: Clean up the code without breaking the test

Repeat for the next piece of behavior.

The key word is "minimum." You write exactly enough code to pass the test — nothing more. This forces the implementation to be driven by its specification (the test), not the other way around.

What TDD changes about the code

TDD produces code that is:

Testable by design. Code that's hard to test is often a sign of poor design: tight coupling, hidden dependencies, too many responsibilities. TDD forces testability at the point of writing, not as an afterthought. Covered by definition. Every behavior has a test because you had to write the test to write the behavior. Incremental. Features are built in small, verified steps. There's no "write everything, then test." Documented. The test suite is an executable specification. Reading the tests tells you what the code is supposed to do.

Where QA engineers fit in TDD teams

QA engineers in TDD teams face a paradox: the developers are already writing tests. What's left?

Several things:

Higher-level testing: Developers write unit tests. QA engineers write integration tests and E2E tests. TDD at the unit level doesn't replace E2E verification. Acceptance test-driven development (ATDD): QA engineers write acceptance tests before developers write any code. Developers make those tests pass. This is TDD at the feature level. It requires QA to be involved at the beginning of development, not the end. Exploratory and edge-case testing: TDD tests are written by developers who know how the system works. QA engineers test what developers didn't think to specify — edge cases, unusual inputs, integration points, performance under load. Test quality review: Are the developer tests actually testing the right things? Are they testing behavior or implementation? Will they catch the bugs that matter? QA engineers can review test code, not just product code.

Acceptance test-driven development (ATDD)

ATDD is the QA-relevant version of TDD. The workflow:

1. Business stakeholder describes a feature in user terms

2. QA engineer translates this into acceptance criteria and acceptance tests

3. Developers implement until the acceptance tests pass

4. QA verifies both the tests and the implementation

The acceptance tests become the source of truth for what "done" means. A feature is done when its acceptance tests pass.

In Playwright terms:

// Written before implementation exists
test('user can reset password via email', async ({ page }) => {
  // Given a user with a registered email
  const userEmail = 'testuser@example.com';
  
  // When they request a password reset
  await page.goto('/forgot-password');
  await page.getByLabel('Email address').fill(userEmail);
  await page.getByRole('button', { name: 'Send reset link' }).click();
  
  // Then they see a confirmation message
  await expect(page.getByRole('alert')).toHaveText(
    'If that email is registered, a reset link is on its way.'
  );
  
  // And they receive a reset email (verified via test email API)
  const inbox = await testEmailAPI.getInbox(userEmail);
  expect(inbox.some(m => m.subject.includes('Reset your password'))).toBe(true);
});

This test is written before the feature is built. The developer's job is to make it pass.

Behavior-Driven Development (BDD)

BDD is an evolution of ATDD that adds a shared language (usually Gherkin) for writing tests that non-engineers can read:

Feature: Password reset

  Scenario: User resets password via email
    Given a user with email "testuser@example.com" is registered
    When they submit the forgot password form with that email
    Then they see "If that email is registered, a reset link is on its way."
    And they receive a password reset email within 1 minute

These Gherkin scenarios map to Playwright test code via a step definition layer (using tools like Cucumber.js or Playwright's BDD plugins).

BDD is most valuable when non-technical stakeholders write or review the scenarios. It adds overhead for teams where all stakeholders can read code.

Should QA engineers practice TDD themselves?

For test automation code: mostly no. TDD is designed for production code, where you're designing a system. Test code should be straightforward to write and verify. Writing tests for your test code creates unnecessary complexity.

The exception: QA engineers who build test frameworks or utilities (page object helpers, fixture utilities, reporting integrations) can benefit from TDD when building those tools.

The practical takeaway for QA in TDD teams

1. Get involved before code is written. Review user stories for testability. Write acceptance criteria. Define edge cases.

2. Write acceptance tests early. Even if developers aren't doing formal ATDD, having acceptance tests written before development starts shapes the implementation.

3. Shift exploratory testing earlier. In TDD teams, exploratory testing becomes more important because it's what finds the things the tests didn't specify.

4. Review developer tests. Not for coverage numbers — for whether the tests actually verify behavior that matters.

TDD doesn't eliminate the need for QA engineers. It changes where QA work happens: from after the code to alongside it.

→ See also: Test-Driven Development for QA: Writing Tests Before Code | Shift-Left Testing: What It Means and How to Practice It | Test Automation Best Practices That Actually Matter