Testing Your Code with Pytest
Transformative Tech Leader | Serial Entrepreneur & Machine Learning Engineer Leveraging 3+ years of expertise in Machine Learning and a background in Web Development, I drive innovation through building, mentoring, and educating. Passionate about harnessing AI to solve real-world problems."
When you write a function or a class, you can also write tests to prove that your code works as expected. Testing ensures that your code responds correctly to different inputs, giving you confidence that it will perform as more people begin to use your programs. It also helps you test new features without breaking existing functionality. In this tutorial, you'll learn how to test Python code using pytest, a popular testing framework.
Why Testing Matters
Testing allows you to:
Ensure your code behaves as expected with various inputs.
Catch bugs early before users encounter them.
Confidently refactor and extend your code without introducing new errors.
What You'll Learn:
Installing pytest and using pip.
Writing and running your first test.
Handling failing tests.
Testing functions and classes.
Step 1: Installing pytest Using pip
Before testing, we need to install pytest. Python has a package manager called pip that allows you to install third-party packages like pytest.
Updating pip
First, make sure your pip version is up to date. Open your terminal and run:
$ python -m pip install --upgrade pip
Installing pytest
To install pytest, use the following command:
$ python -m pip install --user pytest
Once pytest is installed, you can use it to write and run tests.
Step 2: Writing Your First Test
Let’s start by writing a simple function and creating a test for it.
The Function to Test
We'll create a function get_formatted_name() that takes a first and last name and returns them neatly formatted.
Create a Python file name_function.py with this code:
def get_formatted_name(first, last):
"""Generate a neatly formatted full name."""
full_name = f"{first} {last}"
return full_name.title()
Writing the Test
To test this function, we will write a test in a new file called test_name_function.py. The test will check if get_formatted_name() returns the correct output.
from name_function import get_formatted_name
def test_first_last_name():
"""Do names like 'Janis Joplin' work?"""
formatted_name = get_formatted_name('janis', 'joplin')
assert formatted_name == 'Janis Joplin'
Explanation:
test_first_last_name(): The function starts with
test_so that pytest recognizes it as a test.assert: This keyword checks if the returned result from
get_formatted_name('janis', 'joplin')is'Janis Joplin'. If the assertion is true, the test passes; if not, it fails.
Step 3: Running the Test
To run the test, navigate to the folder containing test_name_function.py in the terminal, and type:
$ python -m pytest
You should see something like this:
======================== test session starts ========================
platform darwin -- Python 3.x.x, pytest-7.x.x
rootdir: /path/to/your/files
collected 1 item
test_name_function.py . [100%]
========================= 1 passed in 0.01s ==========================
The single . indicates the test passed successfully. If pytest finds any failing tests, it will display them in the output.
Step 4: Testing for Edge Cases (Failing Tests)
Let’s modify our function to handle middle names, which will break the test temporarily. Here’s the new version of get_formatted_name():
def get_formatted_name(first, middle, last):
"""Generate a neatly formatted full name."""
full_name = f"{first} {middle} {last}"
return full_name.title()
If we run the test now, it will fail because the test was written for names without a middle name:
$ pytest
======================== test session starts ========================
collected 1 item
test_name_function.py F [100%]
================================ FAILURES ================================
_________________________ test_first_last_name __________________________
def test_first_last_name():
"""Do names like 'Janis Joplin' work?"""
> formatted_name = get_formatted_name('janis', 'joplin')
E TypeError: get_formatted_name() missing 1 required positional argument: 'last'
The test fails because our function now expects a middle name. The output shows exactly what went wrong, helping us fix the issue.
Step 5: Fixing the Failing Test
To fix this, we can make the middle name optional by giving it a default value of an empty string (''):
def get_formatted_name(first, last, middle=''):
"""Generate a neatly formatted full name."""
if middle:
full_name = f"{first} {middle} {last}"
else:
full_name = f"{first} {last}"
return full_name.title()
Now the function works for both names with and without a middle name. Rerun the test:
$ pytest
======================== test session starts ========================
collected 1 item
test_name_function.py . [100%]
========================= 1 passed in 0.01s ==========================
The test passes again!
Step 6: Testing Multiple Scenarios
You can add additional tests for cases with a middle name. Add this test to test_name_function.py:
def test_first_middle_last_name():
"""Do names like 'David Lee Roth' work?"""
formatted_name = get_formatted_name('david', 'roth', 'lee')
assert formatted_name == 'David Lee Roth'
Now you have tests for both scenarios!
Exercise
Write a function
add_numbers(a, b)that adds two numbers.Write a test to check if the function returns the correct sum for multiple inputs.
Run the test using
pytest.
With this, you’re ready to start testing your Python code with pytest!
Testing a Class in Python using pytest
In this tutorial, we’ll learn how to write tests for a class using Python's pytest framework. Testing a class is similar to testing a function, but it involves checking the behavior of the class's methods to ensure everything is working as expected.
Let's go through a practical example where we test a class that administers anonymous surveys.
Example: Class to Test
We have a class called AnonymousSurvey that helps manage survey questions and responses:
# survey.py
class AnonymousSurvey:
"""Collect anonymous answers to a survey question."""
def __init__(self, question):
"""Store a question and prepare to store responses."""
self.question = question
self.responses = []
def show_question(self):
"""Show the survey question."""
print(self.question)
def store_response(self, new_response):
"""Store a single response to the survey."""
self.responses.append(new_response)
def show_results(self):
"""Show all the responses that have been given."""
print("Survey results:")
for response in self.responses:
print(f"- {response}")
This class stores a survey question and provides methods to:
Show the question (
show_question()),Store responses (
store_response()), andDisplay results (
show_results()).
Writing the Tests
We’ll write tests to check that this class behaves correctly. Specifically, we’ll:
Verify that a single response is stored correctly.
Verify that multiple responses are stored correctly.
1. Testing Single Response
# test_survey.py
from survey import AnonymousSurvey
def test_store_single_response():
"""Test that a single response is stored properly."""
question = "What language did you first learn to speak?"
survey = AnonymousSurvey(question)
survey.store_response('English')
assert 'English' in survey.responses
This test:
Creates an instance of
AnonymousSurvey.Stores a single response ('English').
Asserts that the response is correctly stored in the
responseslist.
2. Testing Multiple Responses
# test_survey.py
from survey import AnonymousSurvey
def test_store_three_responses():
"""Test that three individual responses are stored properly."""
question = "What language did you first learn to speak?"
survey = AnonymousSurvey(question)
responses = ['English', 'Spanish', 'Mandarin']
for response in responses:
survey.store_response(response)
for response in responses:
assert response in survey.responses
This test:
Stores three different responses.
Loops through the responses and asserts that each one is correctly stored.
Running the Tests
To run the tests, you can use the pytest command in the terminal:
$ pytest test_survey.py
If all tests pass, you will see output like:
========================= test session starts =========================
--snip--
test_survey.py .. [100%]
========================== 2 passed in 0.01s ==========================
Making the Tests Efficient with Fixtures
When you have multiple tests using the same setup (e.g., creating an AnonymousSurvey instance), it’s a good idea to use a fixture. Fixtures allow you to avoid repetitive code by providing a setup that is shared across tests.
Adding a Fixture
# test_survey.py
import pytest
from survey import AnonymousSurvey
@pytest.fixture
def language_survey():
"""A survey that will be available to all test functions."""
question = "What language did you first learn to speak?"
return AnonymousSurvey(question)
def test_store_single_response(language_survey):
"""Test that a single response is stored properly."""
language_survey.store_response('English')
assert 'English' in language_survey.responses
def test_store_three_responses(language_survey):
"""Test that three individual responses are stored properly."""
responses = ['English', 'Spanish', 'Mandarin']
for response in responses:
language_survey.store_response(response)
for response in responses:
assert response in language_survey.responses
Explanation:
Fixture: The
language_surveyfunction is decorated with@pytest.fixture. It creates and returns an instance ofAnonymousSurvey.Test Functions: The test functions now accept the
language_surveyfixture as an argument. Pytest will automatically call the fixture and pass theAnonymousSurveyinstance to the test functions.
Summary of Key Concepts
Assertions: Use
assertstatements to check the expected behavior (e.g.,assert response in survey.responses).Fixtures: They help eliminate repetitive setup code in tests by allowing you to reuse objects across multiple test functions.
Running Tests: Run your tests using
pytest, which automatically finds and runs all test files that follow thetest_*.pynaming convention.
Exercises
Exercise 1: Add a test for a scenario where no responses have been submitted. The test should check that the
responseslist remains empty.Exercise 2: Modify the
AnonymousSurveyclass to allow each user to submit multiple responses. Write a test to verify that multiple responses are stored correctly for each user.
This tutorial should give you a solid foundation for writing and running tests for Python classes. As your projects grow, testing will become an essential tool to ensure your code remains robust and bug-free.

