Skip to content

Testing Fundamentals

Before writing tests, you need to understand the different types and levels of testing.


Why We Test

  • Find bugs early (before production)
  • Make sure code works as expected
  • Allow safe refactoring (change code without breaking it)
  • Serve as documentation (tests show how code should work)

Test Levels

Unit Tests

Test one function or method in isolation.

def test_add():
    assert add(2, 3) == 5
  • Fast — run in milliseconds
  • Many — most of your tests should be unit tests
  • Isolated — no database, no network, no files

Integration Tests

Test how components work together.

def test_user_creation_in_database(db_session):
    user = create_user(db_session, "Alice")
    found = get_user(db_session, user.id)
    assert found.name == "Alice"
  • Slower — use real database or services
  • Fewer — test key interactions
  • More realistic — catch integration issues

End-to-End (E2E) Tests

Test the whole system from the user's point of view.

def test_login_flow(page):
    page.goto("/login")
    page.fill("#username", "alice")
    page.fill("#password", "secret")
    page.click("#submit")
    assert page.url == "/dashboard"
  • Slowest — use real browser and server
  • Fewest — test critical user flows only
  • Fragile — can break from UI changes

The Test Pyramid

        /  E2E  \        ← Few, slow, expensive
       / Integration \    ← Some, medium speed
      /    Unit Tests  \  ← Many, fast, cheap
Level Count Speed Cost
Unit Many Fast Low
Integration Some Medium Medium
E2E Few Slow High

Follow the pyramid

Write many unit tests, fewer integration tests, and even fewer E2E tests.


Test Naming

Good test names describe what is being tested and what the expected result is:

# Good names
def test_login_with_valid_credentials_returns_token(): ...
def test_login_with_wrong_password_raises_error(): ...
def test_empty_cart_returns_zero_total(): ...

# Bad names
def test_login(): ...
def test_1(): ...
def test_it_works(): ...

AAA Pattern

Structure each test with three parts:

def test_discount_calculation():
    # Arrange — set up test data
    price = 100.0
    discount = 0.2

    # Act — run the code being tested
    result = calculate_discount(price, discount)

    # Assert — check the result
    assert result == 80.0

What Makes a Good Test

Quality Description
Independent Does not depend on other tests
Deterministic Same result every run
Fast Runs quickly
Readable Easy to understand what is tested
Focused Tests one thing only

Best Practices

  • Follow the test pyramid — many unit, few E2E
  • Use AAA pattern (Arrange, Act, Assert)
  • Give tests descriptive names
  • Each test should check one behavior
  • Tests should not depend on each other
  • Clean up test data after each test
  • Keep tests simple — no complex logic in tests