Testing is a crucial aspect of software development, as it verifies that the code meets specific requirements and behaves as expected. Often, developers struggle with writing efficient, effective tests that cover various scenarios. In this article, you will explore how to overcome these challenges using Pytest, a popular testing framework in Python.
By diving deep into testing specific code with Pytest, this guide aims to help developers master different aspects of testing, including how to test individual components, use markers to group tests, and even skip particular tests when necessary.
Whether you're a beginner or an experienced developer, this article offers a comprehensive look into testing with Pytest to enhance your code quality.
- Understand the importance of testing in software development.
- Introduction to Pytest and its features.
- Learn how to run a single test using different options in Pytest.
To get started with Pytest, follow these steps:
Installing Pytest: Use pip to install pytest:
pip install pytest
Dependencies: Make sure you have the required version of Python installed.
Naming Conventions: Pytest follows certain naming conventions for test files and functions. Typically, test files should start with test_ or end with _test, and test functions/methods should start with test.
To better understand running individual tests, we'll set up a simple project structure. Here is a class named Library. This class represents a basic library system, managing books and authors. You will test tghe functions of this class. Check the complete Github code from here
To run a single test in pytest, you would need to do the following steps.
Testing a class in Python involves writing separate test functions to check different methods of the class. In this scenario, the Library class is being tested.
Here is a complete code of Library class
from typing import List, Dict, Set
class Library:
def __init__(self):
self.books: Dict[str, str] = {} # Dictionary to store books with their authors
self.authors: Set[str] = set() # Set to store unique authors
def add_book(self, title: str, author: str) -> None:
"""Add a book with its title and author."""
self.books[title] = author
self.add_author(author)
def find_book(self, search_term: str) -> List[str]:
"""Find books containing a search term in their titles."""
return [book for book, _ in self.books.items() if search_term in book]
def list_books(self) -> Dict[str, str]:
"""List all books with their authors."""
return self.books
The tests are stored in a separate files, typically in a directory named tests. Pytest will automatically discover these tests due to the test_ prefix in the filename and function names.
This test function checks the add_book method of the Library class:
from library import Library
def test_add_book():
library = Library()
library.add_book("Python 101", "John Doe")
assert library.has_book("Python 101") == True
Here's what the code does:
- Imports the Library class.
- Defines a test function test_add_book.
- Inside the test, an instance of Library is created.
- The add_book method is called to add a book.
- The assert statement checks that the book has been added by calling the has_book method.
Similar to the previous test, this function checks the remove_book method:
def test_remove_book():
library = Library()
library.add_book("Python 101", "John Doe")
library.remove_book("Python 101")
assert library.has_book("Python 101") == False
The flow is similar, but this time it checks that a book can be removed correctly.
After writing the tests, you can run them using pytest. Here's how:
To run all tests in a specific file (module), use the following command:
pytest tests/unit/test_library.py
To run all tests within a directory, use:
pytest tests
This will find and run all tests in the tests directory.
To run a specific test function, you can use the test's "node ID", which is essentially its path:
pytest tests/unit/test_books.py::test_add_book
This command runs only the test_add_book function in the specified file.
These commands control what tests to run, allowing you to focus on specific areas of your code, which can be particularly useful during development and debugging.
In Pytest, you can assign markers to your test functions using the @pytest.mark decorator. Markers are used to categorize tests and allow you to run specific groups of tests based on those markers. This is especially useful when you have different types of tests, such as fast and slow tests, and you want to run them selectively.
To run tests based on marker expressions, you use the -m flag followed by the marker name.
Let's say we have some math-related tests and some tests related to string transformations. We can create markers for these groups.
import pytest
@pytest.mark.math
def test_addition():
assert 1 + 1 == 2
@pytest.mark.math
def test_subtraction():
assert 5 - 3 == 2
@pytest.mark.string
def test_lowercase_string():
assert "Hello".lower() == "hello"
@pytest.mark.string
def test_uppercase_string():
assert "world".upper() == "WORLD"
The provided code uses the pytest framework to create and categorize tests. It marks two test functions (test_addition
and test_subtraction
) as related to math operations and two others (test_lowercase_string
and test_uppercase_string
) as string-related. Each test checks a specific operation's outcome (e.g., addition, subtraction, string transformations) using assert statements. Marking helps organize and group tests, making it easy to run specific sets of tests based on their purpose.
Once you've added these labels, you can run tests based on their labels.
If you want to run only the math tests, you can use:
pytest -m math
This code demonstrates a test for adding a book to a library using Pytest. The test function is marked as 'slow' to indicate it's a slow-running test. It creates a Library instance, adds a book, and checks if the book is added correctly. If the assertion passes, the test is successful; otherwise, it fails.
@pytest.mark.slow # Marking this test as 'slow'pytest -m slow
def test_add_book():
"""Test adding a book."""
library = Library()
library.add_book("Python for Beginners", "John Doe")
assert library.has_book("Python for Beginners") == True
In your example, you mentioned running tests marked as slow, so you would execute the following command:
pytest -m slow
There may be situations where you want to skip a particular test. Maybe the test is for a feature that's not ready yet, or you're dealing with a known bug that you don't want to test at the moment. In pytest, skipping a test is simple and can be done using a decorator.
Here's how you can do it:
- Import the pytest module: You'll need to have pytest imported in your test file.
- Use the pytest.mark.skip decorator: You can add this line above the test function you want to skip.
import pytest
@pytest.mark.skip
def test_list_books():
"""Test listing all books with authors."""
library = Library()
library.add_book("Python for Beginners", "John Doe")
library.add_book("Advanced Python", "Jane Doe")
assert library.list_books() == {"Python for Beginners": "John Doe", "Advanced Python": "Jane Doe"}
When pytest runs, it will see the @pytest.mark.skip line and know that it should skip this test.
You can also add a reason for skipping the test like this:
import pytest
@pytest.mark.skip (reason="Skipping this test for now because of XYZ reason.")
def test_list_books():
"""Test listing all books with authors."""
library = Library()
library.add_book("Python for Beginners", "John Doe")
library.add_book("Advanced Python", "Jane Doe")
assert library.list_books() == {"Python for Beginners": "John Doe", "Advanced Python": "Jane Doe"}
When you run the tests, pytest will show that this test has been skipped and include the reason why.
You can find more information on skipping tests here.
To sum up, this article has provided you with a comprehensive understanding of the Pytest framework and its powerful features.
You've gained insight into setting up the testing environment, creating and running tests, and utilizing markers to categorize and selectively execute tests.
As you continue to explore and utilize Pytest, you'll be equipped to write better code, improve the quality of your software, and become a more confident developer.
Remember that testing isn't just a process – it's a mindset that empowers you to deliver higher quality solutions to your users.
If you have questions, ideas for improvement, or specific topics you'd like to learn more about, don't hesitate to reach out via various channels like Twitter, GitHub, or Email. Keep building and testing with Pytest – the sky's the limit!