pytest is one of the most popular testing frameworks in Python.
It makes writing and running tests simple and efficient.
See the official documentation for more details:
https://docs.pytest.org/en/stable/index.html
Installing pytest:
To install
pytest for the current user:
$ python3 -m pip install --user pytest
Alternatively, to install it globally on Ubuntu:
$ sudo apt install python3-pytest
To verify the installation:
$ pytest --version
Output:
pytest 7.4.4
pytest.ini:
The pytest.ini file is used to configure pytest behavior.
It takes precedence, even when empty, over other config files.
You may also use a hidden version named .pytest.ini.
Create a pytest.ini file in your project's root directory:
$ vi pytest.ini
[pytest]
minversion = 6.0
addopts = -ra -q
testpaths =
tests
integration
Assertions:
Assertions are used to validate conditions in unit tests.
-
Basic assertions:
assert output # Assert that 'output' is truthy (i.e., evaluates to True)
assert not output # Assert that 'output' is falsy (i.e., evaluates to False)
assert output == value # Assert that 'output' is equal to 'value' (assert equality)
assert output != value # Assert that 'output' is not equal to 'value' (assert inequality)
assert output is None # Assert that 'output' is None
assert output is not None # Assert that 'output' is not None
-
Collection assertions:
assert item in collection # Assert that 'item' is in the 'collection' (assert membership)
assert item not in collection # Assert that 'item' is not in the 'collection' (assert non membership)
assert len(collection) == value # Assert that the length of the 'collection' is equal to 'value' (assert collection length)
assert all(collection) # Assert that all items in the 'collection' are truthy
assert any(collection) # Assert that at least one item in the 'collection' is truthy
-
Numeric assertions:
assert output > value # Assert that 'output' is greater than 'value'
assert output >= value # Assert that 'output' is greater than or equal to 'value'
assert output < value # Assert that 'output' is less than 'value'
assert output <= value # Assert that 'output' is less than or equal to 'value'
assert abs(output - value) < 0.001 # Assert that 'output' is approximately equal to 'value' (floating-point comparison)
-
Type assertions:
assert isinstance(output, specific_type) # Assert that 'output' is of type 'specific_type' (i.e., str)
assert hasattr(obj, 'method_name') # Assert that 'obj' has the attribute 'method_name'
-
Exception assertions:
with pytest.raises(ValueError):
function_that_should_raise_value_error()
with pytest.raises(ValueError, match="invalid input"):
function_with_specific_error_message()
Simple unit test:
-
Create a file hello.py that contains a simple function 'hello':
$ vi hello.py
def hello(name):
if not isinstance(name, str):
raise TypeError("Arguments must be strings")
elif name == '':
raise ValueError("Arguments cannot be empty")
else:
return name
-
Create a unit test file test_hello.py:
$ vi test_hello.py
import pytest
from hello import hello
def test_hello():
"""hello unit test"""
with pytest.raises(TypeError, match="Arguments must be strings"):
hello(123)
with pytest.raises(ValueError):
hello('')
output = hello('abc')
assert output == 'abc'
-
To execute the unit test, open a terminal and run 'pytest' command from the project folder:
$ pytest
Output:
================================================================================ test session starts ================================================================================
platform linux -- Python 3.12.3, pytest-7.4.4, pluggy-1.4.0
rootdir: ~/dev/python/my_project
collected 1 item
test_hello.py . [100%]
================================================================================= 1 passed in 0.01s =================================================================================
-
If the test encounters a failed assertion or an unexpected exception, pytest will report the test as failed or errored.
Here's an example of a unit test that will fail:
import pytest
from hello import hello
def test_hello():
"""hello unit test"""
with pytest.raises(ValueError, match="Arguments must be strings"):
hello(123)
with pytest.raises(TypeError, match="Arguments should be strings"):
hello(123)
with pytest.raises(TypeError):
hello('')
output = hello('abc')
assert output == 'xyz'
Running pytest on this test will produce the following errors:
> raise TypeError("Arguments must be strings")
E TypeError: Arguments must be strings
> with pytest.raises(TypeError, match="Arguments should be strings"):
E AssertionError: Regex pattern did not match.
E Regex: 'Arguments should be strings'
E Input: 'Arguments must be strings'
> raise ValueError("Arguments cannot be empty")
E ValueError: Arguments cannot be empty
> assert output == 'xyz'
E AssertionError: assert 'abc' == 'xyz'
E - xyz
E + abc
Using Fixtures:
Fixtures (
@pytest.fixture) in pytest provide a flexible way to
set up test data,
create and inject mock objects into unit tests,
and establish reusable test environments across multiple test functions.
-
Create a file addition.py that contains a simple class 'Addition':
$ vi addition.py
class Addition:
def __init__(self, i, j):
self.i = i
self.j = j
def add(self):
return self.i + self.j
-
Create a unit test file test_addition.py:
$ vi test_addition.py
import pytest
from addition import Addition
@pytest.fixture
def addition():
addition = Addition(4, 6)
return addition
def test_add(addition):
"""add unit test"""
output = addition.add()
assert output == 10
-
To execute the unit test, open a terminal and run 'pytest' command from the project folder:
$ pytest
================================================================================ test session starts ================================================================================
platform linux -- Python 3.12.3, pytest-7.4.4, pluggy-1.4.0
rootdir: ~/dev/python/my_project
collected 1 item
test_addition.py . [100%]
================================================================================= 1 passed in 0.01s =================================================================================
Fixture Scopes:
Fixtures can have different scopes to optimize test execution.
-
Fixtures recreated for each test (default):
@pytest.fixture(scope="function")
def addition():
addition = Addition(4, 6)
return addition
-
Fixtures shared across all tests in a class:
@pytest.fixture(scope="class")
def addition():
addition = Addition(4, 6)
return addition
-
Fixtures shared across all tests in a module:
@pytest.fixture(scope="module")
def addition():
addition = Addition(4, 6)
return addition
-
Fixtures shared across entire test session:
@pytest.fixture(scope="session")
def addition():
addition = Addition(4, 6)
return addition