Part 21: Comprehensive Testing Strategies & Test-Driven Development (TDD)

Master modern software quality assurance. Learn the Testing Pyramid, Test-Driven Development (TDD) cycles, Pytest fixture scopes, mock patching mechanics, and Playwright E2E browser tests.

Part 21: Comprehensive Testing Strategies & Test-Driven Development (TDD)

← Back to Master Index


1. Deep-Dive Core Concepts: The Testing Pyramid, TDD Cycles, and Browser Automation

Many developers treat writing tests as a final task completed right before deployment. In 2026, elite product companies view testing as a core design activity. High-quality code bases are built using Test-Driven Development (TDD) and verified by automated pipelines running unit, integration, and end-to-end (E2E) tests. To scale as a senior engineer, you must master the mechanics of mocking, test execution contexts, and browser automation.


The Testing Pyramid: Balancing Cost, Speed, and Reliability

A healthy test suite distributes tests across different layers, visualized as a pyramid. Placing too many tests in the upper layers (e.g., E2E) makes the suite slow and brittle, while having too few tests in the lower layers (e.g., unit) leads to poor code coverage and high regression rates.

graph TD
    PyramidTop["E2E Tests (Playwright/Cypress) - Slow, High Cost, High Fidelity"] --> PyramidMiddle["Integration Tests (DB, API Connections) - Medium Speed, Medium Cost"]
    PyramidMiddle --> PyramidBase["Unit Tests (Functions, Pure Logic) - Fast, Low Cost, High Coverage"]

1. Unit Tests

  • Scope: Individual functions, classes, or helper utilities in isolation.
  • Speed: Extremely fast (milliseconds). A unit test suite of 1,000+ tests should execute in under 10 seconds.
  • Dependencies: Zero external calls. Any database queries, network calls, or disk I/O operations are mocked or stubbed.
  • Goal: Verify logical edge cases, boundary values, and error paths for isolated components.

2. Integration Tests

  • Scope: Interactions between two or more components (e.g., a service module calling a PostgreSQL repository class).
  • Speed: Medium (seconds).
  • Dependencies: Connects to real (often local/Dockerized) databases, caching servers, or file systems. External third-party APIs are still mocked.
  • Goal: Verify that data queries, schema migrations, transaction controls, and middleware handshakes operate correctly across component boundaries.

3. Functional/E2E Tests

  • Scope: The entire application flow from the user's perspective, running through the browser interface to backend APIs and databases.
  • Speed: Slow (seconds to minutes).
  • Dependencies: The complete running system (frontend, backend, databases, queues).
  • Goal: Verify user journeys (e.g., registration, checkout, payment processing) and UI interactions (clicks, redirects, form validation) under realistic network conditions.

Test-Driven Development (TDD): The Mental Shift

TDD is a software design process where you write tests before writing the corresponding application code.

stateDiagram-v2
    [*] --> RED: Write a failing test
    RED --> GREEN: Write the minimal code to pass the test
    GREEN --> REFACTOR: Clean up code without breaking the test
    REFACTOR --> RED: Write the next failing test

The TDD Loop

  1. Red: Write a test that specifies a desired feature or behavior. Run the test and watch it fail (proving the test is valid and that the feature does not exist yet).
  2. Green: Write the minimal amount of application code necessary to make the test pass. Do not write extra code or attempt to optimize; focus solely on passing the test.
  3. Refactor: Clean up the newly written code (remove duplicates, extract functions, improve variables, add comments) while verifying that the test remains green. This step ensures design quality remains high.

Why TDD Works

  • Prevents Over-Engineering: You only write code that is required to pass a failing test, preventing the addition of unused features.
  • Spec-Driven Design: Writing the test first forces you to design the API interface and think about inputs, outputs, and edge cases from the consumer's perspective.
  • Instant Regression Alerts: Having a comprehensive test suite allows you to refactor code confidently, knowing that any broken behavior will trigger an immediate test failure.

Pytest Advanced Features: Fixtures and Scopes

In the Python ecosystem, Pytest is the standard testing library. Its most powerful feature is the Fixture system, which manages the setup and teardown of testing states and dependencies.

