█
LastWrite
  • > Curriculum
  • > Pricing
  • > For Educators
  • > About
  • > Contact
Log InGet Started

Questions, concerns, bug reports, or suggestions? We read every message, write to us at [email protected].

More ways to reach us →
LastWrite

Structured computer science lessons for aspiring developers and security professionals.

[email protected]

(201) 785-7951

Mon–Fri, 9 AM–5 PM EST

Learn

  • Curriculum
  • Pricing

Company

  • About
  • For Educators & Schools
  • Contact Us

Legal

  • Terms of Service
  • Privacy Policy
© 2026 LastWrite. All rights reserved.
Curriculum/Software Engineering/Engineering Fundamentals/Testing Fundamentals
60 minIntermediate

Testing Fundamentals

After this lesson, you will be able to: Write unit, integration, and end-to-end tests for JavaScript/TypeScript (Vitest or Jest) and Python (pytest), understand test-driven development, and decide what to test.

Testing is how you ship without fear. This lesson covers the three test layers, what each is for, how to set up Vitest in a JavaScript project and pytest in a Python one, and what test-driven development actually is.

Prerequisites:Debugging Systematically

Three test layers

Unit tests: test ONE function in isolation. Fast (milliseconds), many (hundreds). Use them to lock in pure logic. Integration tests: test how multiple modules wire together (e.g. your API route calling the DB). Slower (seconds), fewer (dozens). Use them to catch boundary bugs. End-to-end (e2e) tests: drive the real app from outside (a browser, an HTTP client). Slowest (tens of seconds), fewest (handful). Use them to verify the critical user journeys work.

The test pyramid (and when to flip it)

Default: lots of unit tests, a moderate number of integration tests, a handful of e2e tests. When the codebase has good types and few pure functions (e.g. a thin web app), inverting the pyramid (heavy integration, light unit) can give more confidence per test. What matters is: tests catch real regressions and run fast enough that you actually run them.

Vitest setup and your first test

Vitest is the modern Vite-native test runner. It works for any TS/JS project even without Vite.

python
// package.json
{
"scripts": { "test": "vitest" },
"devDependencies": { "vitest": "^1.0.0" }
}
// src/math.ts
export function add(a: number, b: number): number {
return a + b;
}
// src/math.test.ts
import { describe, it, expect } from "vitest";
import { add } from "./math";
describe("add", () => {
it("sums two positive numbers", () => {
expect(add(2, 3)).toBe(5);
});
it("handles negatives", () => {
expect(add(-1, 1)).toBe(0);
});
it("handles zero", () => {
expect(add(0, 0)).toBe(0);
});
});
// Run: npm test
// Watch mode: npm test -- --watch

Integration test against a real database

Use a test database (Docker Postgres or a local SQLite for speed). Never test against production.

python
// src/orders.test.ts
import { beforeEach, afterAll, describe, it, expect } from "vitest";
import { db } from "./db";
import { createOrder, listOrdersForUser } from "./orders";
beforeEach(async () => {
await db.query("TRUNCATE orders RESTART IDENTITY CASCADE");
});
afterAll(async () => { await db.end(); });
describe("orders", () => {
it("persists a new order", async () => {
const order = await createOrder({ userId: "u1", total: 42.00 });
const list = await listOrdersForUser("u1");
expect(list).toHaveLength(1);
expect(list[0].total).toBe(42.00);
});
});

The same idea in Python with pytest

pytest is the standard Python test runner. Tests are plain functions named test_*; assertions use the built-in assert.

python
# math.py
def add(a, b):
return a + b
# test_math.py (pytest discovers test_*.py files and test_* functions)
from math import add
def test_sums_two_positives():
assert add(2, 3) == 5
def test_handles_negatives():
assert add(-1, 1) == 0
# Fixtures provide reusable setup (like a temp DB connection):
import pytest
@pytest.fixture
def sample_user():
return {"id": "u1", "name": "Alex"}
def test_uses_fixture(sample_user):
assert sample_user["name"] == "Alex"
# Install: pip install pytest Run: pytest Coverage: pytest --cov

Test-driven development (TDD): red, green, refactor

TDD flips the order: write a failing test FIRST (red), write the minimum code to make it pass (green), then clean up the code with the test as your safety net (refactor). Repeat in small loops. When it shines: the requirements are clear but the design is fuzzy. Writing the test first forces you to design the interface from the caller's point of view before you commit to an implementation. When to skip it: exploratory or throwaway code, or when the design is already obvious (write the test right after). TDD is a tool, not a religion. The goal is working, tested code, whichever order gets you there.

What to test and what NOT to test

Test: business rules, edge cases, regression scenarios you've actually hit, anything that's broken before. Test: pure functions; the result depends only on the input, so the test is short and reliable. Don't test: framework code (React's useState works; you don't need to verify it). Don't test: trivial getters or types (the type checker handles them). Don't test: implementation details that will change as you refactor. Test BEHAVIOR (what the user sees), not internals.

Common mistakes only experienced testers avoid

Mocking everything until tests pass but the real system is broken. Tests should exercise real code where possible. Skipping the regression test after a bug fix. The next deploy can silently undo your work. Brittle tests that break on every refactor. Usually means they test implementation, not behavior. Tests that depend on time, network, or random values without being controlled. Use fake timers, mock APIs, fixed seeds. Treating coverage % as the goal. 90% coverage of trivial code teaches you less than 30% coverage of the critical paths.

Quick Check

You changed a function and now 50 tests fail. What's the most likely cause?

Pick the most likely diagnosis.

Sign in and purchase access to unlock this lesson.

Sign in to purchase
←Debugging Systematically
Back to Engineering Fundamentals
Code Review→