Skip to content
Migrating from Legacy Systems

Replace Excel Test Tracking

Move from spreadsheet-based test tracking to structured, automated test data collection with OpenHTF and TofuPilot.

JJulien Buteau
beginner8 min readMarch 14, 2026

Most hardware teams start tracking test results in Excel or Google Sheets. It works for a while, then it doesn't. You lose real-time visibility, can't handle concurrent access, and manual entry introduces errors that compound over time.

TofuPilot replaces that workflow with structured, automated test data collection. You keep writing tests in Python, and TofuPilot handles storage, analytics, and traceability.

The Spreadsheet Pattern

A typical Excel test log looks something like this:

Serial NumberDateOperatorResultVoltage (V)Current (A)FirmwareNotes
SN-0012025-01-15AlicePASS3.310.52v2.1
SN-0022025-01-15BobFAIL3.580.89v2.1Over current limit
SN-0032025-01-16AlicePASS3.290.48v2.1

This format has real problems at scale:

  • No concurrent access. Two operators can't log results simultaneously without risking overwrites or merge conflicts.
  • No validation. Nothing stops someone from entering "PSAS" instead of "PASS" or putting voltage in the current column.
  • No analytics. Calculating FPY, Cpk, or failure trends means writing fragile formulas or pivot tables that break when the sheet structure changes.
  • No history. When someone edits a cell, the original value is gone. You have no audit trail.

The OpenHTF + TofuPilot Equivalent

Here's the same test expressed as an OpenHTF test with TofuPilot integration. Every run is automatically structured, validated, and stored.

power_board_test.py
import openhtf as htf
from openhtf.util import units
from tofupilot.openhtf import TofuPilot

@htf.measures(
    htf.Measurement("voltage").with_units(units.VOLT).in_range(3.1, 3.5),
    htf.Measurement("current").with_units(units.AMPERE).in_range(maximum=0.7),
)
def power_supply_check(test):
    voltage = 3.31  # Read from your instrument
    current = 0.52
    test.measurements.voltage = voltage
    test.measurements.current = current

def main():
    test = htf.Test(power_supply_check)
    with TofuPilot(test):
        test.execute(test_start=lambda: "SN-001")

if __name__ == "__main__":
    main()

Each run automatically captures the serial number, pass/fail outcome, every measurement with its limits, timestamps, and the test station identity. No manual entry. No copy-paste errors.

What Changes When You Switch

CapabilityExcel / Google SheetsTofuPilot
Data entryManual, error-proneAutomatic from test code
Concurrent accessFile locks, merge conflictsMulti-user, multi-station by default
Measurement validationNoneLimits enforced at test time
FPY and yield trendsManual formulasBuilt-in dashboard, real-time
Cpk and SPCRequires custom macrosAutomatic control charts
Failure analysisManual filteringFailure Pareto, drill-down by station
Audit trailNo historyFull revision history per run
Search and filterCtrl+FFilter by serial, station, date, outcome, batch
API accessNoneREST API for integrations

Keeping Your Existing Data

If you have historical test data in spreadsheets that you want to preserve, you can import it through TofuPilot's REST API. Structure each row as a test run with measurements and POST it to the API.

import_from_csv.py
import csv
from tofupilot import TofuPilotClient

client = TofuPilotClient()

with open("test_results.csv") as f:
    reader = csv.DictReader(f)
    for row in reader:
        client.create_run(
            procedure_id="power-board-test",
            unit_under_test={"serial_number": row["Serial Number"]},
            run_passed=row["Result"] == "PASS",
            steps=[
                {
                    "name": "power_supply_check",
                    "step_passed": row["Result"] == "PASS",
                    "measurements": [
                        {
                            "name": "voltage",
                            "measured_value": float(row["Voltage (V)"]),
                            "unit": "V",
                            "lower_limit": 3.1,
                            "upper_limit": 3.5,
                        },
                        {
                            "name": "current",
                            "measured_value": float(row["Current (A)"]),
                            "unit": "A",
                            "upper_limit": 0.7,
                        },
                    ],
                }
            ],
        )

Run this once to backfill your history, then switch all new tests to the OpenHTF workflow.

What You Get in the Dashboard

Once your tests report to TofuPilot, open the dashboard at tofupilot.app. You'll find:

  • FPY trends across stations and time periods, calculated automatically.
  • Measurement histograms with Cpk and control charts for every measurement you define.
  • Failure Pareto showing which measurements fail most often and on which stations.
  • Station throughput so you can see which lines are running and which are idle.
  • Full traceability per serial number, with every test run, measurement, and revision linked.

These are the analytics you'd otherwise build with pivot tables, VBA macros, or custom scripts. They update in real time as new runs come in.

Running Both Systems in Parallel

You don't have to switch overnight. A practical migration path:

  1. Pick one test station and add the TofuPilot integration to its OpenHTF tests.
  2. Run both systems for a week. Keep logging to the spreadsheet while TofuPilot collects the same data automatically.
  3. Compare results to build confidence that nothing is lost.
  4. Roll out to remaining stations once you're satisfied.

The spreadsheet stays as a backup until you're ready to retire it. No data is at risk during the transition.

More Guides

Put this guide into practice