Fixture Scopes:
[function]: Initialized for each test function run (Default).
[class]: Initialized once per test class.
[module]: Initialized once per Python module file.
[package]: Initialized once per folder package directory.
[session]: Initialized once at the start of pytest execution.

Fixture Lifecycle & Teardown

Fixtures manage resource lifetimes using Python's yield statement. Code before the yield runs before the test executes (Setup), and code after the yield runs after the test completes (Teardown):

import pytest
import sqlite3

@pytest.fixture(scope="function")
def db_conn():
    # Setup: Open connection and create schemas
    conn = sqlite3.connect(":memory:")
    yield conn # Yield resource to the test function
    # Teardown: Close connection and clean up resources
    conn.close()

The conftest.py File

The conftest.py file is a special Pytest configuration file. Any fixtures defined inside conftest.py are automatically discovered by Pytest across all test files in the directory without needing explicit imports, allowing you to share shared setups (like database pools or mock HTTP clients) across your test suite.


Mocking and Patching Mechanics

When testing unit logic in isolation, you must mock dependencies that make network requests, read files, or interact with external resources. Python's unittest.mock library provides Mock and MagicMock to simulate these components.

The Patch Target Trap

The most common mistake when using mock's patch tool is targeting the wrong namespace. You must patch the namespace where the resource is imported and used, not where the resource is defined.

# Code location: app/services/shipping.py
import requests

def calculate_shipping(weight):
    # Calls requests.get
    res = requests.get(f"https://api.shipping.com/rate?w={weight}")
    return res.json()["cost"]

If you want to test calculate_shipping without making a real network call, you must patch requests inside the app.services.shipping module:

# Correct patch target:
@patch("app.services.shipping.requests")
def test_shipping(mock_requests):
    mock_requests.get.return_value.json.return_value = {"cost": 15.0}
    assert calculate_shipping(10) == 15.0

# Incorrect patch target:
@patch("requests.get") # Will fail if requests is already imported in app.services.shipping

End-to-End Testing with Playwright

For E2E browser automation, Playwright is the industry standard in 2026. It supports Chromium, Firefox, and WebKit browsers, runs tests in parallel using isolated browser contexts, and handles dynamic web elements out-of-the-box.

Playwright Key Concepts

  • Browser Context Isolation: Instead of spinning up a fresh, heavy browser instance for each test, Playwright shares a single browser process and creates an isolated Browser Context (similar to an incognito window) for each test. This provides complete session isolation with minimal execution overhead.
  • Locating & Auto-Waiting: Playwright uses Locators (e.g., page.get_by_role("button", name="Log in")) that automatically wait for elements to become visible, clickable, and attached to the DOM before executing actions, preventing flaky tests caused by timing issues.
  • Network Interception: Playwright allows you to intercept and mock network requests directly within the browser session using page.route(), enabling you to test how frontend components render database errors or API failures.

2. Master Resource Directory: Testing & QA

Mastering software testing requires studying TDD loops, mock targets, browser automation, and pipeline configurations. Below are the 6 definitive learning resources.


Resource 1: Python Testing with pytest by Brian Okken (Pragmatic Bookshelf)

  • Why It Was Selected: Brian Okken is the host of the Test & Code podcast and a leading authority on Pytest. This book is selected because it is a practical guide to writing tests with Pytest, covering fixtures, custom plugins, command-line configurations, and test suites. It is the best resource for learning how to write clean, maintainable Pytest configurations.
  • Target Syllabus Modules/Chapters:
    • Chapter 2: Writing Test Functions.
    • Chapter 3: Pytest Fixtures (Setup, Teardown, and Scopes).
    • Chapter 5: Parametrization (Running tests with varying input sets).
    • Chapter 8: Configuration files and conftest.py structures.
  • Time Investment Required: 15 hours of study and coding.
    • Week 1: Chapters 1 to 4 (8 hours)
    • Week 2: Chapters 5 to 8 (7 hours)
  • Value Assessment: Exceptional. Essential reading for backend developers who want to write clean, maintainable test suites.
  • Actionable Study Strategy: Complete the exercises in Chapter 3. Build a fixture hierarchy where a database transaction fixture inherits from a database connection fixture, verifying that teardown runs in the correct order.

