Creating a Test Plan
A test plan is an ordinary pytest project that loads the f3ts plugin. This page covers the recommended file layout and how to scaffold a new plan.
Minimal layout
The smallest plan is two files in one directory:
my_test_plan/
├── config.yml # test-case limits + runner settings (required)
└── test_plan.py # the test casesThat's enough to run offline (see Your First Test).
Recommended layout
As a plan grows — real hardware, shared setup, helper modules — split it up. This mirrors how production FixturFab test plans are organized:
my_test_plan/
├── config.yml # test-case limits, runner + fixture settings
├── conftest.py # pytest fixtures (open/close the hardware interface)
├── interface.py # the class that controls the test hardware / DUT
├── test_plan.py # the test cases
├── hw_modules/ # (optional) drivers for instruments, GPIO, etc.
└── pyproject.toml # dependencies (pins pytest-f3ts)config.yml— limits and metadata for each test case. See Configuration.interface.py— one class that owns all hardware access. See The Test Interface.conftest.py— pytest fixtures, most importantly the session-scopedinterfacefixture that opens the hardware once and tears it down at the end.test_plan.py— the tests. They pull ininterface,test_config, and any f3ts fixtures they need.
Scaffold a new plan
- Create a directory and a Poetry project:bash
mkdir my_test_plan && cd my_test_plan poetry init poetry add pytest-f3ts - Add a
config.yml(must includetest_name,runner_settings, andtest_cases):yamltest_name: "my-test-plan" runner_settings: {} test_cases: test_power_on: test_id: "1.1" description: "DUT powers on" - Add an
interface.pywith a class that controls your hardware:pythonimport logging logger = logging.getLogger(__name__) class TestInterface: """Controls the test hardware / device under test.""" def __init__(self): # Open serial ports, VISA instruments, GPIO, etc. pass def close(self): # Close ports / power down. pass - Add a
conftest.pyexposing the interface as a session fixture:pythonimport pytest from interface import TestInterface @pytest.fixture(scope="session") def interface(request): dut = TestInterface() request.addfinalizer(dut.close) return dut - Add a
test_plan.pywith your first test:pythondef test_power_on(interface): assert interface is not None
Running the test plan
From the plan directory:
bash
pytest -p f3tsSee Running Locally for the offline workflow, and With the Test Runner for running under the FixturFab Test Runner.
A complete, runnable version of this layout (with a mock interface, so it passes with no hardware) is documented in the Starter Test Plan.