Use in Unit Tests (MiL)
This guide shows how to exercise your Software Under Test (SUT) against an SPX simulation inside Python’s unittest. The approach is Model‑in‑the‑Loop (MiL): the SUT is your real client application (e.g., a Modbus TCP reader), while the SPX model acts as the plant/sensor.
We will:
spin up a model (PT100‑style temperature sensor) and expose it over Modbus TCP,
drive the simulation deterministically (no wall‑clock timing),
read the measurements via the SUT and assert expected behavior.
For a hands‑on intro to wiring Modbus, see “Add Modbus TCP/IP to Your Simulation” in this Getting Started section.
Prerequisites
A running SPX Server (e.g.,
http://localhost:8000) and a validSPX_PRODUCT_KEYin your environment.If SPX Server runs in Docker, expose Modbus TCP from the container (for example
- "1502:502"in Compose) and point your SUT at127.0.0.1:1502.Python packages:
pip install spx-python modbus-tk pyyamlThe example uses
modbus-tkin the SUT. You can adapt it to your own stack (pymodbus, embedded code, etc.).
Test structure at a glance
setUpClass: connect to SPX, register the model (YAML), create an instance, start the Modbus server.setUp: create the SUT client (e.g., Modbus master).Tests:
connectivity/read sanity,
temperature evolution under deterministic stepping,
fault flag round‑trip (write in SPX, read in SUT).
tearDown: close SUT connection.tearDownClass: stop/cleanup the SPX instance.
Full example (unittest)
unittest)The model and mapping mirror the Modbus example from the previous guide.
Why deterministic stepping?
Repeatable:
run()advances the model one step, independent of machine load.Stable assertions: you can reason about expected state after N steps.
Protocol servers still run in the background, so your SUT can poll/subscribe reliably.
Prefer
prepare()+run()for unit tests. Reservestart()(real‑time scheduler) for interactive/manual testing or HIL‑like demonstrations.
What to assert in MiL tests
Connectivity: your SUT can read at least once without errors.
Monotonic/shape properties: e.g., with a ramp action, later readings should exceed earlier ones.
Round‑trips: toggling flags/commands in the model is visible to the SUT (and vice versa if applicable).
Tolerance windows: when noise is enabled, compare with deltas (±ε), not exact equality.
Troubleshooting
“Connection refused”: Ensure the SPX model’s Modbus server is started:
inst["communication"]["modbus_slave"].start().No change in values: If you used deterministic stepping, make sure you call
client.run()in your test loop, or explicitly advance model time if your system expects it.Racey reads with
start(): In real‑time mode, add small sleeps (e.g.,time.sleep(0.1)) to align withtimer.dt, or switch back to deterministic stepping for unit tests.
Next: CI integration
In the next article we’ll wire these tests into GitHub Actions: provisioning Python, exporting SPX_PRODUCT_KEY as a secret, starting the SPX Server service, and running the suite headlessly.
Last updated

