pytest

repository·main·Indexed 12 days ago

https://github.com/pytest-dev/pytest

A powerful Python testing framework that scales from simple unit tests to complex functional testing for applications and libraries.

Tokens
102.8K
Snippets
386
Records
490
Agent score
91%

What's inside pytest

  1. Key features of pytest

    main

    pytest is a scalable testing framework with the following core capabilities:

    • Detailed Assertion Introspection: Uses plain assert statements and provides rich information on why an assertion failed.
    • Auto-discovery: Automatically finds test modules and functions.
    • Modular Fixtures: Provides a system for managing small or parametrized long-lived test resources.
    • unittest Compatibility: Can run existing unittest (or trial) test suites out of the box.
    • Plugin Architecture: Supports over 1300+ external plugins to extend functionality.

    Requirements:

    • Python 3.10+ or PyPy3
  2. Core features of pytest

    main

    pytest is a testing framework designed for both small, readable tests and complex functional testing. Key features include:

    • Detailed assertion introspection: Uses plain assert statements and provides rich information on failures.
    • Auto-discovery: Automatically finds test modules and functions.
    • Modular fixtures: Manages small or parametrized long-lived test resources.
    • unittest compatibility: Can run unittest test suites out of the box.
    • Plugin architecture: Supports a rich ecosystem with over 1300+ external plugins.
    • Supported Runtimes: Python 3.10+ or PyPy 3.
  3. Use indirect parametrization to pass values to fixtures

    main

    Indirect parametrization allows you to pass values from @pytest.mark.parametrize into a fixture instead of directly into the test function. This is useful for performing expensive setup at runtime within the fixture. To use it, set indirect=True (or indirect=['arg_name'] for specific arguments) in the decorator. Inside the fixture, access the passed value via request.param using the built-in request fixture.

    import pytest
    
    @pytest.fixture
    def fixt(request):
        # request.param receives the value from the parametrize decorator
        return request.param * 3
    
    @pytest.mark.parametrize("fixt", ["a", "b"], indirect=True)
    def test_indirect(fixt):
        assert len(fixt) == 3
  4. The four steps of a test (Arrange, Act, Assert, Cleanup)

    main

    A well-structured test follows a four-step pattern to ensure clarity and prevent side effects between tests. This pattern is often referred to as the AAA pattern (Arrange, Act, Assert) with an additional Cleanup phase:

    1. Arrange: Prepare the environment and state. This includes initializing objects, starting services, setting up database records, or generating credentials. This step provides the context for the test.
    2. Act: Perform the singular, state-changing action that triggers the behavior you want to test. This is typically a single function or method call.
    3. Assert: Examine the resulting state to verify if the behavior matches expectations. This is where you use assert statements to gather evidence (e.g., assert result == expected_value).
    4. Cleanup: Undo any changes made during the test (e.g., deleting database records or stopping services) to ensure subsequent tests are not accidentally influenced by the current test's state.
  5. How fixture dependencies affect execution order

    main
    When one fixture requests another, the requested fixture is executed first. This creates a dependency chain. If fixture a requests fixture b, b must complete its setup before a begins. You can use this mechanism to force a specific order even if a fixture does not strictly need the return value of another, simply by including it in the fixture's arguments.
  6. Access all collected tests using a session-scoped fixture

    main

    A fixture with scope="session" can access the entire collection of tests by inspecting the request.node object. This is useful for performing global setup that depends on the structure of the test suite, such as inspecting test classes for specific methods or attributes before any tests run.

    To implement this, use a session-scoped fixture with autouse=True. You can iterate over request.node.items to walk through all collected test items. For each item, you can use item.getparent(pytest.Class) to find its parent class and inspect it.

    import pytest
    
    @pytest.fixture(scope="session", autouse=True)
    def callattr_ahead_of_alltests(request):
        print("callattr_ahead_of_alltests called")
        seen = {None}
        session = request.node
        for item in session.items:
            cls = item.getparent(pytest.Class)
            if cls not in seen:
                if hasattr(cls.obj, "callme"):
                    cls.obj.callme()
                seen.add(cls)
  7. Implement fixture teardown using yield fixtures

    main

    The recommended way to perform cleanup (teardown) in pytest is by using yield instead of return in your fixture function.

    1. Code before the yield statement is executed during the setup phase.
    2. The object yielded is passed to the requesting test or fixture.
    3. Code after the yield statement is executed during the teardown phase, once the test is finished.

    Pytest executes teardown code in the reverse order of the setup. If a fixture raises an exception before the yield statement, its teardown code will not run, but pytest will still attempt to tear down any previously successful fixtures in the dependency chain.

    @pytest.fixture
    def receiving_user(mail_admin):
        user = mail_admin.create_user()
        yield user
        # Teardown code runs after the test
        user.clear_mailbox()
        mail_admin.delete_user(user)
  8. How third-party plugin fixtures are discovered

    main

    Fixtures provided by installed third-party plugins are available globally. However, pytest's search priority is as follows:

    1. Local Scopes: pytest first searches for the fixture in the current test's scope, then moves upward through parent directories and conftest.py files.
    2. Plugins: pytest searches for the fixture in installed plugins last.

    This means a locally defined fixture will always take precedence over a plugin fixture of the same name.

  9. Understand the pytest collection tree (Nodes, Collectors, and Items)

    main

    pytest organizes discovered tests into a hierarchical tree of objects called 'nodes'.

    • Node: The base class for all objects in the collection tree.
    • Collector: A node that can contain other nodes (e.g., Session, Package, Module, Class).
    • Item: A leaf node representing a specific test (e.g., Function).

    Common collector types include:

    • Session: The root of the tree.
    • Package: Represents a directory/package.
    • Module: Represents a Python module.
    • Class: Represents a class containing tests.
    • Function: Represents an individual test function.
  10. How pytest capturing precedence works

    main

    When you use a capture fixture (like capsys or capfd) inside a test function, it takes precedence over the global command-line configuration (like -s or --capture=no).

    Even if you run pytest with -s to disable global capturing, any output produced within a test that utilizes a capture fixture will still be captured and made available via readouterr().

  11. Understand changes to parametrization grouping and fixture reuse

    main

    In recent versions of pytest, tests parametrized with a scope higher than function are reordered by the actual parameter value instead of the parameter index.

    This change ensures that items using identical parameter values are grouped together to share a single fixture setup/teardown cycle, even if those items were defined across separate @pytest.mark.parametrize calls. This optimization prevents expensive higher-scoped fixtures from being repeatedly set up and torn down for the same value.

    Key behaviors to note:

    • Unhashable values: Parameter values that are unhashable (such as dict objects) retain the previous index-based grouping behavior.
    • Execution order: Because tests are now grouped by value, the order in which parametrized tests run may change.
    • Fixture counts: The total number of fixture setup/teardown cycles can only decrease (or stay the same) due to this optimization; it will not increase.
  12. Use funcargs for resource injection and parametrization

    main
    The funcargs mechanism is a core part of pytest's fixture management system used for resource injection and parametrization. It allows you to provide specific arguments to test functions dynamically. For more detailed information on modern fixture management, refer to the documentation for fixtures and parametrize.