Step 2: Running Without Hardware #
Goal: Run your tests without real instruments by wrapping your driver classes with Litmus's Mock factory.
In step 1 you wrote vanilla pytest tests against psu and dmm fixtures defined in conftest.py. This step shows the smallest change that lets the same tests run on a laptop with no hardware attached.
The conftest pattern #
Your conftest.py already returns driver instances. Wrap them in litmus.instruments.mocks.Mock when the --mock-instruments flag is set:
# tests/conftest.py
import pytest
from drivers import DMM, PSU
from litmus.instruments.mocks import Mock
@pytest.fixture(scope="session")
def psu(mock_instruments) -> PSU:
if mock_instruments:
return Mock(PSU, measure_voltage=5.0, measure_current=0.042)
return PSU(resource="TCPIP::192.168.1.101::INSTR")
@pytest.fixture(scope="session")
def dmm(mock_instruments) -> DMM:
if mock_instruments:
return Mock(DMM, measure_dc_voltage=3.31)
return DMM(resource="TCPIP::192.168.1.102::INSTR")mock_instruments is a fixture Litmus provides — it returns True whenever --mock-instruments is on the command line or LITMUS_MOCK_INSTRUMENTS=1 is set.
Mock(DMM, measure_dc_voltage=3.31) returns an object that quacks like a DMM — every method call is a no-op unless you configure a return value. Pass a literal for a constant return, a dict to map argument values, or a callable for dynamic behavior.
This is exactly what examples/01-vanilla and examples/02-verify ship.
Running with mocks #
pytest tests/ --mock-instruments -vSame test code as step 1, no hardware required.
# Or via env var
LITMUS_MOCK_INSTRUMENTS=1 pytest tests/ -vMock factory cheat sheet #
# Constant return
dmm = Mock(DMM, measure_dc_voltage=3.31)
# Map by argument — different return per query string
dmm = Mock(DMM, query={"MEAS:VOLT:DC?": "3.300", "*IDN?": "Keysight,34461A,..."})
# Callable — for noise or sweeps
import random
dmm = Mock(DMM, measure_dc_voltage=lambda: 3.3 + random.gauss(0, 0.005))Every method not configured is a silent no-op. Reading an unconfigured attribute (not a method call) raises AttributeError — that's the seam where a missing mock spec shows up.
Mocks vs real hardware #
| You run | mock_instruments is | Test code |
|---|---|---|
pytest tests/ | False | identical |
pytest tests/ --mock-instruments | True | identical |
The point of the wrap-in-conftest pattern: the test code is the same on a laptop and on the bench. Tests don't know which mode they're in.
What you learned #
--mock-instrumentsflag + themock_instrumentsfixtureMock(DriverClass, method=return_value, ...)wraps any driver class- The conftest fixture decides real vs mock — tests don't change
In later steps you'll lift this conftest conditional into station YAML (step 7) so the same setup serves a whole bench of tests. For now, conftest is enough.
Continue #
Now let's adopt three of Litmus's per-test fixtures — context, verify, logger — to start recording measurements with limits.
Tutorial · Step 3 of 11