Python - Testing

13.
Explain the concept of test fixtures in pytest.

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.


14.
How do you run specific test functions in pytest?

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.


15.
What is the purpose of the mock library in Python testing?

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.


16.
Discuss the differences between black-box testing and white-box testing.

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.