What Is a Test Process Model
A process model defines the operations a test system performs around your test code: identifying the unit, initializing instruments, running the test, collecting results, and cleaning up. Your test sequence measures the product. The process model handles everything else.
The term comes from NI TestStand, where the process model is a separate sequence file that calls your test sequence. Other test executives use different names for the same idea. This guide explains what a process model does, why the concept exists, and how the same responsibilities are handled in a Python test framework.
What a Process Model Handles
Every production test does more than measure. Before a single voltage reading, something has to know which unit is on the fixture. After the last measurement, something has to decide the outcome and store the result.
| Responsibility | Example |
|---|---|
| Unit identification | Scan a barcode, read a serial from EEPROM, prompt the operator |
| Resource initialization | Open a VISA session, connect to a power supply, load calibration data |
| Test execution | Run the measurement steps in order |
| Outcome resolution | Decide pass or fail from all step results |
| Result storage | Write a report, insert a database row, upload to a server |
| Cleanup | Power down, release the fixture, close connections |
Only one row in that table is your test. The rest is identical across every procedure on the station, which is exactly why test executives factor it out. Write it once, reuse it everywhere.
Why the Abstraction Exists
Without a process model, every test script repeats the same boilerplate. Ten procedures means ten copies of the barcode scan, ten copies of the report writer, ten places to update when the database schema changes.
The process model inverts the relationship. Instead of your script calling a report function, the model calls your script:
Process Model├── Identify unit├── Initialize resources├── ──> Your test sequence (the only part you write per product)├── Resolve outcome├── Store results└── Clean upThis is inversion of control applied to test software. Your test code becomes a plug-in to a fixed lifecycle, rather than a program that has to remember every step.
The Three Classic Models
Test executives typically ship three built-in models, distinguished by how they handle multiple units:
| Model | Behavior | Use when |
|---|---|---|
| Sequential | One unit at a time, start to finish | Single-position fixture |
| Parallel | Independent units, each starting and finishing on its own schedule | Multiple independent fixtures |
| Batch | Multiple units loaded and tested together as a group | One fixture holding several units |
The distinction matters for throughput. A sequential model on a four-position fixture wastes three quarters of the hardware. For the decision rule and the throughput arithmetic, see Sequential vs Parallel vs Batch Testing.
Each independently tested position in these models is a test socket, the context that owns one unit for the duration of its test.
Callbacks: Customizing Without Forking
A fixed lifecycle is only useful if you can hook into it. Test executives expose named extension points, usually called callbacks, that run at defined moments: before the unit loop, after each unit, before the report is generated.
Callbacks let one product override report formatting while every other product keeps the default, with no copy of the model. The tradeoff is indirection. Reading a test that relies on five overridden callbacks means opening six files to understand one run.
The Same Responsibilities in Python
A Python test framework handles the same lifecycle, but declares it rather than modeling it as a separate callable sequence. In the TofuPilot Framework, the lifecycle is fixed by the engine and the procedure file describes what runs at each stage:
name: Battery Functional Testunit: serial_number: default_value: "BAT000001" part_number: default_value: "BAT-001"plugs: - name: power_supply python: plugs.psu:PowerSupply scope: stationsetup: - name: Warm Up Instruments python: phases.warm_upmain: - name: Measure Voltage python: phases.measure_voltage measurements: - name: voltage unit: V validators: - operator: ">=" expected_value: 4.8 - operator: "<=" expected_value: 5.2teardown: - name: Power Down python: phases.power_downEvery process model responsibility maps to a declaration:
| Process model concept | TofuPilot Framework |
|---|---|
| Unit identification | unit: block, with operator prompt or auto_identify |
| Resource initialization | plugs: with a lifetime scope |
| Pre-test callback | setup: phases |
| Test sequence | main: phases |
| Post-test callback | teardown: phases, which always run |
| Outcome resolution | Engine rule: worst phase outcome wins |
| Result storage | Built in, uploaded by the engine |
The phase functions themselves stay plain Python:
def measure_voltage(measurements, power_supply): measurements.voltage = power_supply.read_voltage()What Changes Without a Model File
Two things are genuinely different, and both are consequences of the lifecycle being fixed rather than editable.
Result storage is not yours to write. In a classic test executive, a report callback is a customization point, and teams write their own database logger. Here, uploading is the engine's job. You gain a schema you do not maintain; you lose the ability to change the storage format.
The order of operations is not overridable. You cannot reorder identification and plug initialization the way a custom model sequence could. What you can control is what runs inside each stage, plus the failure behavior:
execution: on_first_failure: continue workers: 16For most production test software, the fixed lifecycle is the point. The customization that mattered was always the test itself, not the plumbing around it. If your model customization exists to reshape reporting or database writes, that need disappears rather than migrating.
For a callback-by-callback mapping, including which ones to delete rather than port, see Replace TestStand Callbacks in Python.
When You Still Need Custom Orchestration
Some systems genuinely do not fit a fixed lifecycle: a test rig that reconfigures itself mid-run, a depot workflow that decides what to test after a diagnostic pass, a stress loop that runs for days.
For those, drive the loop yourself and use an SDK to record results, rather than bending a declarative procedure into shapes it does not fit. The lifecycle abstraction earns its place when the flow is the same every cycle. When the flow is the product, write the flow.
Key Points
- A process model is the fixed lifecycle around your test code: identify, initialize, execute, resolve, store, clean up.
- It exists to stop every procedure from repeating the same boilerplate.
- Sequential, parallel, and batch models differ only in how they handle multiple units.
- Callbacks customize the lifecycle without copying it, at the cost of indirection.
- A declarative Python framework keeps the same responsibilities but fixes the lifecycle, trading model-file flexibility for far less code to maintain.