14. Testing Strategy (pytest)

Combine unit, integration, and property-based tests; isolate effects and assert logs/metrics where it matters.

Question: What is property-based testing and why is it useful?

Answer: Property-based testing is a technique where you define general properties or invariants that your code should satisfy, and a library (like hypothesis for Python) generates a wide range of random test cases to try and falsify those properties. It is extremely effective at finding subtle edge cases that developers might miss when writing example-based tests.

Question: How do pytest fixtures improve testing?

Answer: Fixtures provide a modular and reusable way to manage the setup and teardown of test dependencies, such as database connections, temporary files, or complex objects. They are dependency-injected into test functions, making tests cleaner, more readable, and easier to maintain compared to older xUnit-style setUp/tearDown methods. Fixtures can also have different scopes (function, class, module, session), which allows for efficient resource management.

import pytest
import json

# `tmp_path` is a built-in pytest fixture that provides a temporary directory
@pytest.fixture
def config_file(tmp_path):
    config_path = tmp_path / "config.json"
    config_data = {"host": "localhost", "port": 5432}
    config_path.write_text(json.dumps(config_data))
    return config_path

def test_read_config(config_file):
    # The `config_file` fixture is injected here by pytest
    data = json.loads(config_file.read_text())
    assert data["host"] == "localhost"
    assert data["port"] == 5432

Question: How do you parametrize tests effectively?

Answer: Use @pytest.mark.parametrize to run the same test logic over multiple inputs and expected outputs.

Explanation: Improves coverage and reduces duplication.

@pytest.mark.parametrize("a,b,s", [(1,2,3),(0,0,0),(-1,1,0)])
def test_add(a,b,s):
    assert add(a,b) == s

Question: How do you test async code?

Answer: Use pytest-asyncio or anyio with an event loop fixture; avoid real sleeps and I/O.

Explanation: Use fakes/mocks and time control.

import pytest, asyncio
@pytest.mark.asyncio
async def test_async():
    assert await coro() == 42

Question: How do you isolate external dependencies in tests?

Answer: Use monkeypatch/mocker to stub environment, time, and network calls.

Explanation: Promotes deterministic, fast tests.