Migrating from Legacy Systems

Test Engineer Interview: A Python Take-Home

Learn how to run a two-hour Python take-home that checks instrument control, limits and failure handling, with starter files, a grading grid and questions.

JJulien Buteau
intermediate9 min readSeptember 23, 2026

A test engineer take-home should prove four things in under two hours: the candidate can talk to an instrument, apply limits, handle a hardware failure, and ship a result somebody else can read. The exercise below does that with a mock power supply, a mock DMM that times out one call in ten, and the TofuPilot Framework as the sequencer. You get starter files, a grading grid and the questions for the follow-up conversation.

What the Exercise Proves

A CV tells you which tools a candidate has used. The take-home tells you whether they can do the job, and it does it in an hour of their time and fifteen minutes of yours.

SkillHow the exercise checks it
Talk to an instrumentSet a voltage on a supply plug, read it back through a DMM plug, both with a realistic method surface.
Apply limitsDeclare the limits in procedure.yaml, not in the phase. Knowing where limits belong is the point.
Handle a hardware failureThe mock DMM raises a timeout one time in ten. The phase has to retry or fail cleanly, never hang or crash.
Ship a resultThe station runs with tofupilot run and produces a run someone else can open. A strong candidate uploads it and sends the link.

The instruments are mocks, so the candidate needs nothing but Python and the CLI. That's deliberate: you're hiring for the reasoning, and the real PyVISA call is a one-line swap they'll make in week one.

The Exercise Brief

Send this as the README of a small repo along with the starter files below. Ask for a pull request or a zip within a week, and tell them it should take about two hours.

take-home/README.md
30 lines
# Take-home: power rail phaseYou have a mock power supply plug and a mock DMM plug. The DMM times outone call in ten. Write a phase that:1. Sets the supply to 5.0 V and enables the output.2. Reads the voltage back through the DMM.3. Records the reading as the measurement `rail_5v`, with limits declared   in `procedure.yaml` (4.85 V to 5.15 V).4. Handles a DMM timeout without crashing the run. Retrying is fine.   Decide how many times, and say why in a comment.5. Turns the supply output off at the end, even when something fails.It should run with:    curl -fsSL https://www.tofupilot.app/install | sh    tofupilot run ./procedure.yamlOptional: run it with `--upload` and send us the run link fromhttps://tofupilot.app. The Lab tier is free and takes two minutes to set up.What we look at: where the limits live, how the timeout is handled, whetherthe supply is always turned off, and whether the code would still be clearto you in six months. We don't care about line count.Files:- procedure.yaml       (skeleton, fill in the phase and its measurement)- phases/power_rail.py (stub)- plugs/mock_psu.py    (done, don't edit)- plugs/mock_dmm.py    (done, don't edit)

Keep the brief short. A long brief tests reading, not engineering.

The Starter Files

Four files. The plugs are complete and the candidate shouldn't touch them. The procedure and the phase are stubs.

take-home/plugs/mock_psu.py
# Mock bench power supply. Same method surface as a real SCPI supply plug.# STATE stands in for the wire between the supply and the DMM.STATE = {"setpoint": 0.0, "output": False}class PowerSupply:    def __init__(self):        STATE["setpoint"] = 0.0        STATE["output"] = False    def set_voltage(self, volts: float) -> None:        STATE["setpoint"] = float(volts)    def output(self, enabled: bool) -> None:        STATE["output"] = bool(enabled)    def close(self) -> None:        STATE["output"] = False
take-home/plugs/mock_dmm.py
25 lines
# Mock DMM that times out one call in N, like a flaky GPIB or LAN link.import randomfrom plugs.mock_psu import STATEclass InstrumentTimeout(Exception):    """Raised when the instrument doesn't answer in time."""class Multimeter:    TIMEOUT_ONE_IN = 10    def __init__(self):        # A real plug opens a VISA resource here. The mock reads the supply's wire.        self._rng = random.Random()    def read_voltage(self) -> float:        if self._rng.randrange(self.TIMEOUT_ONE_IN) == 0:            raise InstrumentTimeout("no reply from DMM within 2000 ms")        volts = STATE["setpoint"] if STATE["output"] else 0.0        return volts + self._rng.gauss(0.0, 0.01)    def close(self) -> None:        pass
take-home/procedure.yaml
20 lines
# Skeleton. Add the phase under main and its measurement with limits.name: Take-home power railversion: 0.1.0unit:  serial_number:    default_value: TH-0001  part_number:    default_value: TAKE-HOME-Aplugs:  - name: psu    python: plugs.mock_psu:PowerSupply  - name: dmm    python: plugs.mock_dmm:Multimetermain:  - name: Power Rail    python: phases.power_rail    measurements: []
take-home/phases/power_rail.py
# Stub. Set 5 V on the supply, read it back on the DMM, record rail_5v.# Handle the DMM timeout. Always turn the supply off.def power_rail(measurements, psu, dmm):    raise NotImplementedError

