product: assert audience: test-developer authority: normative
Measurement Patterns
Numeric measurement (default type)
Pass condition: value >= low_limit && value <= high_limit.
measurement:
name: "VOUT_5V" # REQUIRED — stored in every record
target: 5.05 # mock: used as measured value
value: "{{measured_v}}" # gRPC runners: template resolves to actual
low_limit: 4.75 # lower bound (inclusive)
high_limit: 5.25 # upper bound (inclusive)
unit: "V"
Numeric comparison operators
operator |
Aliases | Evaluation |
|---|---|---|
| (omitted) | — | value >= low_limit && value <= high_limit |
equal |
eq |
value == target |
notequal |
ne |
value != target |
greaterthan |
gt |
value > low_limit |
greaterthanorequal |
gte, ge |
value >= low_limit |
lessthan |
lt |
value < high_limit |
lessthanorequal |
lte, le |
value <= high_limit |
log |
— | Always PASS; informational only |
These operators apply to type: numeric only. For type: boolean and type: string
the only evaluation mechanism IS the expected: field — however, operator: log IS
supported on any type (numeric, boolean, string) to make the measurement informational-only.
Boolean measurement
Pass condition: value == expected.
Operators are NOT supported for type: boolean. Omitting expected: causes the
measurement to always PASS (informational-only).
measurement:
name: "RELAY_SELFTEST"
type: boolean
value: "true"
expected: "true"
String measurement
Pass condition: value == expected (exact, case-sensitive).
Operators are NOT supported for type: string. Omitting expected: causes the
measurement to always PASS.
measurement:
name: "FW_VERSION"
type: string
value: "v2.1.0"
expected: "v2.1.0"
Informational-only measurement (log operator)
operator: log applies to all measurement types. The measurement always evaluates
to PASS — the value IS recorded without any limit or expected-value check.
# Numeric — record a current draw without limits
measurement:
name: "SUPPLY_CURRENT_LOG"
target: 0.245
unit: "A"
operator: log
# String — record a MAC address
measurement:
name: "MAC_ADDRESS"
type: string
operator: log
value: "{{mac_address}}"
# Boolean — record a pin state
measurement:
name: "BOOT_FLAG"
type: boolean
operator: log
value: "{{boot_flag}}"
Multiple measurements from one step
Use measurements: (plural) when a single function returns several values:
- name: "Read all power rails"
runner: python
runner_type: python3.11
module: "instruments"
function: "read_power_rails"
outputs:
rail_3v3: "{{v3v3}}"
rail_5v0: "{{v5v0}}"
measurements:
- name: "RAIL_3V3"
value: "{{v3v3}}"
low_limit: 3.135
high_limit: 3.465
unit: "V"
- name: "RAIL_5V0"
value: "{{v5v0}}"
low_limit: 4.75
high_limit: 5.25
unit: "V"
measurements: takes precedence over measurement: when both are present.
Grouping measurements (group)
Every measurement accepts an optional group label. It is purely organisational — it
never affects the verdict — and lets you tag measurements so reports can group and filter
them instead of showing one long flat list. The classic case is repeated, identical
hardware: a DUT with five identical connectors can tag each connector's measurements with
CON1..CON5.
measurements:
- name: "CONT_PIN1"
value: "{{con1_pin1}}"
low_limit: 0
high_limit: 1
unit: "Ohm"
group: "CON1" # ← organisational tag; shown as a badge in the report
- name: "CONT_PIN2"
value: "{{con1_pin2}}"
low_limit: 0
high_limit: 1
unit: "Ohm"
group: "CON1"
group supports {{variable}} templates, so a sub-sequence reused per connector can set
group: "CON{{connector_index}}" and each invocation lands in its own group.
Notes:
groupis part of the measurement's structural identity: two measurements that are otherwise identical but carry different groups are distinct definitions (so their statistics/yield are tracked separately).- Max length 100 characters. Omit it entirely for ungrouped measurements.
- Grouping is complementary to structuring the test into one step (or sub-sequence) per
connector, which already gives each connector its own collapsible section and verdict.
Use per-step structure for independent verdicts/yield; use
groupwhen you want to tag measurements that live within a shared step.
Declaring a "no result" value (invalid_value)
Many packages seed their numeric variables to a sentinel such as -999, so that a step which
errors or never runs FAILS honestly instead of recording a plausible midpoint. That is the right
thing to do for the verdict, and it quietly ruins the statistics: nothing else in the system
distinguishes this is not a reading from this is a reading, so the sentinel is averaged in and
Cp/Cpk end up describing a process that nobody runs.
invalid_value declares it. Numeric measurements only.
variables:
vout: "-999" # seeded so an unrun step cannot pass
measurements:
- name: "VOUT_5V"
value: "{{vout}}"
low_limit: 4.75
high_limit: 5.25
unit: "V"
invalid_value: -999 # ← "no result", not a measurement
What it changes, and what it deliberately does not:
| Verdict | Unchanged. -999 is still outside the limits, so the step still FAILS. |
| Report | Unchanged. The operator still sees the value that was recorded. |
| Yield | Unchanged. The failure still counts against yield. |
| Statistics | The sample is excluded from mean, sigma, Cp and Cpk, and counted in excludedInvalidSamples. |
The exclusion is applied at query time as well as at record time, so declaring invalid_value on
an existing measurement also cleans up the history that was recorded before you declared it.
It is part of the structural specification, like low_limit and group, so adding or changing it
creates a new definition version.
When the step fails: measurements that were not acquired
A step whose method throws (or whose runner call fails) is FAIL, and every measurement it declares
is still recorded, so the report shows what was expected. What each measurement records depends on
where its value: comes from:
value: reads |
Recorded as |
|---|---|
| Only variables the failing step itself wrote (e.g. an output the runner returned before failing) | That value, verdict FAIL - visible for triage, never PASS |
| Any variable the failing step did NOT write | Not acquired: verdict FAIL, no value at all |
| A variable that does not exist (the template stays unresolved), on any step | Not acquired: verdict FAIL, no value at all, and the step FAILS |
A literal (no {{...}}) |
That literal, verdict FAIL |
"Not acquired" means the measurement has no reading: every value field is empty in the record, the
report and the live monitor show not acquired instead of a number, and the statistics never see
it (it has no value to average, so no invalid_value is needed to keep it out). The step's error
message lists the measurements that were not acquired.
This matters most in a sub-sequence invoked once per connector. Before 2.6.0, when CON4's read
threw, value: "{{loopback_serial}}" still resolved to the value CON3 had left in the variable and
was published under CON4's group. Now CON4's measurement is not acquired, and - see
Variable System - CON4 starts from the
sub-sequence's declared defaults rather than from CON3's values.
For the same reason, when a step fails its outputs: mapping skips any entry whose template reads a
variable the step did not write, rather than copying an earlier step's value into it.
A step that PASSES may still measure a variable set by an earlier step - the usual "prompt, then evaluate" pattern - and that is recorded as a reading, as before.