Skip to main content

Python Testing with pytest: Write Tests That Actually Help

Intermediate25 min6 exercises90 XP
0/6 exercises

Imagine you wrote a spell-checker. It works great on the word "hello." But what about "HeLLo"? Or an empty string? Or a number? You won't know until something breaks — unless you write tests.

A test is a small piece of code that checks whether your other code works correctly. It's like a safety net for your programs. Every time you make a change, you run your tests, and they instantly tell you if you broke something.

In this tutorial, you'll learn to write tests using pytest-style conventions — the most popular testing approach in Python. We'll use plain assert statements and simple test functions that you can run right here in your browser.

Why Should You Write Tests?

You might think: "I can just run my code and see if it works." That's fine for tiny programs. But real projects have hundreds of functions. You can't manually check every single one after every change.

Tests solve three big problems:

  • Catching bugs early — find mistakes before your users do
  • Confidence to change code — refactor without fear of breaking things
  • Documentation — tests show exactly how your code should behave
  • Here's a simple example. Say you write a function that adds two numbers:

    Testing by hand — fragile and easy to forget
    Loading editor...

    That print statement tells you the answer is 5, but you have to check it with your eyes every time. What if someone changes the function later? Nobody will remember to re-run that print.

    How Do You Write Your First Test Function?

    A test function is just a regular Python function whose name starts with test_. Inside, you use assert to check that something is true. If the assertion passes, the test passes. If it fails, Python raises an error.

    Your first test functions
    Loading editor...

    Notice the pattern: each test function has a descriptive name that explains what it checks. The name test_add_positive_numbers tells you exactly what scenario is being tested.

    In a real project, you'd use the pytest framework to discover and run tests automatically. Here, we call them manually — but the logic is identical.

    How Do Assert Statements Work?

    The assert statement is the heart of every test. It says: "I believe this thing is true. If it's not, stop everything and tell me."

    Common assert patterns
    Loading editor...

    Here are the most useful types of assertions you'll write:

    Assert cheat sheet (reference only)
    # Equality
    assert result == expected
    
    # Truthiness
    assert is_valid
    assert not is_empty
    
    # Membership
    assert item in collection
    assert key in dictionary
    
    # Type checking
    assert isinstance(value, int)
    
    # Comparisons
    assert count > 0
    assert length <= max_length

    When an assertion fails, Python tells you exactly what went wrong:

    What happens when a test fails
    Loading editor...

    What Are Edge Cases and Why Do They Matter?

    An edge case is an unusual or extreme input that your code might not handle well. Think of it like stress-testing a bridge — you don't just drive a car over it, you also check what happens with a full convoy of trucks.

    Common edge cases to test:

  • Empty inputs: empty strings, empty lists, zero
  • Boundary values: the first item, the last item, exactly at a limit
  • Unexpected types: what if someone passes None?
  • Large inputs: very long strings, huge numbers
  • Negative values: negative numbers when you expect positive ones
  • Testing edge cases for a simple function
    Loading editor...

    How Should You Organize Your Tests?

    As your project grows, you'll have dozens or even hundreds of tests. Keeping them organized is essential. The standard approach is to group related tests together.

    One powerful technique is using a test runner — a function that collects and runs all your test functions, reporting which passed and which failed:

    A simple test runner
    Loading editor...
    Messy test organization
    def test1():
        assert add(1, 2) == 3
    
    def test2():
        assert add(-1, 1) == 0
    
    def another_test():
        # Won't be found by pytest!
        assert add(0, 0) == 0
    Clean test organization
    def test_add_positive_numbers():
        assert add(1, 2) == 3
    
    def test_add_mixed_signs():
        assert add(-1, 1) == 0
    
    def test_add_zeros():
        assert add(0, 0) == 0

    What Is Test-Driven Thinking?

    Here's a mindset shift that will make you a better programmer: instead of writing code first and testing later, think about your tests while you design your function.

    Before you write a single line of code, ask yourself: "How will I know this works?" The answer to that question is your test.

    Thinking about tests before writing code
    Loading editor...

    Practice Exercises

    Write Your First Test
    Write Code

    Write a function called square that returns the square of a number. Then write a function called test_square that tests it with at least 3 different assert statements (test with a positive number, zero, and a negative number). Call test_square() and print All tests passed! at the end.

    Loading editor...
    Fix the Failing Test
    Fix the Bug

    The function is_adult is supposed to return True if age is 18 or older, and False otherwise. The tests are correct, but the function has a bug. Fix the function so all tests pass, then print All tests passed!.

    Loading editor...
    Predict the Test Results
    Predict Output

    Read the code below carefully. The test runner catches assertion errors and reports results. Predict exactly what gets printed. Think about which tests will pass and which will fail.

    Loading editor...
    Test the Edge Cases
    Write Code

    The truncate function shortens a string to a maximum length and adds "..." if it was cut. Write a test_truncate function that tests at least 4 edge cases: a string shorter than the limit, a string exactly at the limit, a string longer than the limit, and an empty string. Call the test and print All tests passed!.

    Loading editor...
    Build a Mini Test Runner
    Write Code

    Write a function called run_tests that takes a list of test functions, runs each one inside a try/except block, and prints a summary at the end. Use the format shown: print PASS: <function_name> or FAIL: <function_name> for each test, then print a blank line followed by <passed> passed, <failed> failed. Use the provided test functions to demonstrate it.

    Loading editor...
    Refactor Repetitive Tests
    Refactor

    The tests below work but are repetitive. Refactor them to use a data-driven approach: store test cases as a list of tuples (input, expected) and loop through them. The function, all assertions, and the final print statement should remain. Print Testing: <input> -> <expected> for each case, then All 5 tests passed! at the end.

    Loading editor...