Measurements
Learn how to capture and validate data within OpenHTF phases, including boolean, string, numeric, multidimensional, and marginal measurements.
Last updated · Verified with OpenHTF 1.6.1
Create measurements to capture and validate data within phases.
A measurement declares a name and a validator. When the phase sets the value, OpenHTF checks it against the validator: temperature 25 °C in range 0 to 100 passes, voltage 10.6 V above the 10 V maximum fails, and firmware version 1.2.4 equals the expected string and passes.
- temperature.in_range(0, 100)25.0 °CPASS0100
- voltage.in_range(maximum=10)10.6 VFAIL010
- firmware_version.equals("1.2.4")"1.2.4"PASSexpected "1.2.4"=="1.2.4"
Hardware tests are more complex than simple pass/fail checks like in software testing. They often require measuring physical values and comparing them to limits. OpenHTF simplifies logging and validating numeric, string, and boolean values, either individually or in arrays, using built-in decorators.
Numeric
You can define and validate numeric measurements.
import openhtf as htf
from openhtf.util import units
@htf.measures(
htf.Measurement("temperature") # Declares the measurement name
.in_range(0, 100) # Defines the validator
.with_units(units.DEGREE_CELSIUS) # Specifies the unit
.with_precision(1) # Rounds to 1 decimal place
)
def phase_temperature(test):
test.measurements.temperature = 25 # Set the temperature value to 25°C
def main():
test = htf.Test(phase_temperature)
test.execute(lambda: "SN1234")
if __name__ == "__main__":
main()Validators
.in_range(minimum, maximum)number, numberEnsure the measurement is within the given range.
.within_percent(value, percent)number, numberEnsure the measurement is within the given percentage range.
.equals(value)numberEnsure the measurement exactly matches the specified value.
.with_validator(lambda)function → boolApply a custom validator function to the measurement.
Options
.with_units(units)UnitDescriptorDefine the unit of the measurement (e.g. units.AMPERE, units.VOLT). All 2,134 constants are listed in the units reference.
.with_precision(precision)intRound the value to the specified precision before validation.
import openhtf as htf
from openhtf.util import units
@htf.measures(
htf.Measurement("voltage")
.in_range(maximum=10)
.with_units(units.VOLT)
)
def phase_voltage(test):
test.measurements.voltage = 5.3
@htf.measures(
htf.Measurement("memory")
.equals(8)
.with_units(units.GIGABYTE)
)
def phase_memory(test):
test.measurements.memory = 8
def main():
test = htf.Test(phase_voltage, phase_memory)
test.execute(lambda: "SN1234")
if __name__ == "__main__":
main()String
You can define and validate string measurements.
import openhtf as htf
from openhtf.util import units
@htf.measures(
htf.Measurement("firmware_version")
.equals("1.2.4")
)
def phase_firmware(test):
test.measurements.firmware_version = "1.2.4"
def main():
test = htf.Test(phase_firmware)
test.execute(lambda: "SN1234")
if __name__ == "__main__":
main()Validators
.equals(value)strEnsure the measurement exactly matches the specified value.
.matches_regex(pattern)strEnsure the string matches the specified regex pattern.
.with_validator(lambda)function → boolApply a custom validator function to the measurement.
Boolean
You can define and validate boolean measurements.
import openhtf as htf
@htf.measures(
htf.Measurement("is_led_switch_on")
.equals(True)
)
def phase_led(test):
test.measurements.is_led_switch_on = True
def main():
test = htf.Test(phase_led)
test.execute(lambda: "SN1234")
if __name__ == "__main__":
main()Validators
.equals(value)boolEnsure the measurement exactly matches the specified value.
.with_validator(lambda)function → boolApply a custom validator function to the measurement.
Multi-dimensional
You can capture data in arrays, like time-series or sweeps, with multidimensional measurements. Each dimension is an input axis (the coordinates you index with); with_units describes the stored value.
import openhtf as htf
from openhtf.util import units
import random
@htf.measures(
htf.Measurement("temperature_over_time")
.with_dimensions(units.SECOND) # Input axis: elapsed time
.with_units(units.DEGREE_CELSIUS) # Stored value: temperature
)
def thermal_soak(test):
for t in range(10):
temperature = round(24 + random.uniform(-0.5, 0.5), 2)
test.measurements.temperature_over_time[t] = temperature
@htf.measures(
htf.Measurement("gain_sweep")
.with_dimensions(units.HERTZ, units.VOLT) # Axes: frequency, input level
.with_units(units.DECIBEL) # Stored value: gain
)
def frequency_sweep(test):
for freq in (100, 1_000, 10_000):
for level in (0.1, 1.0):
test.measurements.gain_sweep[freq, level] = round(20 - freq / 10_000, 2)
def main():
test = htf.Test(thermal_soak, frequency_sweep)
test.execute(lambda: "SN1234")
if __name__ == "__main__":
main()Indexing a dimensioned measurement returns a DimensionedMeasuredValue; call .to_dataframe() on it (requires pandas) to analyse the samples inside a later phase. Monitors create a one-dimensional time series like temperature_over_time automatically from a background thread.
Options
.with_dimensions(*dims)UnitDescriptor or DimensionDeclare one input axis per argument. Use htf.Dimension(description=..., unit=...) to label an axis.
.dimension_pivot_validate(validator)validatorApply a scalar validator to every stored value, e.g. .dimension_pivot_validate(validators.in_range(20, 30)). See the validators reference.
Multiple measurements
You can also use multiple measurements in a single phase.
import openhtf as htf
from openhtf.util import units
@htf.measures(
htf.Measurement("is_connected").equals(True),
htf.Measurement("firmware_version").equals("1.2.7"),
htf.Measurement("temperature").in_range(0, 100).with_units(units.DEGREE_CELSIUS),
)
def phase_multi_measurements(test):
test.measurements.is_connected = True
test.measurements.firmware_version = "1.2.7" if test.measurements.is_connected else "N/A"
test.measurements.temperature = 22.5
def main():
test = htf.Test(phase_multi_measurements)
test.execute(lambda: "SN1234")
if __name__ == "__main__":
main()Advanced use cases
Marginal
You can mark a measure as marginal to show it's close to failing, even if it passes.
import openhtf as htf
from openhtf.util import units
@htf.measures(
htf.Measurement('resistance')
.with_units('ohm')
.in_range(minimum=5, maximum=17, marginal_minimum=9, marginal_maximum=11)
)
def phase_marginal(test):
test.measurements.resistance = 13
def main():
test = htf.Test(phase_marginal)
test.execute(lambda: "SN1234")
if __name__ == "__main__":
main()================= test: openhtf_test outcome: PASS (MARGINAL) =================Documentation
You can add a description to your measurements.
import openhtf as htf
@htf.measures(
htf.Measurement("temperature")
.in_range(0, 100)
.doc("This measurement tracks the ambient temperature during the test.")
)
def phase_temperature(test):
test.measurements.temperature = 25
def main():
test = htf.Test(phase_temperature)
test.execute(lambda: "SN1234")
if __name__ == "__main__":
main()Dynamic naming
You can customize measurement names dynamically at execution.
import openhtf as htf
@htf.measures(
htf.Measurement("test_result_{level}")
.with_args(level="high")
.equals(True)
)
def phase_test(test):
test.measurements.test_result_high = True
def main():
test = htf.Test(phase_test)
test.execute(lambda: "SN1234")
if __name__ == "__main__":
main()Transformation function
You can apply measurement transformation functions before validation.
import openhtf as htf
@htf.measures(
htf.Measurement("voltage")
.in_range(0, 10)
.with_transform(lambda x: x * 1.1)
.with_units("V")
)
def phase_voltage(test):
test.measurements.voltage = 5 # Value will be transformed to 5.5 before validation
def main():
test = htf.Test(phase_voltage)
test.execute(lambda: "SN1234")
if __name__ == "__main__":
main()Callable decorators
Python decorators are callables, so you can define measurement parameters dynamically at runtime and apply them to phase functions. The Decorators page explains the mechanism; this is the measurement-specific case.
import openhtf as htf
from openhtf.util import units
def MyPhaseFunction(test):
"""Define the function without any htf decorators that need to change at runtime"""
test.measurements.resistance = 10.5
def main():
# Get measurement limits dynamically at runtime
min_ohms = 5
max_ohms = 17 # These could come from configuration, user input, etc.
# Create the measurement with dynamic parameters
my_measurement = (htf.Measurement('resistance')
.with_units('ohm')
.in_range(minimum=min_ohms, maximum=max_ohms))
# Apply the decorator as a callable to create the phase
my_phase = htf.measures(my_measurement)(MyPhaseFunction)
# Use the dynamically created phase in your test
test = htf.Test([my_phase])
test.execute(lambda: "SN1234")
if __name__ == "__main__":
main()Typical use: limits loaded from a configuration file or a product database at station start.