MindForge
MindForge
DashboardHelp & Support
Dashboard
MindForge

Interview Preparation Bootcamp

Course progress

0%
0 of 177 modules complete
Start Here0/1
  • How to Use This Course
Data Structures & Algorithms0/27
  • Arrays and Hashing
  • Two Pointers
  • Sliding Window
  • Sliding Window: Harder Problems
  • Binary Search
  • Stacks
  • Practice Test: Arrays, Pointers, Windows, Search and Stacks
  • Trees: Traversals and Core Patterns
  • Binary Search Trees and Rebuilding a Tree
  • Tries (Prefix Trees)
  • Heaps and Priority Queues
  • Graphs: BFS and DFS
  • Topological Sort and Cycle Detection
  • Union-Find (Disjoint Set Union)
  • Practice Test: Trees, Heaps and Graphs
  • Dynamic Programming Basics
  • Dynamic Programming: Two Dimensions
  • Dynamic Programming: Hard String Matching
  • Backtracking
  • Backtracking: Constraint Satisfaction
  • Greedy Algorithms
  • Interval Problems
  • Bit Manipulation
  • Math and Geometry
  • String Manipulation
  • Pattern Recall and Mock Coding Rounds
  • Practice Test: DP, Backtracking, Greedy and More

System Design

Foundations0/16
  • The System Design Interview Framework
  • Back-of-the-Envelope Estimation
  • Networking and Protocols for Design
  • API Design and Idempotency
  • Load Balancing, Proxies, and Scaling Out
  • Caching and CDNs
  • Choosing a Database: Storage, Indexes and Transactions
  • Replication, Partitioning, and Sharding
  • CAP, PACELC, and Levels of Consistency
  • Messaging, Queues, and Event-Driven Architecture
  • Distributed Transactions and Data Integrity
  • Coordination, Consensus, and Distributed Locks
  • Reliability, Resilience, and Rate Limiting
  • Observability, Security, and Cost
  • System Design Cheat Sheet
  • Practice Test: System Design Foundations
Case Studies0/23
  • Design a URL Shortener
  • Design a Rate Limiter
  • Design a Notification Service
  • Design a Job Queue System
  • Design a Chat App (WhatsApp)
  • Design an Analytics Pipeline
  • Design a Multi-tenant SaaS Architecture
  • Design a Social Media Feed (Twitter/X)
  • Design a Search Autocomplete System
  • Design a File Storage Service (Dropbox)
  • Design Google Docs
  • Design a Code Review System
  • Design Netflix
  • Design YouTube
  • Design Spotify
  • Design Uber/Lyft
  • Design Airbnb
  • Design Ticketmaster (Eventbrite)
  • Design an E-commerce Cart, Checkout, and Catalog
  • Design a Payment System
  • Design a Logging and Monitoring System
  • Mock Design Rounds
  • Practice Test: System Design Case Studies
Low-Level Design0/19
  • The LLD Interview Framework and UML
  • OOP Foundations for Design
  • SOLID Principles
  • Creational Patterns
  • Structural Patterns
  • Behavioral Patterns I — Strategy, Observer, Command, State, Template Method
  • Behavioral Patterns II and Anti-Patterns
  • Concurrency in Low-Level Design
  • Design a Parking Lot
  • Design an Elevator System
  • Design a Movie Ticket Booking System
  • Design an Expense-Sharing App (Splitwise)
  • Design a Vending Machine and an ATM
  • Design a Rate Limiter and a Logging Framework
  • Design a Notification Service and an In-Memory Cache
  • Design Games — Tic-Tac-Toe, Snake and Ladder, Chess
  • Database Design for LLD
  • LLD Cheat Sheet and Recall Drill
  • Practice Test: Low-Level Design

Backend

Python0/5
  • How Python Runs Your Code
  • Functions Deep Dive: Closures, Generators, Context Managers, Scope
  • Classes Deep Dive: Method Types, MRO, Metaclasses
  • Standard Library and Serialization
  • Practice Test: Python
Django0/6
  • Django Request Lifecycle
  • Lab: Django Middleware — Request ID & Response Time
  • Django ORM and Querysets
  • Django's Cache Framework
  • Practice Test: Django
  • Django in Production: Debugging Labs
FastAPI0/5
  • FastAPI, ASGI, and Async
  • FastAPI Dependency Injection
  • FastAPI Background Tasks
  • Lab: FastAPI Async — Concurrent Aggregation
  • Practice Test: FastAPI
