SuperAPI AI Directory

tdd-strict

Testing & quality

View repository

Updated 10-16 02:14

About this skill

Strict Test-Driven Development

This skill enforces TDD practices based on the core principle:

"If you didn't watch the test fail, you don't know if it tests the right thing."

When to activate

  • For every new feature implementation
  • For bug fixes (first a test that reproduces the bug, then the fix)
  • For refactoring (tests must pass before AND after the change)
  • For API extensions
  • For every exported function

The Red-Green-Refactor cycle

1. RED: Write a test that fails

// ZUERST: Test schreiben
describe('calculateOEE', () => {
  it('should return 0 when availability is 0', () => {
    const result = calculateOEE({ availability: 0, performance: 100, quality: 100 });
    expect(result).toBe(0);
  });
});

// Test MUSS fehlschlagen:
// Error: calculateOEE is not defined
// ODER
// Error: Expected 0 but received undefined

Important: The test MUST fail for the right reason:

  • Function does not exist
  • Function returns the wrong result
  • NOT: syntax error in the test itself

2. GREEN: Minimal code that passes the test

// DANACH: Minimaler Code
export function calculateOEE(params: OEEParams): number {
  if (params.availability === 0) return 0;
  // Weitere Logik kommt spaeter durch weitere Tests
  return 0;
}

Rule: Write the SIMPLEST code that passes the test.

  • No optimizations
  • No additional features
  • No "obvious" extensions

3. REFACTOR: Clean up without new behavior

// Nach mehreren gruenen Tests: Refactoring erlaubt
export function calculateOEE({ availability, performance, quality }: OEEParams): number {
  return (availability * performance * quality) / 10000;
}

Rules for refactoring:

  • All existing tests MUST continue to pass
  • Do NOT add new behavior
  • Only improve code structure
  • After every refactoring step: run the tests

The 13 invalid rationalizations

These excuses are NEVER acceptable:

1. "Too simple to test"

Reality: Simple code needs simple tests. 1 line of test is okay.

it('should add two numbers', () => {
  expect(add(2, 3)).toBe(5);
});

2. "I'll test later"

Reality: "Later" means "never". TDD means test FIRST.

3. "Already tested manually"

Reality: Manual tests are not reproducible and do not scale.

4. "Time pressure allows no tests"

Reality: Tests save time during debugging and prevent regressions.

5. "Private methods don't need to be tested"

Reality: Test the behavior through public APIs. If not testable: refactor.

6. "UI code can't be tested"

Reality: React Testing Library, Playwright, Storybook exist precisely for that.

7. "The logic is trivial"

Reality: Trivial logic changes. Tests document expected behavior.

8. "We have a QA process"

Reality: QA finds bugs later and more expensively. TDD prevents bugs from the start.

9. "The code is only temporary"

Reality: Temporary code often lives for years. Tests also secure temporary code.

10. "Tests slow down development"

Reality: TDD accelerates in the long term through less debugging and regressions.

11. "Legacy Code has no tests"

Reality: Write characterization tests before changes. Improve step by step.

12. "The framework/library already tests that"

Reality: Test YOUR usage of the framework, not the framework itself.

13. "Mocking is too cumbersome"

Reality: If mocking is too complex, the design is too complex. Refactor.

Verification Checklist

Before every commit the following MUST apply:

  • Every exported function has at least one test
  • Every test failed BEFORE the implementation (RED observed)
  • Every test checks exactly ONE thing (Single Assertion Principle)
  • Minimal code per test (no over-engineering)
  • Edge cases covered:
    • Null/undefined inputs
    • Empty arrays/strings
    • Boundary values (0, MAX_INT, negative numbers)
    • Invalid inputs (TypeError expected)
  • Test names describe behavior: should [erwartetes Ergebnis] when [Bedingung]
  • No skip or only tests in the commit
  • Coverage at least 80% for new code

TDD Workflow in Practice

Step 1: Create test file

# Fuer neue Funktion in src/utils/oee.ts
touch src/utils/oee.test.ts

Step 2: Minimal failing test

// src/utils/oee.test.ts
import { describe, it, expect } from 'vitest';
import { calculateOEE } from './oee';

describe('calculateOEE', () => {
  it('should return 85 for sample manufacturing data', () => {
    const result = calculateOEE({
      availability: 90,
      performance: 95,
      quality: 99
    });
    expect(result).toBeCloseTo(84.645, 2);
  });
});

Step 3: Run test (MUST fail)

