# Tutorial — Test a Resistor with OpenHTF
URL: /tutorial
Step-by-step OpenHTF tutorial. Build a resistor test with a power supply and multimeter plug (PyVISA, with a simulation mode), add a measurement with limits, read the console summary, and upload the record.
Build a complete test in six steps: apply a voltage with a programmable supply, read the current with a multimeter, compute the resistance with Ohm's law and check it against a tolerance. This mirrors the resistor tutorial in the upstream repository, rewritten so every step runs without hardware.
You will use phases, measurements with validators and units, plugs with a simulation flag, and configuration to switch between the bench and the simulator.
Create `main_test.py` with one phase that does nothing yet:
A phase that returns nothing is a `CONTINUE`, so the test passes.
Tell OpenHTF the phase will record `resistor_val`:
It fails: a declared measurement that is never set is a failure (see `allow_unset_measurements` in Configuration). To see *why* it failed, add a console summary callback:
Set a placeholder value and the test passes again:
Instruments live in plugs. These two wrap a multimeter and a power supply over PyVISA and take a `simulate` flag so the tutorial runs on any laptop. Copy them into `resistor_plugs.py`:
`tearDown()` runs after the test whether it passed or not, so the supply is always switched off. The VISA resource strings are the Rigol DM858 and DP932E used upstream; replace them with your own (`pyvisa.ResourceManager().list_resources()` lists what is connected — see the PyVISA plug guide).
Inject both plugs and compute the resistance:
Right now the plugs are constructed with `simulate=False`, so this step needs the bench. The next step fixes that.
The plugs take `simulate` in `__init__`, but `@htf.plug(dmm=MultimeterPlug)` constructs them with no arguments. `bind_init_args` binds constructor arguments to configuration values, so the same script runs on the bench (`simulate: false`) and on a laptop (`simulate: true`):
Run it simulated without touching the code:
A 5.6 kΩ ±5 % resistor must read between 5320 Ω and 5880 Ω. Add the validator:
With a 220 Ω part on the bench (or by forcing `current = 0.018` in the simulator) the summary now explains the failure:
You have a deployable resistor test. From here:
Save every run: `test.add_output_callbacks(json_factory.OutputToJSON("./records/{dut_id}.{start_time_millis}.json", indent=2))` — Output Callbacks.
Ask the operator for the serial number instead of hard-coding it — Device Under Test.
Give the operator a browser UI — Operator UI.
Track yield across thousands of resistors: `test.add_output_callbacks(upload())` — Manufacturing Test Analytics.