The mock DMM reads the supply's STATE so a correct phase gets about 5 V back and a phase that forgot to enable the output gets 0 V and a failed limit. In a real station the DMM plug opens a VISA resource instead and has no idea a supply exists, which is exactly why the phase, not the plug, is the thing under test.

The Grading Grid

Score each row before the conversation, from the code alone. Three "strong" rows and no "weak" ones is a hire at mid level.

CriterionWeakSolidStrong
LimitsHardcoded in the phase, or a bare if with a print.Declared in procedure.yaml under the measurement, phase only records the value.Same, and the pull request description says where 4.85 and 5.15 came from and asks whether they're right.
Timeout handlingUncaught, or a bare except: that swallows everything.Catches InstrumentTimeout, retries a bounded number of times, fails the phase after that.Bounded retry with a short wait, the retry count is a named constant with a comment on why, and the failure message says what the operator should check.
CleanupSupply left on when the phase fails.try/finally turns the output off.Same, and the phase doesn't reach into the plug's private state.
RunsDoesn't run, or needs edits to run.tofupilot run ./procedure.yaml passes and fails as expected.Ran it with --upload and sent the run link from TofuPilot.
ReadabilityOne function that does everything, no names.Small functions, clear names, one comment where it earns its place.You could hand it to a junior and they'd understand it without asking.
CommunicationZip file, no note.Short note on what they did and what they'd do next.Note flags a real gap in the brief (settling time before the read, what "one in ten" means for FPY).

A "weak" in timeout handling or cleanup is the one to weigh most. Those two rows are what a station does at 2 a.m. when nobody is watching.

The 45-Minute Follow-Up

Walk through their code for ten minutes, then spend the rest on these. There are no right answers; you're listening for how they reason about a floor.

"What would you change to test 4 DUTs at once?" Solid: split the phase per slot, one supply channel per DUT, keep the limits shared. Strong: asks whether the DMM is switched through a relay matrix or there are four DMMs, and points out the timeout now costs four units of throughput, not one.

"Where do the 4.85 and 5.15 come from?" Solid: the regulator datasheet plus design margin. Strong: asks what the downstream load tolerates, whether the number is the same at cold, and who signs off when the limit changes. Points out that the limit lives in procedure.yaml so the change is a reviewable diff.

"Cpk on rail_5v drops from 1.6 to 1.1 over a month. What do you do?" Solid: open the control chart in TofuPilot, look at whether the mean drifted or the spread grew, check the DMM calibration date. Strong: asks whether it's one station or all of them, and whether the fixture contact resistance changed before blaming the boards.

"The DMM times out one in ten. What's that doing to FPY?" Solid: with a retry it's doing nothing to FPY but something to cycle time. Strong: says a flaky link should be an error outcome, not a fail, so it doesn't show up as a yield loss, and asks how the platform distinguishes the two.

"An operator says the station passed a board that came back from the customer dead. Where do you start?" Solid: pull the run for that serial number and read the measurements. Strong: compares it to the population for that phase, checks whether it passed near a limit, and asks what the test doesn't cover.

"You inherit a LabVIEW station nobody can open. What's your first week?" Listen for: run it as an operator first, find the limits, get the results off the machine, then decide about a rewrite. Bonus if they've read onboard a test engineer in one week or say something close to it.

Score the conversation against Python skills checklist for test engineers so two interviewers rate the same way.

What Not to Test For

Don't test for a LabVIEW certification. CLAD, CLD and CLA prove someone passed an NI exam, and the exam covers the tool, not the floor. A candidate with a CLD who can't explain a timeout retry is a weaker hire than one without it who can.

Don't test for NI product names. Whether someone has used a PXI chassis or knows what TDMS stands for says nothing about whether they can bring up a fixture. Ask how they'd get a measurement out of an instrument they've never seen, and let them pick the vendor.

Don't test for your sequencer. If a candidate has only used OpenHTF, pytest or an in-house runner, the TofuPilot Framework is a day of reading. The brief tells them the structure; the exercise checks whether they use it well.

Don't add a whiteboard algorithm round. A test engineer sorts by yield, not by comparison count.

The posting that produces candidates for this exercise is in test engineer job description for Python hires.

Start With One Station

Before you send the take-home, run it yourself. Better, build it from a real station: pick the one with the highest volume or the one only one person can open, rebuild it in Python with the TofuPilot Framework, run it side by side with the LabVIEW version on the same units for two weeks, and cut the take-home from one of its phases. Candidates then solve a problem that exists on your floor, and the follow-up questions write themselves.

install-and-run.sh
# Install the CLI, then run the station locally (no account needed) or with upload.curl -fsSL https://www.tofupilot.app/install | shtofupilot run ./procedure.yamltofupilot run ./procedure.yaml --upload

The rebuild is in how to migrate from LabVIEW to Python for manufacturing tests with TofuPilot, and the framework is at tofupilot.com/products/framework. The Lab tier is free, and tofupilot run works without an account.

More Guides

Put this guide into practice