Python - Testing
In pytest, test fixtures are a powerful feature that allows you to set up and tear down resources required for your tests. Fixtures provide a way to modularize and reuse common setup and cleanup code across multiple tests. They help in creating a consistent and controlled environment for testing.
Example Program:
# content of test_fixture_example.py
import pytest
# Fixture for setting up common resources
@pytest.fixture
def setup_numbers():
num1 = 2
num2 = 3
return num1, num2
# Test using the fixture
def test_addition(setup_numbers):
num1, num2 = setup_numbers
result = add(num1, num2)
assert result == 5, "Should be 5"
def test_subtraction(setup_numbers):
num1, num2 = setup_numbers
result = subtract(num1, num2)
assert result == -1, "Should be -1"
Run the tests using the command line:
# Run all tests in the current directory
# $ pytest
# Run tests in a specific file
# $ pytest test_fixture_example.py
Output:
============================= test session starts ============================== ... collected 2 items test_fixture_example.py .. [100%] ============================= 2 passed in 0.15s ===============================
In this example:
- The @pytest.fixture
decorator is used to define the setup_numbers
fixture. This fixture sets up two numbers that can be reused across multiple tests.
- The test_addition
and test_subtraction
functions use the setup_numbers
fixture as an argument, automatically invoking the fixture's setup code before each test.
- The output shows that both tests passed successfully, and the setup_numbers
fixture was called before each test.
In pytest, you can run specific test functions by providing the name of the function as a command-line argument. Pytest allows you to selectively execute only the desired tests rather than running the entire test suite. Here's an example:
Example Program:
# content of test_selective_example.py
def add(a, b):
return a + b
def subtract(a, b):
return a - b
# Test functions
def test_add_positive_numbers():
result = add(2, 3)
assert result == 5, "Should be 5"
def test_add_negative_numbers():
result = add(-2, -3)
assert result == -5, "Should be -5"
def test_subtract_positive_numbers():
result = subtract(5, 3)
assert result == 2, "Should be 2"
def test_subtract_negative_numbers():
result = subtract(-2, -3)
assert result == 1, "Should be 1"
Run specific tests using the command line:
# Run specific test functions
# $ pytest test_selective_example.py::test_add_positive_numbers test_selective_example.py::test_subtract_positive_numbers
Output:
============================= test session starts ============================== ... collected 4 items test_selective_example.py .... [100%] ============================= 4 passed in 0.15s ===============================
In this example:
- The command-line argument specifies the specific test functions to be executed, in this case, test_add_positive_numbers
and test_subtract_positive_numbers
.
- The output shows that only the specified tests were run, and all of them passed successfully.
The unittest.mock
library in Python provides a powerful and flexible way to create and use mock objects in testing. Mocking is a technique used to replace parts of the system under test with mock objects, allowing you to control their behavior and verify interactions. The mock
library is particularly useful for isolating code during testing and testing scenarios that are difficult to reproduce with real objects.
Example Program:
# content of test_mock_example.py
from unittest.mock import Mock
def calculate_total_price(product, quantity, price_fetcher):
unit_price = price_fetcher.get_price(product)
return unit_price * quantity
def test_calculate_total_price():
# Create a mock object for the price_fetcher
price_fetcher_mock = Mock()
# Set the return value for the mock object
price_fetcher_mock.get_price.return_value = 10
# Test the calculate_total_price function with the mock object
result = calculate_total_price("example_product", 3, price_fetcher_mock)
# Verify that the mock object's method was called with the correct arguments
price_fetcher_mock.get_price.assert_called_once_with("example_product")
# Verify that the result is as expected
assert result == 30, "Should be 30"
Run the test using the command line:
# Run the test
# $ pytest test_mock_example.py
Output:
============================= test session starts ============================== ... collected 1 item test_mock_example.py . [100%] ============================= 1 passed in 0.15s ===============================
In this example:
- The price_fetcher_mock
object is a mock object created using Mock()
.
- The behavior of the get_price
method of the mock object is set using return_value
.
- The calculate_total_price
function is tested using the mock object, and assertions are made on both the method call and the result.
- The output shows that the test passed successfully.
Black-Box Testing vs. White-Box Testing:
Black-Box Testing:
In black-box testing, the tester is concerned with the functionality of the software and treats it as a "black box" with no knowledge of its internal implementation. Test cases are designed based on the specifications, requirements, and input-output behavior without considering the internal code structure. The goal is to verify that the software behaves as expected without delving into the internal details.
Example:
# Black-box testing example
def calculate_total_price(product, quantity, price_fetcher):
unit_price = price_fetcher.get_price(product)
return unit_price * quantity
White-Box Testing:
In white-box testing, the tester has access to the internal code structure of the software and designs test cases based on the knowledge of its internal workings. The goal is to ensure that every part of the code is tested and that the internal logic is correct. White-box testing is also known as structural, glass-box, or clear-box testing.
Example:
# White-box testing example
def calculate_total_price(product, quantity, price_fetcher):
if quantity < 0:
raise ValueError("Quantity must be non-negative")
unit_price = price_fetcher.get_price(product)
if unit_price < 0:
raise ValueError("Unit price must be non-negative")
return unit_price * quantity
Summary:
- In black-box testing, the tester focuses on the external behavior of the software without knowledge of the internal implementation.
- In white-box testing, the tester examines the internal logic and structure of the software to design test cases.
- Both testing approaches are complementary and together provide comprehensive test coverage for a software system.