Skip to content

The Test Interface

The single most important pattern in a pytest-f3ts plan is the test interface: one class that owns all access to your hardware and the device under test (DUT), exposed to tests as a session-scoped fixture.

Why a dedicated interface class

  • Open once, not per test. Connecting to instruments and the DUT is slow. A session-scoped fixture opens the connection once for the whole run and closes it at the end.
  • Tests stay readable. Tests call interface.measure_rail("5v0"), not raw serial/VISA calls. Swapping hardware or mocking for offline runs only touches one file.

The interface class

Put hardware access in interface.py:

python
import logging

logger = logging.getLogger(__name__)


class TestInterface:
    """Owns all hardware access for the test plan."""

    def __init__(self):
        # Open serial ports, VISA/SCPI instruments, GPIO expanders, etc.
        # Anything that raises here will fail fixture setup loudly — which is
        # what you want if the bench isn't ready.
        self._open = True

    def measure_rail(self, name: str) -> float:
        """Return a measured rail voltage in volts."""
        ...

    def close(self):
        """Close ports / power down the DUT."""
        self._open = False

Exposing it as a fixture

In conftest.py, wrap the class in a session-scoped fixture and register teardown with addfinalizer:

python
import pytest

from interface import TestInterface


@pytest.fixture(scope="session")
def interface(request):
    dut = TestInterface()
    request.addfinalizer(dut.close)  # runs even if a test fails
    return dut

Tests then just ask for interface:

python
def test_5v0_rail(interface, test_config, record_property):
    from pytest_f3ts.utils import log_vars
    meas = interface.measure_rail("5v0")
    log_vars(record_property)
    assert test_config.min_limit <= meas <= test_config.max_limit

Running offline without hardware

For development, give the interface a mock implementation that returns deterministic values, so the whole plan runs with nothing connected. The Starter Test Plan does exactly this — its interface.py returns fixed nominal readings, so pytest -p f3ts passes on any machine.

Multiple interfaces

Large plans often have several: a power/measurement interface, an SSH or serial console to the DUT, an instrument driver. Each can be its own class and its own fixture. Keep each one focused on a single transport or instrument.