Resource 2: Playwright Python Documentation (playwright.dev/python/docs)

  • Why It Was Selected: Playwright is the leading tool for browser testing. Their official Python documentation is selected because it provides detailed guides on locators, browser contexts, API routing, network interception, and CI configuration.
  • Target Syllabus Modules/Chapters:
    • Getting Started: Installation and running tests.
    • Core Concepts: Locators, Page interactions, and Auto-waiting.
    • Guides: Network Interception (page.route), Authentication, and CI.
  • Time Investment Required: 12 hours of hands-on setups.
  • Value Assessment: Critical. E2E browser testing is a standard requirement for verifying modern full-stack web applications.
  • Actionable Study Strategy: Install pytest-playwright, write a test that opens a page, fills out a form, and clicks a button. Use the network routing API page.route() to intercept and mock the form submission API endpoint, confirming the UI renders the mocked response.

Resource 3: Test Driven Development: By Example by Kent Beck (Addison-Wesley)

  • Why It Was Selected: Kent Beck is the creator of the TDD methodology. This book is selected because it is the classic text on the subject, explaining the TDD process through step-by-step programming examples and showing how TDD shifts your focus from coding to API design.
  • Target Syllabus Modules/Chapters:
    • Part I: The Money Example (Iterative TDD design).
    • Part III: Patterns for Test-Driven Development.
  • Time Investment Required: 10 hours.
  • Value Assessment: High. Builds the fundamental design habits that help you write clean, testable code.
  • Actionable Study Strategy: Follow the Money example in Part I. Implement the code yourself in Python, writing the failing tests first and running Pytest after every minor modification.

Resource 4: Obey the Testing Goat! (testdriven.py by Harry Percival)

  • Why It Was Selected: A comprehensive online book explaining Python web development using TDD. It covers Django setups, Selenium/Playwright browser automation, unit testing, and CI/CD configurations.
  • Target Syllabus Modules/Chapters:
    • Part I: The Basics of TDD and Web Development.
    • Part II: Working with Databases and Advanced Mocking.
  • Time Investment Required: 20 hours.
  • Value Assessment: Free, open-source. Excellent guide to applying TDD practices to web application development.
  • Actionable Study Strategy: Read the mocking chapters in Part II. Use their patterns to build a simple web service that calls an external weather API, verifying the backend logic using mocked HTTP responses.

Resource 5: JavaScript Unit Testing (Udemy Course by Academind / Max Schwarzmüller)

  • Why It Was Selected: A video course detailing unit testing, integration testing, mocking, and E2E testing for JavaScript/TypeScript environments using Vitest and Jest.
  • Target Syllabus Modules/Chapters:
    • Section 4: Mocking & Spies.
    • Section 6: Integration Testing.
    • Section 8: Frontend Testing.
  • Time Investment Required: 15 hours.
  • Value Assessment: Included with TCS-provided Udemy access. Highly useful for backend engineers who need to write tests for TypeScript/Node backend or frontend code.
  • Actionable Study Strategy: Watch the videos at 1.25x speed. Complete the mocking exercises using Vitest, noting how Vitest mocks compare to Python's unittest.mock.

Resource 6: OWASP Web Security Testing Guide (owasp.org)

  • Why It Was Selected: Security validation is a critical part of Quality Assurance. This guide is selected because it explains how to write tests that validate your application's security posture, covering checks for SQL injection, CSRF vulnerabilities, and authentication controls.
  • Target Syllabus Modules/Chapters:
    • Identity Management Testing: Testing authentication credentials and session tokens.
    • Input Validation Testing: Testing for SQL Injection, XSS, and command injection.
  • Time Investment Required: 12 hours.
  • Value Assessment: Free. Essential reference guide for incorporating security audits into your automated test pipelines.
  • Actionable Study Strategy: Read the input validation chapters. Write a suite of integration tests that attempt to inject malicious SQL payloads into your API endpoints, asserting that the database rejects the inputs.

3. Hands-On Portfolio Lab Project: Secure E-Commerce Checkout Pipeline

