# 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.