Databases (PostgreSQL)0/6
  • Indexing
  • Query Optimization
  • Transactions and Isolation
  • Migrations and Schema Changes
  • Hierarchical Data and Safe Concurrent Updates
  • Practice Test: Databases
Caching, Queues & Distributed Systems0/8
  • Redis Caching
  • Redis Advanced Patterns
  • Celery and Background Workers
  • Kafka and Message Queues
  • Async Pipelines
  • Distributed Systems Basics
  • Distributed Locks
  • Practice Test: Caching, Queues and Distributed Systems
APIs, Security & Deployment0/9
  • REST API Design
  • API Versioning and Error Handling
  • API Security and Authentication
  • WebSockets and Real-Time Systems
  • Docker and Containers
  • File Storage with S3
  • Testing Strategies
  • Mock Backend Round
  • Practice Test: APIs, Security and Deployment
Frontend0/38
  • HTML Document Structure and Semantics
  • CSS Box Model, Flexbox and Grid
  • CSS Architecture and Theming
  • CSS and Rendering Performance
  • Variables, Scope, Hoisting, and Truthiness
  • Functions, this, and Binding
  • Objects and Prototypal Inheritance
  • JavaScript Utility Patterns: Cloning, Caching, and Memory
  • Promises From Scratch
  • The Event Loop and Browser Rendering Pipeline
  • The DOM: Nodes, Events, and Traversal
  • Practice Test: HTML, CSS and JavaScript
  • React Rendering Fundamentals
  • React Hooks Internals
  • React Performance
  • Lab: React — Stateful Counter Component
  • State Management
  • State Machines in React
  • React Custom Hooks and Composition Patterns
  • TypeScript for React
  • React Testing
  • Practice Test: React
  • HTTP and Caching
  • Network Performance and Web Vitals
  • Code Splitting and Bundles
  • Virtualization
  • GraphQL and Data Fetching
  • Web Security
  • WebSockets and Real-time
  • SSR and Next.js
  • Micro-frontends
  • Build Tools and Bundling
  • Accessibility
  • Error Handling
  • Internationalization
  • Animation
  • Rapid Recall and Mock Frontend Rounds
  • Practice Test: Performance, Architecture and Production
Behavioral & Interview Day0/14
  • Resume and Project Walkthroughs
  • The STAR Method and Refining Your Stories
  • Conflict and Difficult Coworkers
  • Failures, Incidents, and Obstacles
  • Leadership, Mentorship, and Ownership
  • Teamwork, Communication, and Feedback
  • Growth and Handling Ambiguity
  • Career Goals, Why This Company, and Negotiation
  • Situational Questions
  • Company Research
  • Mock Behavioral Rounds
  • Interview Day
  • After the Interview
  • Practice Test: Behavioral and Interview Day
Testing Strategies
Course contents

Interview Preparation Bootcamp

Course progress

0%
0 of 177 modules complete
Start Here0/1
  • How to Use This Course
Data Structures & Algorithms0/27
  • Arrays and Hashing
  • Two Pointers
  • Sliding Window
  • Sliding Window: Harder Problems
  • Binary Search
  • Stacks
  • Practice Test: Arrays, Pointers, Windows, Search and Stacks
  • Trees: Traversals and Core Patterns
  • Binary Search Trees and Rebuilding a Tree
  • Tries (Prefix Trees)
  • Heaps and Priority Queues
  • Graphs: BFS and DFS
  • Topological Sort and Cycle Detection
  • Union-Find (Disjoint Set Union)
  • Practice Test: Trees, Heaps and Graphs
  • Dynamic Programming Basics
  • Dynamic Programming: Two Dimensions
  • Dynamic Programming: Hard String Matching
  • Backtracking
  • Backtracking: Constraint Satisfaction
  • Greedy Algorithms
  • Interval Problems
  • Bit Manipulation
  • Math and Geometry
  • String Manipulation
  • Pattern Recall and Mock Coding Rounds
  • Practice Test: DP, Backtracking, Greedy and More

System Design

Foundations0/16
  • The System Design Interview Framework
  • Back-of-the-Envelope Estimation
  • Networking and Protocols for Design
  • API Design and Idempotency
  • Load Balancing, Proxies, and Scaling Out
  • Caching and CDNs
  • Choosing a Database: Storage, Indexes and Transactions
  • Replication, Partitioning, and Sharding
  • CAP, PACELC, and Levels of Consistency
  • Messaging, Queues, and Event-Driven Architecture
  • Distributed Transactions and Data Integrity
  • Coordination, Consensus, and Distributed Locks
  • Reliability, Resilience, and Rate Limiting
  • Observability, Security, and Cost
  • System Design Cheat Sheet
  • Practice Test: System Design Foundations
