Have you ever fixed a bug in one part of your code, only to have something completely unrelated break? Or perhaps you've wanted to refactor a complex function but were too afraid you might silently break it. This is where automated testing comes in. It's a safety net that gives you the confidence to improve your code and ensure it works as expected.
This guide will walk you through the basics of writing, organizing, and running tests in Python using pytest, the modern standard for Python testing.
Why Write Automated Tests?
Before we get to the "how," let's cover the "why."
- Confidence: A good test suite proves that your code behaves as you expect.
- A Refactoring Safety Net: When you have tests, you can clean up, optimize, or restructure your code with confidence, knowing that your tests will immediately tell you if you've broken something.
- Living Documentation: Tests are examples of how your code is meant to be used. A well-written test can be clearer than any documentation page.
- Better Design: Thinking about how to test your code often forces you to write smaller, more modular, and more reusable functions.
Your Tool of Choice: pytest
While Python has a built-in unittest module, the community has largely embraced pytest. It's powerful, has a simpler syntax, and requires less boilerplate code.
First, install it in your project's environment:
pip install pytest
Writing Your First Test
Testing with pytest is incredibly straightforward. It's based on writing small functions that use a simple assert statement to check if something is True.
Let's say we have a simple function in a file named my_math.py:
# my_math.py
def add(x: int, y: int) -> int:
return x + y
To test this, we create a test file. pytest automatically discovers test files named test_*.py or *_test.py. Inside these files, it runs functions prefixed with test_.
# test_my_math.py
from my_math import add
def test_add_positive_numbers():
assert add(2, 3) == 5
def test_add_negative_numbers():
assert add(-1, -1) == -2
That's it! There's no need for special classes or complex structures. If the assert statement holds true, the test passes. If it's false, the test fails.
Where to Store Your Tests?
There are two common patterns for organizing your test files.
Option 1: The tests/ Directory (Recommended)
This is the most common and recommended approach for most projects. You create a separate tests directory at the top level of your project.
my_project/
├── src/
│ ├── __init__.py
│ └── my_math.py
└── tests/
└── test_my_math.py
Pros:
- Clean separation between your application code and your test code.
- Easy to exclude tests from your final package distribution.
Option 2: Alongside Your Code
You can also place test files in the same directory as the code they are testing.
my_project/
└── src/
├── __init__.py
├── my_math.py
└── test_my_math.py
Pros:
- Tests are physically close to the code they verify.
- Can be convenient for large applications with many sub-packages.
For most projects, start with a top-level tests/ directory.
How to Run Your Tests
This is the easiest part. Open your terminal, navigate to the root directory of your project (my_project/), and simply run the pytest command.
$ pytest
pytest will automatically scan your project for test files and functions, run them, and give you a report.
Passing Output:
============================= test session starts ==============================
...
collected 2 items
tests/test_my_math.py .. [100%]
============================== 2 passed in 0.01s ===============================
The .. indicates that two tests passed.
Failing Output:
If we were to change a test to assert add(2, 3) == 6, the output would be very different and incredibly helpful:
=================================== FAILURES ===================================
_________________________ test_add_positive_numbers __________________________
def test_add_positive_numbers():
> assert add(2, 3) == 6
E assert 5 == 6
E + where 5 = add(2, 3)
tests\\test_my_math.py:4: AssertionError
=========================== 1 failed, 1 passed in 0.03s ============================
pytest gives you a detailed report showing exactly which assertion failed and what the values were.
Conclusion
You now have the building blocks to start testing your Python code. Testing isn't a chore; it's an investment that pays for itself many times over in saved debugging time and increased confidence.
Start small. Pick one important function in your project and write one simple test for it. As you build your test suite, you'll wonder how you ever coded without it.
Top comments (0)