Week 14 of 16

Testing Fundamentals

Why code that works once isn't code you can trust — and how pytest changes that

Day 66 75 minutes Watch

Day 66 of 80

The Testing Problem

What You're Doing Right Now

Every time you want to check if your code works, you probably do something like this:

  1. Open a terminal
  2. Run the app
  3. Type some inputs and look at the output
  4. Decide "looks good" and move on

That works when you have 50 lines of code. It breaks down fast when you have 500 — or when you change something in Week 15 and discover you quietly broke something from Week 11 that you haven't touched since.

The Hidden Cost of Manual Testing

Manual testing doesn't scale. As the DVP Prompt Vault grows — more commands, more edge cases, more platforms — you cannot manually verify every path every time you make a change. One missed edge case ships as a bug.

Automated tests are code that checks other code. You write them once, run them in a single command, and instantly know if anything broke. The test suite is your safety net — it lets you refactor, extend, and improve with confidence.

What a Test Is

The Three-Step Structure of Every Test

No matter how complex, every test follows the same three-step structure:

  1. Set up data — create the objects, variables, and state needed for the test
  2. Run the code — call the function or method you're testing
  3. Check the result — assert that the output matches what you expected

This pattern is sometimes called Arrange / Act / Assert (AAA). Keep it in your head — every test you write for the rest of this course follows it.

test_example.py python
def test_platform_is_valid():
    # 1. Arrange — set up the data
    platform = "Kling"
    valid_platforms = ["Kling", "Runway", "Veo"]

    # 2. Act — run the code being tested
    result = platform in valid_platforms

    # 3. Assert — check the result
    assert result is True
This test has no dependencies on a running server, a file, or user input. It just checks one logical thing. That's the goal: small, focused, independent tests.

Watch: pytest from the Ground Up

ResourceLengthFocus
Corey Schafer — Python Unit Testing with pytest ~40 min Writing real test functions, running pytest, reading output
How to Watch

Don't just watch passively. Pause when Corey writes a test function and type it yourself. Run it. Break it on purpose and watch the output. The failure message is your first real debugging tool in pytest.

After watching, skim the pytest section of this article: Real Python — Getting Started with Testing in Python. Focus on the sections about pytest specifically, not unittest.

Install pytest

terminal bash
pip install pytest
That's it. pytest is a third-party library but it's the de facto standard for Python testing. If you're in a virtual environment (you should be), install it there.

Verify it installed correctly:

terminal bash
pytest --version
You should see something like pytest 8.x.x. If you get "command not found", your virtual environment may not be active.

pytest vs unittest

Why pytest Wins

Python ships with a built-in testing module called unittest. You'll see it in older codebases. You don't need to use it. Here's why pytest is better for this course:

Featureunittestpytest
Requires a class? Yes (class TestFoo(unittest.TestCase)) No — plain functions work
Assertions self.assertEqual(a, b) assert a == b
Test discovery Verbose setup Auto-discovers any test_*.py file
Fixtures Complicated Clean, composable with @pytest.fixture
Output Minimal Rich, color-coded, shows exact failure details
comparison.py python
# unittest — requires a class, special assertions
import unittest

class TestMath(unittest.TestCase):
    def test_addition(self):
        self.assertEqual(1 + 1, 2)

# pytest — just a function
def test_addition():
    assert 1 + 1 == 2
The pytest version is 4 lines shorter and easier to read. The logic is the same. Always use pytest going forward.

assert Is Your Main Tool

How assert Works

assert is a Python keyword. It evaluates an expression, and if the expression is False, it raises an AssertionError and the test fails. If it's True, nothing happens — the test continues (and eventually passes).

You can add a message to describe what went wrong:

assert_examples.py python
# Basic equality
assert result == "Kling"

# With a helpful message (shown on failure)
assert result == "Kling", f"Expected 'Kling', got '{result}'"

# Membership test
assert "aerial" in shots

# Length check
assert len(shots) == 3

# Truthiness
assert results  # passes if list is non-empty

# Not equal
assert platform != "Pika"
pytest intercepts AssertionError and shows you exactly what value you got vs what you expected — even without a message string. This is one of pytest's best features over raw Python asserts.
One Assert Per Concept (Guideline)

You can have multiple asserts in one test, but each test should check one logical thing. If a test has 10 asserts, consider splitting it. When it fails, you want to know immediately what broke — not wade through 10 checks to find the one that failed.

How pytest Discovers Tests

Run pytest from your project root and it automatically finds all test files. The rules:

RuleExample
Files named test_*.py or *_test.py test_models.py, api_test.py
Functions starting with test_ def test_platform_valid():
Classes starting with Test (optional) class TestPrompt:
Naming Convention Matters

If your file is called my_checks.py and your function is called check_platform(), pytest will not find it. The test_ prefix is the convention. Follow it exactly.

Reading pytest Output

terminal — passing bash
$ pytest test_basics.py -v

test_basics.py::test_addition PASSED            [ 25%]
test_basics.py::test_string_upper PASSED        [ 50%]
test_basics.py::test_list_append PASSED         [ 75%]
test_basics.py::test_dictionary_access PASSED   [100%]

=================== 4 passed in 0.02s ===================
The -v flag (verbose) shows each test name. Without it you just see dots. Use -v when learning — it's much clearer.
terminal — failing bash
$ pytest test_basics.py -v

test_basics.py::test_addition FAILED            [ 25%]

========================= FAILURES =========================
____________________ test_addition ____________________

    def test_addition():
>       assert 1 + 1 == 3

E       assert 2 == 3

test_basics.py:2: AssertionError
=================== 1 failed, 3 passed in 0.05s ===================
pytest shows the exact line that failed, what value you got (2), and what you expected (3). This is why assert messages are optional — pytest's introspection is already detailed.

End of Day Checklist

Tomorrow

Day 67 is hands-on. You'll write your first real test file — test_basics.py — with tests for strings, lists, and dictionaries. Then you'll see both green (passing) and red (failing) output. You'll also learn pytest.raises() for testing errors.