Case Studies0/23
  • Design a URL Shortener
  • Design a Rate Limiter
  • Design a Notification Service
  • Design a Job Queue System
  • Design a Chat App (WhatsApp)
  • Design an Analytics Pipeline
  • Design a Multi-tenant SaaS Architecture
  • Design a Social Media Feed (Twitter/X)
  • Design a Search Autocomplete System
  • Design a File Storage Service (Dropbox)
  • Design Google Docs
  • Design a Code Review System
  • Design Netflix
  • Design YouTube
  • Design Spotify
  • Design Uber/Lyft
  • Design Airbnb
  • Design Ticketmaster (Eventbrite)
  • Design an E-commerce Cart, Checkout, and Catalog
  • Design a Payment System
  • Design a Logging and Monitoring System
  • Mock Design Rounds
  • Practice Test: System Design Case Studies
Low-Level Design0/19
  • The LLD Interview Framework and UML
  • OOP Foundations for Design
  • SOLID Principles
  • Creational Patterns
  • Structural Patterns
  • Behavioral Patterns I — Strategy, Observer, Command, State, Template Method
  • Behavioral Patterns II and Anti-Patterns
  • Concurrency in Low-Level Design
  • Design a Parking Lot
  • Design an Elevator System
  • Design a Movie Ticket Booking System
  • Design an Expense-Sharing App (Splitwise)
  • Design a Vending Machine and an ATM
  • Design a Rate Limiter and a Logging Framework
  • Design a Notification Service and an In-Memory Cache
  • Design Games — Tic-Tac-Toe, Snake and Ladder, Chess
  • Database Design for LLD
  • LLD Cheat Sheet and Recall Drill
  • Practice Test: Low-Level Design

Backend

Python0/5
  • How Python Runs Your Code
  • Functions Deep Dive: Closures, Generators, Context Managers, Scope
  • Classes Deep Dive: Method Types, MRO, Metaclasses
  • Standard Library and Serialization
  • Practice Test: Python
Django0/6
  • Django Request Lifecycle
  • Lab: Django Middleware — Request ID & Response Time
  • Django ORM and Querysets
  • Django's Cache Framework
  • Practice Test: Django
  • Django in Production: Debugging Labs
FastAPI0/5
  • FastAPI, ASGI, and Async
  • FastAPI Dependency Injection
  • FastAPI Background Tasks
  • Lab: FastAPI Async — Concurrent Aggregation
  • Practice Test: FastAPI
Databases (PostgreSQL)0/6
  • Indexing
  • Query Optimization
  • Transactions and Isolation
  • Migrations and Schema Changes
  • Hierarchical Data and Safe Concurrent Updates
  • Practice Test: Databases
Caching, Queues & Distributed Systems0/8
  • Redis Caching
  • Redis Advanced Patterns
  • Celery and Background Workers
  • Kafka and Message Queues
  • Async Pipelines
  • Distributed Systems Basics
  • Distributed Locks
  • Practice Test: Caching, Queues and Distributed Systems
APIs, Security & Deployment0/9
  • REST API Design
  • API Versioning and Error Handling
  • API Security and Authentication
  • WebSockets and Real-Time Systems
  • Docker and Containers
  • File Storage with S3
  • Testing Strategies
  • Mock Backend Round
  • Practice Test: APIs, Security and Deployment
Frontend0/38
  • HTML Document Structure and Semantics
  • CSS Box Model, Flexbox and Grid
  • CSS Architecture and Theming
  • CSS and Rendering Performance
  • Variables, Scope, Hoisting, and Truthiness
  • Functions, this, and Binding
  • Objects and Prototypal Inheritance
  • JavaScript Utility Patterns: Cloning, Caching, and Memory
  • Promises From Scratch
  • The Event Loop and Browser Rendering Pipeline
  • The DOM: Nodes, Events, and Traversal
  • Practice Test: HTML, CSS and JavaScript
  • React Rendering Fundamentals
  • React Hooks Internals
  • React Performance
  • Lab: React — Stateful Counter Component
  • State Management
  • State Machines in React
  • React Custom Hooks and Composition Patterns
  • TypeScript for React
  • React Testing
  • Practice Test: React
  • HTTP and Caching
  • Network Performance and Web Vitals
  • Code Splitting and Bundles
  • Virtualization
  • GraphQL and Data Fetching
  • Web Security
  • WebSockets and Real-time
  • SSR and Next.js
  • Micro-frontends
  • Build Tools and Bundling
  • Accessibility
  • Error Handling
  • Internationalization
  • Animation
  • Rapid Recall and Mock Frontend Rounds
  • Practice Test: Performance, Architecture and Production
