Factor #3: Speed - Fast Tests Get Run

A test suite that takes 30 minutes to run is a test suite that doesn't get run. Speed isn't just nice to have - it's essential for a healthy development workflow.

The Psychology of Slow Tests

When tests are slow:

  • Developers run them less frequently
  • They get commented out "temporarily"
  • People push without running the full suite
  • CI becomes the only place tests run (and everyone ignores failures)
  • The feedback loop stretches from seconds to hours

Common Speed Killers

Real Database Operations
// 😱 BAD: Real database for every test (500ms+ per test)
test('user can update profile', async () => {
  await db.connect();
  await db.query('INSERT INTO users ...');
  await db.query('UPDATE users SET ...');
  const result = await db.query('SELECT * FROM users ...');
  await db.close();
  expect(result.name).toBe('Alice');
});

// ✅ GOOD: In-memory test double (1ms per test)
test('user can update profile', () => {
  const repo = new InMemoryUserRepo();
  const user = repo.save({ name: 'Bob' });
  repo.update(user.id, { name: 'Alice' });
  expect(repo.find(user.id).name).toBe('Alice');
});
Unnecessary Sleeps and Timeouts
// 😱 BAD: Fixed delays (always wastes time)
test('async operation completes', async () => {
  triggerAsync();
  await sleep(2000); // Always waits 2 seconds
  expect(getResult()).toBe('done');
});

// ✅ GOOD: Wait for condition (only waits as long as needed)
test('async operation completes', async () => {
  triggerAsync();
  await waitFor(() => getResult() === 'done', {
    timeout: 2000,
    interval: 10
  });
  expect(getResult()).toBe('done');
});
Heavy Setup/Teardown
// 😱 BAD: Rebuild everything for each test
beforeEach(async () => {
  await buildDockerContainer();
  await seedDatabase();
  await compileAssets();
  await startServers();
});

// ✅ GOOD: Share expensive setup, isolate cheap state
beforeAll(async () => {
  // One-time expensive setup
  await startTestServer();
});

beforeEach(() => {
  // Cheap per-test isolation
  resetInMemoryDatabase();
  clearMockCalls();
});

Speed Optimization Strategies

1. Parallelize Everything
Run independent tests concurrently
// Jest
jest --maxWorkers=4

// Pytest
pytest -n auto

// Go
go test -parallel 8 ./...
2. Use Test Doubles
Replace slow dependencies with fast fakes

Database → In-memory store
HTTP calls → Mocked responses
File system → Memory filesystem
Time delays → Fake timers

3. Lazy Loading
Only set up what each test actually needs
// Instead of loading all fixtures
const getUser = lazy(() => loadFixture('user.json'));
const getOrders = lazy(() => loadFixture('orders.json'));

test('user test', () => {
  // Only loads user fixture
  const user = getUser();
});

Speed Targets

Measuring and Monitoring

# Find slow tests in Jest
jest --listTests --findRelatedTests --json

# Profile Python tests
pytest --durations=10

# Go test benchmarks
go test -bench=. -benchtime=10s

# Track test time trends in CI
- name: Run tests with timing
  run: |
    START=$(date +%s)
    npm test
    END=$(date +%s)
    echo "Tests took $((END-START)) seconds"