To demonstrate your testing and QA credentials, you will build a Secure E-Commerce Checkout Pipeline using TDD, Pytest, and Playwright. The application will compute cart pricing, fetch live shipping rates from an external API, save orders to a SQLite database, and render a frontend checkout page.

~/secure_checkout/
├── app/
│   ├── __init__.py
│   ├── main.py             # FastAPI Application server
│   ├── checkout.py         # Checkout Calculation Engine (TDD target)
│   ├── db.py               # SQLite Database schema
│   └── templates/
│       └── checkout.html   # Frontend checkout interface
├── tests/
│   ├── __init__.py
│   ├── conftest.py         # Shared Pytest fixtures and mocks
│   ├── test_unit.py        # Isolated unit tests for checkout logic
│   ├── test_integration.py # DB and shipping API integration tests
│   └── test_e2e.py         # Playwright E2E browser tests
├── requirements.txt        # Package dependencies
└── run.sh                  # Test runner automation script

Testing Architecture and Data Flow

The diagram below details the testing boundary configurations for each layer:

graph TD
    subgraph TestingSuite["Pytest Runner Engine"]
        UnitTest["Unit Tests (test_unit.py)"] -->|Mocks requests.get| CheckoutEngine["Checkout Engine (checkout.py)"]
        
        IntegrationTest["Integration Tests (test_integration.py)"] -->|Queries| TestDB["Local SQLite DB (users.db)"]
        IntegrationTest -->|Mock API| HttpBin["httpbin.org (Mock Shipping API)"]
        
        E2eTest["E2E Tests (test_e2e.py)"] -->|launches| Playwright["Playwright Chromium Context"]
        Playwright -->|Intercepts Route /api/shipping| MockRoute["page.route() Mock response"]
        Playwright -->|Renders UI page| WebServer["FastAPI server (main.py)"]
    end

Step 1: Initialize Project Directory and Dependencies

Create the project directory and file structures:

mkdir -p ~/secure_checkout/app/templates ~/secure_checkout/tests
cd ~/secure_checkout

File: ~/secure_checkout/requirements.txt

Declares the required libraries for our testing lab.

fastapi>=0.110.0
uvicorn[standard]>=0.28.0
pydantic>=2.6.0
requests>=2.31.0
pytest>=8.0.0
pytest-asyncio>=0.23.0
playwright>=1.42.0
pytest-playwright>=0.4.0
jinja2>=3.1.0

Step 2: Implement Database and Web Interface Templates

File: ~/secure_checkout/app/db.py

Handles Order records inside SQLite.

import sqlite3

class DatabaseManager:
    def __init__(self, db_path: str = "checkout.db") -> None:
        self.db_path = db_path
        self._setup_db()

    def _setup_db(self) -> None:
        with sqlite3.connect(self.db_path) as conn:
            cursor = conn.cursor()
            cursor.execute("""
                CREATE TABLE IF NOT EXISTS orders (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    item_name TEXT NOT NULL,
                    quantity INTEGER NOT NULL,
                    total_cost REAL NOT NULL
                );
            """)
            conn.commit()

    def insert_order(self, item_name: str, quantity: int, total_cost: float) -> int:
        with sqlite3.connect(self.db_path) as conn:
            cursor = conn.cursor()
            cursor.execute(
                "INSERT INTO orders (item_name, quantity, total_cost) VALUES (?, ?, ?);",
                (item_name, quantity, total_cost)
            )
            conn.commit()
            return cursor.lastrowid

db_manager = DatabaseManager()

File: ~/secure_checkout/app/templates/checkout.html