Behavioral & Interview Day0/14
  • Resume and Project Walkthroughs
  • The STAR Method and Refining Your Stories
  • Conflict and Difficult Coworkers
  • Failures, Incidents, and Obstacles
  • Leadership, Mentorship, and Ownership
  • Teamwork, Communication, and Feedback
  • Growth and Handling Ambiguity
  • Career Goals, Why This Company, and Negotiation
  • Situational Questions
  • Company Research
  • Mock Behavioral Rounds
  • Interview Day
  • After the Interview
  • Practice Test: Behavioral and Interview Day
Browsing without an account — progress is saved in this browser only.Sign in to save it

Testing Strategies

Reading55 min

Every backend interview loop asks some version of "how would you test this," sometimes as its own question, more often as a follow-up right after you write code on a whiteboard. This lesson covers the testing pyramid, pytest unit tests, integration tests with fixtures, testing async code, mocking at a system boundary, the mock-versus-stub distinction, and writing tests toward a coverage target.

The testing pyramid

The pyramid is a guide to how much of each test type to write, based on speed and cost:

        /\
       /  \      E2E (few): slow, brittle, high confidence in the whole system
      /----\
     /      \    Integration (some): real DB/cache, one service boundary at a time
    /--------\
   /          \  Unit (many): fast, isolated, one function/class at a time
  /------------\
  • Unit tests check one function or class in isolation, with its dependencies mocked or faked. Each one runs in milliseconds. These should be most of your suite.
  • Integration tests check how your code talks to a real dependency, a real Postgres through a test container or a rolled-back transaction, or a real Redis. Write fewer of these, since they're slower and need real infrastructure to run.
  • End-to-end tests hit the whole system through its real interface, usually HTTP, sometimes across several services. These are the slowest and the most brittle, since any unrelated change can break one, but they catch integration bugs unit tests structurally can't see. Keep them to your critical user flows only.

If asked "what's wrong with only unit tests" or "only end-to-end tests": all-unit gives fast feedback but no confidence the pieces actually work together, since a mock can quietly drift from what the real dependency does. All-end-to-end gives high confidence but a slow, flaky suite that's expensive to maintain and hard to debug when something fails. The pyramid's shape is a deliberate trade-off between speed and confidence, not an arbitrary ratio.

Remember: unit tests are fast but can't catch integration bugs; end-to-end tests catch those bugs but are slow and brittle. Write mostly unit tests, some integration tests, and only a few end-to-end tests for the flows that matter most.

Sign in to try this knowledge check.

Unit tests with pytest

Python
# app/pricing.py
def apply_discount(price: float, percent: float) -> float:
    if not 0 <= percent <= 100:
        raise ValueError("percent must be between 0 and 100")
    return round(price * (1 - percent / 100), 2)
Python
# tests/test_pricing.py
import pytest
from app.pricing import apply_discount


def test_apply_discount_basic():
    assert apply_discount(100, 20) == 80.0


def test_apply_discount_zero_percent():
    assert apply_discount(50, 0) == 50.0


def test_apply_discount_full_discount():
    assert apply_discount(50, 100) == 0.0


@pytest.mark.parametrize("bad_percent", [-1, 101, 150])
def test_apply_discount_rejects_out_of_range(bad_percent):
    with pytest.raises(ValueError):
        apply_discount(100, bad_percent)


@pytest.mark.parametrize(
    "price, percent, expected",
    [
        (99.99, 10, 89.99),
        (10, 33.33, 6.67),
        (0, 50, 0.0),
    ],
)
def test_apply_discount_table(price, percent, expected):
    assert apply_discount(price, percent) == expected

pytest.mark.parametrize is the tool most candidates under-use. It replaces N nearly identical test functions with one table of cases, and each row shows up as its own separate result in the test report, so a failure points straight at the exact input that broke.

Good unit tests cover the happy path, boundary values (zero, negative, the maximum), and invalid input that should raise. Test only the happy path in an interview, and expect to get asked "what about X" as a follow-up, so cover it yourself first.

Remember: pytest.mark.parametrize turns a pile of copy-pasted test functions into one table, with each row reported separately on failure. Always cover the happy path, the boundaries, and the invalid input that should raise.

