Testing Overview
Testing Overview
Section titled “Testing Overview”Automated testing gives you confidence that your code does what it’s supposed to do — and keeps doing it as the codebase grows.
The Testing Pyramid
Section titled “The Testing Pyramid” /\ /E2E\ ← Few, slow, expensive /------\ / Integ \ ← Some /----------\ / Unit \ ← Many, fast, cheap /______________\| Level | Scope | Speed | Cost | Quantity |
|---|---|---|---|---|
| Unit | Single function / class | < 1ms | Low | Many |
| Integration | Multiple components together | Seconds | Medium | Some |
| End-to-End | Full user journey | Minutes | High | Few |
Test Types Explained
Section titled “Test Types Explained”Unit Tests
Section titled “Unit Tests”Test a single unit of code in isolation. External dependencies (databases, APIs, time) are mocked.
- Fast — run in milliseconds
- Precise — pinpoint exactly what broke
- Fragile to refactoring if testing implementation not behaviour
Integration Tests
Section titled “Integration Tests”Test multiple components working together — e.g., a service + database, or an API endpoint + controller + database layer.
- Slower than unit tests
- Catch issues that unit tests miss (misconfigured ORM, wrong SQL, mismatched interfaces)
- More maintenance overhead
End-to-End Tests
Section titled “End-to-End Tests”Drive the application like a real user — browser automation (Playwright, Cypress) or API calls that hit the live system.
- Most confidence — tests what users actually experience
- Slowest to run, most brittle
- Expensive to write and maintain
Key Principles
Section titled “Key Principles”AAA (Arrange, Act, Assert):
Arrange — set up the system under test and its dependenciesAct — call the thing being testedAssert — verify the expected outcomeFIRST:
- Fast — unit tests should run in milliseconds
- Independent — tests should not depend on each other or run order
- Repeatable — same result every time, regardless of environment
- Self-validating — pass or fail without manual inspection
- Timely — written alongside (or before) the code
Test behaviour, not implementation: Test what a function does, not how it does it internally. Tests that check internal state break when you refactor.
What Makes a Good Test
Section titled “What Makes a Good Test”# Bad test — checks internal state, too tightly coupleddef test_add_to_cart(): cart = ShoppingCart() cart.add("item-1", qty=2) assert cart._items["item-1"] == 2 # internal structure
# Good test — checks observable behaviourdef test_add_to_cart(): cart = ShoppingCart() cart.add("item-1", qty=2) assert cart.total_items() == 2 assert cart.contains("item-1")Test Coverage
Section titled “Test Coverage”Coverage measures what % of your code is executed by tests. It’s a useful signal but not a goal in itself:
- 100% coverage doesn’t mean no bugs
- Low coverage is a red flag
- Aim for high coverage on business logic, less on boilerplate
# JavaScript (Jest)npx jest --coverage
# Python (pytest)pytest --cov=src --cov-report=html
# C# (.NET)dotnet test /p:CollectCoverage=true /p:CoverletOutputFormat=lcovTooling Reference
Section titled “Tooling Reference”| Language | Unit Testing | Mocking | E2E |
|---|---|---|---|
| JavaScript/TypeScript | Jest, Vitest | Jest mocks, MSW | Playwright, Cypress |
| Python | pytest | pytest-mock, unittest.mock | Playwright |
| C# | xUnit, NUnit, MSTest | Moq, NSubstitute | Playwright |
| Go | testing (stdlib) | testify | Playwright |
| Java | JUnit | Mockito | Playwright, Selenium |