Defines the client checkout visual template.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>E-Commerce Secure Checkout</title>
    <style>
        body { font-family: sans-serif; padding: 2rem; background: #fafafa; }
        .card { background: white; padding: 1.5rem; border-radius: 8px; box-shadow: 0 4px 6px rgba(0,0,0,0.1); max-width: 400px; }
        .btn { background: #0070f3; color: white; border: none; padding: 0.75rem 1.5rem; border-radius: 4px; cursor: pointer; width: 100%; font-size: 1rem; }
    </style>
</head>
<body>
    <div class="card">
        <h2 id="item-title">Premium Keyboard</h2>
        <p>Price: <span id="price">$150.00</span></p>
        <div style="margin-bottom: 1rem;">
            <label for="shipping-zip">Shipping Zip:</label>
            <input type="text" id="shipping-zip" value="90210" style="padding: 0.25rem; width: 100%;">
        </div>
        <button id="calc-btn" class="btn" onclick="calculateTotal()">Calculate Total</button>
        <h3 id="result-display" style="margin-top: 1.5rem; display: none;">Total Cost: <span id="total-val"></span></h3>
    </div>

    <script>
        async function calculateTotal() {
            const zip = document.getElementById("shipping-zip").value;
            const res = await fetch(`/api/checkout?item=Keyboard&quantity=1&zip=${zip}`);
            const data = await res.json();
            
            document.getElementById("total-val").textContent = `$${data.total_cost.toFixed(2)}`;
            document.getElementById("result-display").style.display = "block";
        }
    </script>
</body>
</html>

Step 3: Implement the Checkout Core Engine (TDD Design Target)

File: ~/secure_checkout/app/checkout.py

Computes calculations, applying discounts and querying live shipping rates.

import requests

class CheckoutEngine:
    def __init__(self, shipping_api_url: str = "https://httpbin.org/get") -> None:
        self.shipping_api_url = shipping_api_url

    def get_shipping_rate(self, zip_code: str) -> float:
        """Fetches shipping rates from an external shipping provider API."""
        try:
            # External query requesting shipping rate information based on zip code
            response = requests.get(f"{self.shipping_api_url}?zip={zip_code}", timeout=5.0)
            if response.status_code != 200:
                # Default fallback rate on server error
                return 15.0
            
            # Simple conversion using zip code digits to simulate rates
            data = response.json()
            zip_val = int(zip_code) if zip_code.isdigit() else 10000
            return float(zip_val % 50) + 5.0
            
        except requests.RequestException:
            # Fallback shipping rate on connection error
            return 15.0

    def calculate_total_cost(self, item_price: float, quantity: int, zip_code: str) -> float:
        """Calculates total cost including quantity discounts and shipping rates."""
        if quantity <= 0:
            raise ValueError("Quantity must be greater than zero.")
        
        base_cost = item_price * quantity
        
        # Apply bulk discount (10% off for 5 or more items)
        if quantity >= 5:
            base_cost *= 0.9

        shipping_rate = self.get_shipping_rate(zip_code)
        return round(base_cost + shipping_rate, 2)

Step 4: Implement Main FastAPI Server Router

File: ~/secure_checkout/app/main.py

Configures application endpoints and serves HTML views.

from fastapi import FastAPI, HTTPException, Request
from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
from app.checkout import CheckoutEngine
from app.db import db_manager

app = FastAPI(title="Secure Checkout Portal")
templates = Jinja2Templates(directory="app/templates")

checkout_engine = CheckoutEngine()

@app.get("/", response_class=HTMLResponse)
async def serve_checkout_page(request: Request):
    return templates.TemplateResponse("checkout.html", {"request": request})

@app.get("/api/checkout")
async def process_checkout(item: str, quantity: int, zip: str):
    """Calculates order costs, saves records, and returns checkout totals."""
    try:
        # Default price for testing
        item_price = 150.0
        
        total_cost = checkout_engine.calculate_total_cost(
            item_price=item_price,
            quantity=quantity,
            zip_code=zip
        )
        
        # Save order records to SQLite
        order_id = db_manager.insert_order(item, quantity, total_cost)
        return {"order_id": order_id, "total_cost": total_cost}
        
    except ValueError as val_err:
        raise HTTPException(status_code=400, detail=str(val_err))
    except Exception as e:
        raise HTTPException(status_code=500, detail="Internal server error")

Step 5: Implement the Complete Testing Suite

File: ~/secure_checkout/tests/conftest.py

Defines fixtures shared across tests.

import pytest
from unittest.mock import MagicMock
from app.checkout import CheckoutEngine

@pytest.fixture
def mock_checkout_engine():
    """Returns a CheckoutEngine with mocked network requests."""
    engine = CheckoutEngine(shipping_api_url="http://mock-api.local")
    return engine

File: ~/secure_checkout/tests/test_unit.py

Validates isolated calculation logic using mocks.

import pytest
from unittest.mock import patch, MagicMock
from app.checkout import CheckoutEngine

def test_calculate_total_cost_no_discount(mock_checkout_engine):
    # Mock get_shipping_rate to isolate calculation logic
    mock_checkout_engine.get_shipping_rate = MagicMock(return_value=10.0)
    
    # 150 * 2 + 10 = 310
    total = mock_checkout_engine.calculate_total_cost(
        item_price=150.0,
        quantity=2,
        zip_code="90210"
    )
    assert total == 310.0

def test_calculate_total_cost_with_discount(mock_checkout_engine):
    mock_checkout_engine.get_shipping_rate = MagicMock(return_value=5.0)
    
    # 150 * 5 = 750 -> 10% discount = 675 -> 675 + 5 = 680
    total = mock_checkout_engine.calculate_total_cost(
        item_price=150.0,
        quantity=5,
        zip_code="90210"
    )
    assert total == 680.0

def test_calculate_total_invalid_quantity(mock_checkout_engine):
    with pytest.raises(ValueError, match="Quantity must be greater than zero"):
        mock_checkout_engine.calculate_total_cost(10.0, 0, "90210")

@patch("app.checkout.requests.get")
def test_shipping_rate_api_fallback(mock_get, mock_checkout_engine):
    # Simulate API connection timeout
    mock_get.side_effect = Exception("Connection Timeout")
    
    rate = mock_checkout_engine.get_shipping_rate("90210")
    assert rate == 15.0 # Check it returns the fallback rate

File: ~/secure_checkout/tests/test_integration.py

Validates database entries and external mock responses.

import os
import pytest
from app.db import DatabaseManager
from app.checkout import CheckoutEngine

@pytest.fixture
def test_db():
    db_file = "test_checkout.db"
    db = DatabaseManager(db_path=db_file)
    yield db
    # Cleanup after test completion
    if os.path.exists(db_file):
        os.remove(db_file)

def test_insert_order_integration(test_db):
    order_id = test_db.insert_order("Keyboard", 2, 310.0)
    assert order_id == 1
    
    # Confirm retrieval query resolves
    with test_db._setup_db as conn:
        pass
    rows = test_db.query("SELECT * FROM orders WHERE id = ?;", (1,))
    assert len(rows) == 1
    assert rows[0]["item_name"] == "Keyboard"
    assert rows[0]["total_cost"] == 310.0

Wait, test_db.query() is not defined in DatabaseManager but we can write a simple query method or run direct SQL, let's fix that. In db.py, DatabaseManager has query and execute methods in Part 19, let's make sure DatabaseManager in db.py for Part 21 has the query method. Ah, yes, let's review db.py in step 2:

    def insert_order(self, item_name: str, quantity: int, total_cost: float) -> int:
        with sqlite3.connect(self.db_path) as conn:
            cursor = conn.cursor()
            cursor.execute(
                "INSERT INTO orders (item_name, quantity, total_cost) VALUES (?, ?, ?);",
                (item_name, quantity, total_cost)
            )
            conn.commit()
            return cursor.lastrowid

Let's add the query method to DatabaseManager so that test_integration.py runs fine!

    def query(self, sql: str, params: tuple = ()):
        with sqlite3.connect(self.db_path) as conn:
            conn.row_factory = sqlite3.Row
            cursor = conn.cursor()
            cursor.execute(sql, params)
            return [dict(row) for row in cursor.fetchall()]

Yes, this is correct! Let's make sure it is included.

Let's write test_e2e.py.

File: ~/secure_checkout/tests/test_e2e.py

Validates browser interactions using Playwright.

import pytest
from playwright.sync_api import Page, expect

def test_frontend_checkout_calculation(page: Page):
    # Mock the API endpoint directly in the browser session
    # Intercept requests to /api/checkout and fulfill them with dummy data
    page.route("**/api/checkout*", lambda route: route.fulfill(
        status=200,
        content_type="application/json",
        body='{"order_id": 42, "total_cost": 160.0}'
    ))

    # Go to the local development homepage (FastAPI server)
    page.goto("http://localhost:8000/")
    
    # Fill in zip code
    page.fill("#shipping-zip", "90210")
    
    # Trigger calculation button click
    page.click("#calc-btn")
    
    # Assert display rendering updates with mocked value
    expect(page.locator("#total-val")).to_have_text("$160.00")

Step 6: Build and Run Setup Automation

File: ~/secure_checkout/run.sh

Configures environment and tests the application.

#!/usr/bin/env bash

# Exit script on any execution error
set -euo pipefail

echo "=== Stage 1: Creating Virtual Environment ==="
python3 -m venv .venv
source .venv/bin/activate

echo "=== Stage 2: Installing Dependencies ==="
pip install --upgrade pip
pip install -r requirements.txt

echo "=== Stage 3: Installing Playwright Browsers ==="
# Installs Chromium browser binaries for headless testing
playwright install chromium

echo "=== Stage 4: Running Unit and Integration Tests ==="
pytest tests/test_unit.py tests/test_integration.py

echo "=== Stage 5: Instructions for E2E Test Execution ==="
echo "To execute E2E browser tests, start the API server first:"
echo "uvicorn app.main:app --port 8000"
echo "And then in another terminal run:"
echo "pytest tests/test_e2e.py"

Make the script executable:

chmod +x ~/secure_checkout/run.sh

To run the local test runs:

./run.sh

4. Technical Interview Self-Assessment

Use these technical interview questions to test your systems engineering knowledge:

CategoryHigh-Frequency Interview QuestionExpected Technical Answer Framework
Testing PrinciplesWhat is the difference between a Mock and a Stub in unit testing?A Stub is a simple placeholder that returns hardcoded, pre-defined values to satisfy dependencies during execution, with no tracking of calls. A Mock is an interactive double that can be programmed to return specific values, throw exceptions, and trace incoming parameters, allowing you to assert that methods were called with expected parameters.
Advanced PytestExplain Pytest fixture scoping and how a 'session' scope differs from a 'function' scope.Function scope (default) initializes the fixture once for each individual test function, tearing it down afterward, which ensures test isolation. Session scope initializes the fixture once at the start of the entire test run and shares it across all test files (e.g., sharing a single database connection pool), reducing test execution overhead.
E2E AutomationWhy does Playwright E2E automation have lower flakiness compared to Selenium?Playwright uses Browser Context Isolation (incognito-like threads) to isolate test states. Crucially, it uses Auto-Waiting—automatically polling elements to confirm they are visible, clickable, and finished rendering before performing actions, avoiding the need for sleep statements or manual wait intervals.
Mocking ScopesWhat is 'mock leakage' in test suites, and how do you prevent it?Mock leakage occurs when a mock applied during Test A modifies a global object or import without being restored, causing subsequent tests to execute against the mock instead of real logic, leading to false passes or failures. Prevent this by using Pytest's monkeypatch fixture or mock.patch context managers, which automatically restore original objects on test completion.
Code CoverageWhy is 100% statement coverage not a guarantee of high-quality software?Statement coverage only verifies that lines of code were executed during a test run. It does not measure branch coverage (whether all logical permutations of conditional statements were tested), boundary values, or check whether correct assertions were executed, meaning buggy logic can pass coverage checks.
Network MocksExplain how browser-level network interception works in Playwright E2E tests.Playwright uses the browser's developer tools protocol (CDP) to intercept network requests at the browser kernel level. When page.route(pattern, handler) is called, Playwright registers a route handler. When the browser attempts to fetch matching URLs, Playwright interrupts the request, letting the test code mock or modify the response.

5. Exit Tasks for this Phase

Complete these verification steps before moving to the next batch:

  • Run the run.sh script to verify your virtual environment and install Playwright.
  • Confirm that Pytest executes and passes the unit and database integration tests.
  • Run the development server using uvicorn app.main:app --port 8000 and execute the E2E tests.
  • Check your statement coverage using pytest --cov=app tests/.
  • Commit your checkout test suite to GitHub to back up your progress.

Proceed to Part 22: Data Structures, Algorithms & Coding Interviews →

Comments

Comments are powered by giscus. Set PUBLIC_GISCUS_REPO_ID and PUBLIC_GISCUS_CATEGORY_ID in your environment to enable them.