Sign in to try this knowledge check.

Integration tests with fixtures

Fixtures set up and tear down shared state, a DB connection, a test client, seed data, so every test starts from the same known baseline.

Python
# tests/conftest.py
import pytest
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from app.db import Base
from app.models import User

TEST_DB_URL = "postgresql://test:test@localhost:5432/test_db"


@pytest.fixture(scope="session")
def engine():
    engine = create_engine(TEST_DB_URL)
    Base.metadata.create_all(engine)
    yield engine
    Base.metadata.drop_all(engine)


@pytest.fixture
def db_session(engine):
    connection = engine.connect()
    transaction = connection.begin()
    Session = sessionmaker(bind=connection)
    session = Session()

    yield session

    # Roll back everything the test did: next test starts clean,
    # and we never pay the cost of recreating the schema per test.
    session.close()
    transaction.rollback()
    connection.close()


@pytest.fixture
def sample_user(db_session):
    user = User(username="alice", email="alice@mindforge.test")
    db_session.add(user)
    db_session.commit()
    return user
Python
# tests/test_user_repository.py
from app.repository import get_user_by_username, deactivate_user


def test_get_user_by_username(db_session, sample_user):
    found = get_user_by_username(db_session, "alice")
    assert found.id == sample_user.id


def test_deactivate_user(db_session, sample_user):
    deactivate_user(db_session, sample_user.id)
    db_session.refresh(sample_user)
    assert sample_user.is_active is False

The transaction-rollback pattern in db_session is the detail that shows real experience: it gives every test a clean database state without paying the cost of dropping and recreating the schema per test, by wrapping the whole test in a transaction that never commits.

For a FastAPI app, an integration test usually also spins up a TestClient and overrides the DB dependency to point at the test session:

Python
from fastapi.testclient import TestClient
from app.main import app
from app.dependencies import get_db


@pytest.fixture
def client(db_session):
    def override_get_db():
        yield db_session
    app.dependency_overrides[get_db] = override_get_db
    yield TestClient(app)
    app.dependency_overrides.clear()


def test_create_user_endpoint(client):
    response = client.post("/users", json={"username": "bob", "email": "bob@mindforge.test"})
    assert response.status_code == 201
    assert response.json()["username"] == "bob"

Remember: wrap each test in a transaction that rolls back at the end. That gives every test a clean database without the cost of rebuilding the schema between tests.

Sign in to try this knowledge check.

Testing async code

pytest-asyncio is the standard plugin. Two things trip people up: marking the test as async, and mocking async dependencies correctly.

Python
# pip install pytest-asyncio
import pytest
from app.services import fetch_user_profile

pytestmark = pytest.mark.asyncio  # apply to every test in this module


async def test_fetch_user_profile_success(mocker):
    mock_http = mocker.AsyncMock()
    mock_http.get.return_value.json = mocker.AsyncMock(return_value={"id": 1, "name": "Alice"})
    mocker.patch("app.services.http_client", mock_http)

    profile = await fetch_user_profile(user_id=1)

    assert profile["name"] == "Alice"
    mock_http.get.assert_awaited_once_with("/users/1")

Use AsyncMock, not Mock, for anything your code will await. A plain Mock returns another Mock object when called, not something awaitable, so await some_mock() raises TypeError: object Mock can't be used in 'await' expression. AsyncMock, built into unittest.mock since Python 3.8, returns a coroutine automatically.

Testing concurrency itself, not just one async function in isolation, needs a different approach: run the coroutines together with asyncio.gather and assert on timing or on the order events happened in, rather than only on the final state.

Python
import asyncio
import time

async def test_requests_run_concurrently():
    async def slow_call():
        await asyncio.sleep(0.2)
        return "done"

    start = time.monotonic()
    results = await asyncio.gather(slow_call(), slow_call(), slow_call())
    elapsed = time.monotonic() - start

    assert results == ["done", "done", "done"]
    assert elapsed < 0.5  # concurrent, not 0.6s sequential

Remember: mock anything the code under test will await with AsyncMock, never a plain Mock. A plain Mock's return value isn't awaitable and will raise a TypeError the moment it's awaited.

Sign in to try this knowledge check.

Mocking at the boundary

The rule to state out loud in an interview: mock at the boundary, the external dependency, not the function you're actually testing. A payment gateway client is a good boundary to mock; the business logic that calls it is what you want to actually exercise.

Python
from unittest.mock import patch, MagicMock
import pytest

