vial-circle-checkUse 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 valid SPX_PRODUCT_KEY in your environment.

  • If SPX Server runs in Docker, expose Modbus TCP from the container (for example - "1502:502" in Compose) and point your SUT at 127.0.0.1:1502.

  • Python packages:

    pip install spx-python modbus-tk pyyaml
  • The example uses modbus-tk in 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:

    1. connectivity/read sanity,

    2. temperature evolution under deterministic stepping,

    3. fault flag round‑trip (write in SPX, read in SUT).

  • tearDown: close SUT connection.

  • tearDownClass: stop/cleanup the SPX instance.


Full example (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. Reserve start() (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 with timer.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