Learning how to test Python code starts with a simple idea: write a separate program that calls your functions with known inputs and checks whether the output matches what you expect. Instead of running your script by hand and reading the terminal to decide if it worked, you write assertions that do the checking for you. Those assertions run every time you change your code and tell you immediately whether something broke.
A test is a function that calls another function and compares the result against an expected value. When the comparison fails, the framework prints the actual and expected values side by side. When it passes, you get a short confirmation and move on.
Run dozens of tests in seconds after every change. That speed catches mistakes before they reach a teammate.
Error handling with try and except blocks keeps your program running when problems occur. Testing prevents those problems from reaching users.
What a test looks like
The simplest possible test is a few lines of Python that call a function and check the return value. You can write this kind of check without any framework at all, just using Python's built-in assert statement.
def add(a, b):
return a + b
result = add(2, 3)
assert result == 5, f"Expected 5, got {result}"When the assertion is true, nothing visible happens and your script continues. When it is false, Python raises an AssertionError with your message and stops.
This raw assert pattern works for quick checks, but it has two problems. First, the script stops at the first failure, so you only learn about one bug at a time. Second, there is no summary at the end.
A proper test framework solves both problems. It runs every test independently, collects all results, and prints a report. Python's standard library includes the unittest module for this purpose. The third-party pytest framework is widely used for its simpler syntax.
The four parts of a test
Every automated test, regardless of the framework, has four parts. Understanding these parts helps you design tests that are clear, focused, and easy to debug when they fail.
- Arrange: set up the data, objects, and conditions the test needs, such as creating a list or initializing a class instance.
- Act: call the function or method you want to test.
- Assert: check that the result matches your expectation.
- Cleanup: restore any state the test changed, such as closing a file or removing a temporary record.
Here is that pattern written as a test function:
def test_average_of_three_numbers():
# Arrange
numbers = [4, 8, 12]
expected = 8
# Act
result = sum(numbers) / len(numbers)
# Assert
assert result == expectedThis is sometimes called Arrange-Act-Assert. If a test fails, you can look at the arrange section to see the inputs, the act section to see what was called, and the assert section to see what went wrong.
Python's built-in testing tools
Python ships with the unittest module, which gives you a test runner, assertion methods, and a way to group related tests into classes. You do not need to install anything extra to use it.
A test case in unittest is a class that inherits from unittest.TestCase. Each method whose name starts with test_ is a separate test that the framework discovers and runs automatically.
import unittest
def categorize_age(age):
if age < 13:
return "child"
elif age < 20:
return "teen"
return "adult"
class TestCategorizeAge(unittest.TestCase):
def test_child(self):
self.assertEqual(categorize_age(8), "child")The TestCase base class provides many assertion methods. The assertEqual method checks that two values match. Other methods like assertTrue and assertRaises handle boolean conditions and exception checking. The article on Python unittest explained covers the full set.
When you run this file, unittest discovers the test methods, runs each one, and prints a report. A passing run shows dots followed by OK. A failing run prints the exact assertion that failed and the mismatched values.
Why pytest is popular
The pytest framework offers a lighter alternative to unittest. Instead of subclassing TestCase and calling assertion methods, you write plain functions using Python's built-in assert statement. pytest discovers test functions automatically by looking for files and functions that start with test_.
def add(a, b):
return a + b
def test_add_positive_numbers():
assert add(2, 3) == 5
def test_add_negative_numbers():
assert add(-1, -1) == -2
def test_add_zero():
assert add(5, 0) == 5Run these tests with the pytest command in your terminal. pytest finds the file, runs all three functions, and reports the results. When an assertion fails, pytest shows the exact values involved in the comparison, which makes debugging faster than reading a generic error message.
pytest also includes a fixture system that replaces the setUp and tearDown methods from unittest with a more flexible pattern. Fixtures are functions that provide pre-built data or objects, and pytest handles the cleanup automatically. The article on writing better Python tests with pytest walks through fixtures and other pytest features step by step.
Tests as documentation
A well-written test suite does more than catch bugs. It serves as executable documentation for your code. When another developer, or your future self, wants to understand what a function is supposed to do, the tests show the expected inputs and outputs with concrete examples. Unlike comments, tests cannot go out of date without failing.
Consider a function that checks email format and the test that documents its expected behavior:
def is_valid_email(address):
return "@" in address and "." in address.split("@")[-1]
def test_is_valid_email():
assert is_valid_email("user@example.com") is True
assert is_valid_email("user@example") is False
assert is_valid_email("userexample.com") is False
assert is_valid_email("") is FalseThe test immediately tells you what the function considers valid and invalid. If someone later changes the validation logic, they will see exactly which cases the existing code expects to pass.
Where tests live in a project
There is no single rule for where to put test files, but the Python community has settled on two common patterns. The first pattern places test files next to the code they test, so calculator.py lives alongside test_calculator.py in the same directory. The second and more common pattern creates a separate tests directory at the top level of the project.
my_project/
calculator.py
utils.py
tests/
test_calculator.py
test_utils.pyThe separate tests directory keeps production code clean and makes it easy to exclude tests when packaging or deploying. Both patterns work, and the article on organizing and running Python tests covers the tradeoffs for each layout.
Testing is a habit, not a phase
The most important idea in testing is that you do it continuously, not at the end. After writing a function that works, write one test that locks down the happy path. When a bug report arrives, write a test that reproduces the bug before fixing it. When you refactor, run the existing tests to confirm you did not break anything.
Start small. Pick one function in your current project, write a single test for it, and run that test. The rest of this section builds on that single habit.
Rune AI
Key Insights
- Automated testing means writing code that checks your code, using assertions to compare actual output against expected output.
- Python includes the unittest module in its standard library; pytest is a popular third-party alternative that uses plain assert statements.
- A test case is a single scenario with a known input and an expected result; a test suite groups related test cases together.
- Test fixtures handle setup and cleanup so every test starts from a known state.
- Running tests after every change catches regressions early, when they are cheapest to fix.
Frequently Asked Questions
What is the difference between manual testing and automated testing in Python?
Do I need to install anything extra to write tests in Python?
Should I write tests before or after writing my code?
Conclusion
Testing is not a separate activity from programming. It is the part of programming that tells you whether your code actually works. Start with one assertion in one test file, run it, and build the habit of writing a test every time you fix a bug or add a feature.
More in this topic
Write Your First Python Unit Test
Write and run your first Python unit test step by step, using unittest to check a small function and understand the test output.
The Factory Pattern in Python
Learn how to use the factory pattern in Python to encapsulate object creation, making your code more flexible and easier to extend.
Organize and Run Python Tests
Learn how to structure test directories, run tests with unittest and pytest, configure test discovery, and integrate tests into your workflow.