npm run test -- --run src/utils/oee.test.ts

# Erwartete Ausgabe:
# Error: Cannot find module './oee'
# ODER nach Stub:
# Error: Expected 84.645 but received undefined

Step 4: Minimal implementation

// src/utils/oee.ts
export interface OEEParams {
  availability: number;
  performance: number;
  quality: number;
}

export function calculateOEE(params: OEEParams): number {
  return (params.availability * params.performance * params.quality) / 10000;
}

Step 5: Run test (MUST pass)

npm run test -- --run src/utils/oee.test.ts

# Erwartete Ausgabe:
# PASS src/utils/oee.test.ts

Step 6: Next test for edge case

it('should throw when values exceed 100', () => {
  expect(() => calculateOEE({
    availability: 101,
    performance: 100,
    quality: 100
  })).toThrow('Values must be between 0 and 100');
});

Back to Step 3 (RED) -> Step 4 (GREEN) -> Repeat.

Recognize Anti-Patterns

FORBIDDEN: Test after code

// FALSCH: Code zuerst geschrieben
function add(a: number, b: number): number {
  return a + b;
}

// Dann erst Test geschrieben - UNGUELTIG!
// Du weisst nicht ob der Test das richtige testet

FORBIDDEN: Too much code at once

// FALSCH: Komplette Klasse ohne Tests implementiert
class UserService {
  async create(user: User) { ... }
  async update(id: string, data: Partial<User>) { ... }
  async delete(id: string) { ... }
  async findById(id: string) { ... }
  async findAll(filters: FilterOptions) { ... }
}

// RICHTIG: Eine Methode nach der anderen mit TDD

FORBIDDEN: Adjust tests so that they pass

// FALSCH: Test geaendert weil Implementation anders ist
// Vorher: expect(result).toBe(100);
// Nachher: expect(result).toBe(99.5); // "Weil die Formel das so ausgibt"

// RICHTIG: Implementation korrigieren ODER Anforderungen klaeren

Framework-Specific Patterns

React Components (Vitest + Testing Library)

import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

describe('LoginButton', () => {
  it('should show loading spinner when clicked', async () => {
    render(<LoginButton onLogin={vi.fn()} />);

    await userEvent.click(screen.getByRole('button', { name: /login/i }));

    expect(screen.getByRole('progressbar')).toBeInTheDocument();
  });
});

API Endpoints (Supertest)

import request from 'supertest';
import { app } from '../app';

describe('POST /api/analyze', () => {
  it('should return 401 without authentication', async () => {
    const response = await request(app)
      .post('/api/analyze')
      .send({ data: 'test' });

    expect(response.status).toBe(401);
    expect(response.body.error).toBe('Unauthorized');
  });
});

Async Code

describe('fetchUserData', () => {
  it('should retry 3 times on network failure', async () => {
    const mockFetch = vi.fn()
      .mockRejectedValueOnce(new Error('Network'))
      .mockRejectedValueOnce(new Error('Network'))
      .mockResolvedValueOnce({ id: 1, name: 'Test' });

    const result = await fetchUserData(1, { fetch: mockFetch });

    expect(mockFetch).toHaveBeenCalledTimes(3);
    expect(result.name).toBe('Test');
  });
});

In Case of Violation of TDD

  1. STOP: No further code generation without test
  2. WARNING: "TDD violation detected: [Description]"
  3. INSTRUCTION: Show the correct TDD workflow
  4. QUESTION: "Should I write the test first?"

Metrics for Measuring Success

  • Test-to-Code Ratio: At least 1:1 (test LOC to code LOC)
  • Coverage: Minimum 80% for new code
  • Red-Green Time: Short cycles (5-10 minutes per feature)
  • Test Execution Time: Unit tests under 10 seconds

Commands

# Einzelnen Test laufen lassen (RED pruefen)
npm run test -- --run path/to/file.test.ts

# Alle Tests (vor Commit)
npm run test

# Coverage Report
npm run test:coverage

# Watch Mode (waehrend Entwicklung)
npm run test -- --watch

Integration with Other Skills

  • code-quality-gate: TDD tests are part of Gate 1 (Pre-Commit)
  • strict-typescript-mode: Tests must also be type-safe
  • supervisor: Verifier agent verifies TDD compliance

Sources and Further Reading

  • Kent Beck: "Test-Driven Development: By Example"
  • Robert C. Martin: "Clean Code" (Chapter 9: Unit Tests)
  • Martin Fowler: "Refactoring" (Test safety in refactoring)