Tests that verify that a program works correctly under correct conditions are called positive tests. An exception raised during the positive testing lead to failure of the test.
Tests which check behaviour for invalid inputs are called negative tests. The purpose of the negative testing is verification of the graceful handling of error states. Raising of an exception is often the expected behaviour of the tested code.
For example, the computer_move
function should raise an error
(e.g., ValueError
) when the board is full.
It is much better to raise an exception than doing nothing and silently letting the program get stuck elsewhere. You can use such function in a more complex program and be sure that it will raise an understandable error when it is called under bad conditions. The error helps you to fix the actual problem. The sooner you discover an error the easier is to fix it.
Use the with
statement and the raises
function
to test that your code raises the expected exception.
The raises
function is imported from the pytest
module.
We have not talked about the with
statement and context managers yet.
But don't worry, you will learn about them later. Just check how it is used
to test whether an exception is raised.
import pytest
import tic_tac_toe
def test_move_failure():
with pytest.raises(ValueError):
tic_tac_toe.computer_move('oxoxoxoxoxoxoxoxoxox')
Let's now try to edit the function for getting a perimeter of rectangle so that it raises a ValueError if any of the sides is smaller or equal to zero. Add a test for the new functionality.
Fixtures
in pytest are reusable components that set up specific states (e.g. database connections, test data).
You can easily create and use a fixture in pytest
in a following way:
import pytest
@pytest.fixture
def sample_data():
return [1, 2, 3, 4]
def test_sum(sample_data):
assert sum(sample_data) == 10
By default fixture
is called once per a test function - scope=function
parameter of a fixture.
For resource utilization purposes it could be useful to create a fixture only once per whole module
:
import pytest
# A fixture to provide a configuration dictionary
@pytest.fixture(scope="module")
def config_data():
config = {
"api_endpoint": "http://api.example.com",
"retry_attempts": 5,
"timeout": 10
}
return config
# Example test using the fixture
def test_api_endpoint(config_data):
assert config_data["api_endpoint"] == "http://api.example.com"
# Another test using the same fixture
def test_retry_attempts(config_data):
assert config_data["retry_attempts"] == 5
This approach is useful when the setup does not involve external resources that require explicit cleanup after the tests.