from orders.services import charge_and_create_order


@patch("orders.services.payment_gateway")
def test_charge_and_create_order_success(mock_gateway):
    mock_gateway.charge.return_value = MagicMock(id="ch_123", status="succeeded")

    order = charge_and_create_order(user_id=1, amount_cents=2500)

    mock_gateway.charge.assert_called_once_with(amount_cents=2500, user_id=1)
    assert order.status == "paid"
    assert order.charge_id == "ch_123"


@patch("orders.services.payment_gateway")
def test_charge_and_create_order_gateway_failure_rolls_back(mock_gateway):
    mock_gateway.charge.side_effect = Exception("card declined")

    with pytest.raises(Exception, match="card declined"):
        charge_and_create_order(user_id=1, amount_cents=2500)

    # verify no partial order was persisted: the transaction rolled back
    from orders.models import Order
    assert not Order.objects.filter(user_id=1).exists()

The second test is worth more in an interview than a second happy-path test would be: it proves a failed external call doesn't leave a half-created order behind, that the database write genuinely rolled back. Testing a real failure mode beats testing the happy path twice.

Remember: mock the external dependency at the boundary, never the function you're actually testing. Testing that a failure rolls back cleanly is worth more than a second happy-path test.

Sign in to try this knowledge check.

Mock vs stub: the interview answer

Both are "test doubles," fake objects standing in for a real dependency, but they answer different questions.

Stub Mock
Purpose Provide canned responses so the test can run Verify interactions happened as expected
What it checks Nothing on its own; you assert on the code under test's output Asserts the code under test called it correctly (assert_called_with, assert_awaited_once)
Typical use "When this dependency is called, return X" "Prove we called send_email exactly once with these args"
Python
# Stub: just returns canned data, no assertion on how it was called
def test_get_user_greeting_stub(mocker):
    stub_repo = mocker.Mock()
    stub_repo.get_user.return_value = {"name": "Alice"}

    greeting = get_user_greeting(stub_repo, user_id=1)

    assert greeting == "Hello, Alice!"
    # We don't care HOW get_user was called, just that we got usable data back.


# Mock: asserts the interaction itself
def test_send_welcome_email_mock(mocker):
    mock_mailer = mocker.Mock()

    send_welcome_email(mock_mailer, "alice@mindforge.test")

    mock_mailer.send.assert_called_once_with(
        to="alice@mindforge.test", subject="Welcome!"
    )
    # We DO care that send() was called with exactly these args:
    # that's the whole behavior being tested here.

Rule of thumb: reach for a stub when testing what a function returns, and reach for a mock when testing that a function calls something correctly, where the call itself is the side effect you care about, like sending an email or publishing an event, with no return value to assert on instead.

Remember: a stub answers "what did the function return." A mock answers "did the function call the right thing, the right number of times, with the right arguments."

Sign in to try this knowledge check.

Writing tests to a coverage target

Coverage measures which lines executed during a test run. It's a useful floor, not a quality signal by itself, since 100% coverage with no real assertions on behavior proves nothing.

bash
pip install pytest-cov
pytest --cov=app --cov-report=term-missing --cov-fail-under=80
Name                 Stmts   Miss  Cover   Missing
--------------------------------------------------
app/pricing.py          12      0   100%
app/repository.py       28      4    86%   45-48
app/services.py         40     12    70%   88-99
--------------------------------------------------
TOTAL                    80     16    80%

--cov-report=term-missing prints the exact uncovered line numbers, so you know exactly which branch to target next, usually error-handling paths and edge cases, since the happy path tends to get covered first almost by accident. --cov-fail-under=80 makes CI fail below the threshold, which is what turns "80% coverage" from an aspiration into an enforced gate.

To hit a target deliberately: run coverage once, read the Missing column, and write one test per uncovered branch, rather than one large test that happens to touch more lines. A test aimed directly at a specific branch is the kind that actually catches a regression there later; incidental coverage from a broad test often isn't asserting anything meaningful about the branch it happens to execute.

Remember: 100% coverage with weak assertions proves nothing. Read the Missing column and write one targeted test per uncovered branch, favoring error paths, which are the ones the happy path never touches.

Sign in to try this knowledge check.
PreviousFile Storage with S3NextMock Backend Round

Course progress

0%
0 of 177 modules complete

On this page

  • The testing pyramid
  • Unit tests with pytest
  • Integration tests with fixtures
  • Testing async code
  • Mocking at the boundary
  • Mock vs stub: the interview answer
  • Writing tests to a coverage target