When people say “we need more tests,” they usually mean “we need fewer production bugs.” The problem is that not all tests prevent bugs equally, and not all tests cost the same to maintain.
The best teams don’t just write tests. They write the right tests.
The Three Main Test Types
1) Unit Tests (Fast, Focused)
Unit tests validate small pieces of logic in isolation. They should run in milliseconds.
// Example (JavaScript)
import { calculateDiscount } from './pricing'
test('applies 10% discount for premium users', () => {
expect(calculateDiscount(100, 'premium')).toBe(10)
})
Good for: pure functions, business rules, edge cases.
Bad for: catching wiring issues between services, database integration, auth flows.
2) Integration Tests (Real Boundaries)
Integration tests verify that multiple components work together: your API + database, your service + queue, etc.
# Example (Python + pytest)
def test_create_user_saves_to_db(client, db):
resp = client.post('/users', json={"email": "[email protected]"})
assert resp.status_code == 201
assert db.users.find_one({"email": "[email protected]"}) is not None
Good for: catching misconfigurations, migrations, serialization issues.
Cost: slower and more brittle than unit tests, but worth it.
3) End-to-End (E2E) Tests (User-Level)
E2E tests simulate a real user clicking through your app. They are the closest to real behavior.
// Example (Playwright)
import { test, expect } from '@playwright/test'
test('user can log in and see dashboard', async ({ page }) => {
await page.goto('https://example.com')
await page.fill('#email', '[email protected]')
await page.fill('#password', 'password')
await page.click('text=Login')
await expect(page.locator('h1')).toHaveText('Dashboard')
})
Good for: preventing broken flows that unit tests never catch.
Downside: slow and flaky if you write too many.
The Test Pyramid (Practical Version)
- Most tests should be unit tests (fast and cheap)
- A smaller layer of integration tests (high value, moderate cost)
- A thin layer of E2E tests for critical user journeys (high cost, high confidence)
What I Recommend for a Typical Web App
- Unit tests for core business logic (pricing, permissions, validation)
- Integration tests for API endpoints that touch the database
- 3-8 E2E tests for the highest value flows (login, checkout, signup)
Two Real-World Tips
- Test behavior, not implementation. If you refactor and tests fail, you probably tested the wrong thing.
- Make test data realistic. Weird Unicode names, long emails, missing fields all of these show up in production.
Testing isn’t about having 100% coverage. It’s about having enough confidence to ship changes quickly without breaking users. Aim for that.
