Migrating from Legacy Systems

OpenHTF vs NI TestStand

Comparaison d'OpenHTF et NI TestStand pour l'automatisation des tests en production, avec matrices de fonctionnalités, exemples de code, analyse des coûts.

JJulien Buteau
intermediate10 min de lecture14 mars 2026

OpenHTF est un framework de test gratuit et open source de Google. NI TestStand est un séquenceur de test commercial de NI (Emerson) à 4 310 $/poste/an. Les deux exécutent des tests en production avec des mesures, des limites et un séquencement. Ils diffèrent par le coût, le support de plateforme, l'intégration de base de données et la gestion des données de test. Ce guide les compare côte à côte avec du code réel et des compromis concrets.

Comparaison des fonctionnalités

FonctionnalitéOpenHTFNI TestStand
LangagePythonLabVIEW, C, .NET, Python
LicenceApache 2.0 (gratuit)4 310 $/poste/an
PlateformeLinux, macOS, WindowsWindows uniquement
Éditeur de testN'importe quel éditeur de codeSequence Editor propriétaire
Contrôle de versionGit (fichiers .py simples)Difficile (fichiers .seq binaires)
Mesures structuréesIntégrées (nom, valeur, limites, unités)Intégrées (Numeric Limit, String Value)
Saisie numéro de sérieInvite intégréeIntégrée (Process Model)
DUT parallèleLimitéNatif
Pilotes instrumentsPyVISA, pyserial, nidaqmxNI VISA, IVI, pilotes NI
Journalisation BDDTofuPilot (1 ligne)Intégrée (schéma complexe)
Analyses (FPY, Cpk)TofuPilot (automatique)Requêtes personnalisées ou outil tiers
Intégration CI/CDNative (Python)Limitée (ajoutée 2025 Q2)
Génération de rapportsTofuPilot (automatique)XML/HTML intégrés
CommunautéPetite (~640 étoiles GitHub)Grande (forums NI, cours de formation)
Courbe d'apprentissageMoyenneÉlevée

Analyse des coûts

MétriqueOpenHTF + TofuPilotNI TestStand
5 postes, 1 an0 $ (TofuPilot Lab est gratuit)21 550 $
20 postes, 1 an0 $86 200 $
Déploiement runtimeGratuitLicences runtime supplémentaires
FormationAuto-formation (docs openhtf.com)Cours NI (2 000 $+)
Dépendance fournisseurAucuneÉlevée (écosystème NI)

TestStand nécessite aussi Windows, ce qui signifie des licences Windows pour chaque station de test. OpenHTF fonctionne sous Linux, qui est gratuit et plus stable pour les stations de production en fonctionnement continu.

Le même test dans les deux frameworks

Un test fonctionnel simple : mesurer un rail 3,3 V, vérifier qu'il est entre 3,2 V et 3,4 V.

OpenHTF + TofuPilot

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


@htf.measures(
    htf.Measurement("rail_3v3")
    .in_range(3.2, 3.4)
    .with_units(units.VOLT),
)
def test_power_rail(test):
    voltage = 5.02  # Remplacer par la lecture de l'instrument
    test.measurements.rail_3v3 = voltage


def main():
    test = htf.Test(
        test_power_rail,
        procedure_id="FCT-001",
        part_number="PCBA-100",
    )
    with TofuPilot(test):
        test.execute(test_start=lambda: input("Scanner le numéro de série : "))


if __name__ == "__main__":
    main()

Les mesures sont des données structurées : nom, valeur, limites, unités. L'intégration TofuPilot se fait avec une seule instruction with. L'opérateur reçoit automatiquement une invite de numéro de série.

NI TestStand

Dans TestStand, vous créez un fichier de séquence (.seq) dans le Sequence Editor. Vous ajoutez une étape « Numeric Limit Test », définissez l'expression de test sur la lecture de votre instrument, configurez Low Limit = 3,2 et High Limit = 3,4. Vous connectez l'étape à un Code Module (VI LabVIEW, DLL ou assembly .NET) qui communique avec l'instrument.

La logique de test réside dans le Code Module. Le séquencement, les limites et le reporting résident dans le fichier .seq. Vous ne pouvez pas voir les deux dans un seul fichier texte, et vous ne pouvez pas faire de diff du fichier .seq dans Git.

Correspondance des concepts

Chaque concept TestStand a un équivalent direct dans OpenHTF.

TestStandOpenHTF
Fichier de séquence (.seq)Script de test Python (.py)
Sequence Editor (interface graphique)N'importe quel éditeur de code (VS Code, PyCharm)
StepFonction de phase
Numeric Limit Testhtf.Measurement("name").in_range(low, high)
String Value Testhtf.Measurement("name").equals("expected")
Pass/Fail Testhtf.Measurement("name").equals(True)
Code Module (DLL, VI)Classe Plug
FileGlobals / StationGlobalsAttributs d'instance du Plug
Process ModelIntégration TofuPilot
Groupes Setup / CleanupPremières/dernières fonctions de phase, ou PhaseGroups
UUT Serial Numbertest.execute(test_start=lambda: input("Scanner : "))

Pilotes d'instruments

TestStand utilise NI VISA et les pilotes IVI. OpenHTF utilise PyVISA, qui communique avec les mêmes instruments via la même couche VISA. Si votre instrument fonctionne avec TestStand, il fonctionne avec PyVISA.

Pilote TestStandÉquivalent Python
NI VISA / IVIPyVISA + backend NI-VISA
NI DAQmxnidaqmx (package Python officiel NI)
NI Switchniswitch (package Python officiel NI)
NI DMMnidmm (package Python officiel NI)
Serial / UARTpyserial
DLL personnaliséectypes ou cffi

NI publie des packages Python officiels pour la plupart de son matériel. Vous ne perdez pas le support d'instruments en passant à Python.

Plug OpenHTF pour un instrument

TestStand encapsule les instruments dans des Code Modules. OpenHTF les encapsule dans des Plugs, qui ont une gestion automatique du cycle de vie (setUp/tearDown).

plugs/multimeter.py
import pyvisa
from openhtf.plugs import BasePlug
import openhtf as htf
from openhtf.util import units
from tofupilot.openhtf import TofuPilot


class MultimeterPlug(BasePlug):
    """Encapsule une connexion multimètre SCPI."""

    def setUp(self):
        rm = pyvisa.ResourceManager()
        self.instr = rm.open_resource("TCPIP::192.168.1.100::INSTR")
        self.instr.timeout = 5000

    def measure_voltage(self, channel=1):
        self.instr.write(f":CONF:VOLT:DC AUTO,(@{channel})")
        self.instr.write(":INIT")
        return float(self.instr.query(":FETCH?"))

    def tearDown(self):
        self.instr.close()


@htf.measures(
    htf.Measurement("rail_3v3")
    .in_range(3.2, 3.4)
    .with_units(units.VOLT),
)
@htf.plug(dmm=MultimeterPlug)
def measure_power_rail(test, dmm):
    test.measurements.rail_3v3 = dmm.measure_voltage(channel=1)


def main():
    test = htf.Test(measure_power_rail, procedure_id="FCT-001", part_number="PCBA-100")
    with TofuPilot(test):
        test.execute(test_start=lambda: input("Scanner le numéro de série : "))


if __name__ == "__main__":
    main()

Partage de données entre les étapes

TestStand utilise FileGlobals et StationGlobals pour passer des données entre les étapes. OpenHTF utilise les attributs d'instance du Plug. Le Plug persiste pendant toute l'exécution du test.

plugs/shared_state.py
from openhtf.plugs import BasePlug
import openhtf as htf
from openhtf.util import units


class SharedState(BasePlug):
    """Remplace les FileGlobals et StationGlobals de TestStand."""

    def setUp(self):
        self.cal_offset = 0.0
        self.firmware_version = ""

    def tearDown(self):
        pass


@htf.plug(state=SharedState)
def calibrate(test, state):
    state.cal_offset = 0.023


@htf.measures(
    htf.Measurement("corrected_voltage")
    .in_range(3.2, 3.4)
    .with_units(units.VOLT),
)
@htf.plug(state=SharedState)
def measure_corrected(test, state):
    raw = 3.323  # Remplacer par la lecture de l'instrument
    test.measurements.corrected_voltage = raw - state.cal_offset

Base de données et données de test

C'est là que les deux frameworks diffèrent le plus.

Journalisation BDD de TestStand

Le logger de base de données intégré de TestStand écrit les résultats dans SQL Server, Oracle ou Access en utilisant un schéma fixe. Les tables principales sont UUT_RESULT, STEP_RESULT et PROP_RESULT, avec des tables supplémentaires pour chaque type de propriété (PROP_NUMERICLIMIT, PROP_STRINGVALUE, etc.).

Interroger ce schéma nécessite des JOINs à plusieurs niveaux :

TableContientSe joint à
UUT_RESULTNuméro de série, pass/fail global, horodatagesNiveau supérieur
STEP_RESULTNom de l'étape, statut de l'étapeUUT_RESULT.ID
PROP_RESULTMétadonnées de la propriétéSTEP_RESULT.ID
PROP_NUMERICLIMITLimites numériques (bas, haut)PROP_RESULT.ID
PROP_NUMERICValeur numérique mesuréePROP_RESULT.ID
PROP_STRINGVALUERésultats de comparaison de chaînesPROP_RESULT.ID

Une simple requête pour obtenir les mesures d'une unité nécessite 5-6 JOINs de tables. Ajouter des métadonnées personnalisées implique de modifier le mapping du schéma de base de données du Process Model, ce qui est fragile et difficile à maintenir entre les versions de TestStand.

TestStand n'inclut pas d'analyses. FPY, Cpk, cartes de contrôle et Pareto des défaillances nécessitent tous des requêtes SQL personnalisées ou un outil tiers comme WATS.

OpenHTF + TofuPilot

OpenHTF n'a pas de journalisation BDD intégrée. TofuPilot s'en charge. Vous ajoutez une ligne with TofuPilot(test): et chaque exécution est stockée avec des mesures structurées, des limites, des unités, des numéros de série et des métadonnées.

BDD TestStandTofuPilot
JOIN de 6 tables pour les résultats d'une unitéRecherche par numéro de série, historique complet
SQL personnalisé pour le FPYTendances FPY, mises à jour en temps réel
Pas de Cpk sans code personnaliséCpk par mesure, automatique
Pas de cartes de contrôleCartes de contrôle avec UCL/LCL
Pas de Pareto des défaillancesPareto des défaillances avec exploration détaillée
Schéma verrouillé sur la conception de NIDonnées structurées, accès API REST
SQL Server/Oracle/AccessCloud ou auto-hébergé
Un site à la fois (sauf si vous construisez la réplication)Multi-site dès le premier jour

Pas de base de données à provisionner, pas de schéma à maintenir, pas de SQL à écrire. TofuPilot suit tout automatiquement. Ouvrez l'onglet Analytics pour voir le FPY, le Cpk et l'analyse des défaillances pour n'importe quelle procédure.

Contrôle de version et Git

Les fichiers de séquence TestStand (.seq) sont binaires. Vous pouvez les stocker dans Git, mais vous ne pouvez pas faire de diff, examiner les changements dans une pull request, ni fusionner des branches.

NI a ajouté un panneau Git dans TestStand 2025 Q2. Il permet de committer, pull et push des fichiers .seq depuis le Sequence Editor. Mais c'est du suivi au niveau fichier, pas du diff au niveau contenu. Vous pouvez voir qu'un fichier a changé, pas ce qui a changé à l'intérieur. NI fournit un « Diff and Merge Utility » séparé qui peut comparer visuellement deux fichiers .seq, mais il ne s'intègre pas dans les workflows de pull request.

Les tests OpenHTF sont de simples fichiers Python. Les workflows Git standard s'appliquent.

CapacitéTestStandOpenHTF
Committer des fichiersOui (panneau Git, 2025 Q2)Oui (n'importe quel client Git)
Diff des changementsNiveau fichier uniquement (Diff Utility pour comparaison visuelle)Ligne par ligne (git diff)
Revue de pull requestImpossible (binaire)Revue de code standard
Branche par fonctionnalitéLes fichiers sont suivis, le contenu ne fusionne pasStandard
Linting CI/CDImpossibleflake8, mypy, ruff
Test automatisé des testsDifficilepytest sur la logique de test
Historique blameNongit blame

À quoi ressemble un diff de test Python

Quand vous changez une limite de mesure dans un test OpenHTF, la pull request montre exactement ce qui a changé :

test_power_rail.py
@htf.measures(
     htf.Measurement("rail_3v3")
-    .in_range(3.2, 3.4)
+    .in_range(3.1, 3.5)
     .with_units(units.VOLT),
 )

Les relecteurs voient l'ancienne limite, la nouvelle limite et le contexte. Dans TestStand, le même changement est invisible à l'intérieur d'un fichier .seq binaire.

Intégration CI/CD

NI a mis à jour le contrat de licence de TestStand en 2025 Q2 pour permettre l'utilisation CI/CD sans coût supplémentaire (si vous avez au moins une licence de développement active). Mais exécuter TestStand en CI nécessite toujours un runner Windows avec TestStand installé, et les fichiers de séquence ne peuvent pas être lintés ni analysés statiquement.

Les tests OpenHTF sont en Python. Ils s'exécutent dans n'importe quel système CI avec un environnement Python.

Capacité CI/CDTestStandOpenHTF
Exécution en CIOui (2025 Q2, runner Windows requis)Oui (tout OS, tout CI)
Licence pour CIIncluse avec 1+ licence dev (2025 Q2)Gratuit
Analyse statiqueImpossible (.seq binaire)flake8, mypy, ruff, pylint
Test unitaire sur la logique de testDifficilepytest
Lint des noms de mesureImpossibleRègles personnalisées
Runner DockerNon (Windows requis)Oui
.github/workflows/test-lint.yml
# Linter et vérifier les types des scripts de test OpenHTF en CI
name: Test Script CI
on: [push, pull_request]
jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - run: pip install ruff mypy openhtf
      - run: ruff check tests/
      - run: mypy tests/

Vous pouvez détecter les problèmes de nommage des mesures, les erreurs d'import et les problèmes de types avant que les scripts de test n'arrivent sur la ligne de production.

Quand utiliser chacun

ScénarioMeilleur choix
Nouveau projet, équipe PythonOpenHTF + TofuPilot
Investissement matériel NI existant, grande entrepriseTestStand (si ça fonctionne, gardez-le)
Stations de test multi-OS (Linux, macOS)OpenHTF (TestStand est Windows uniquement)
Budget limité (startup, petite équipe)OpenHTF + TofuPilot (0 $)
Besoin de FPY, Cpk, analyses prêtes à l'emploiOpenHTF + TofuPilot
Intégration poussée NI PXI/CompactRIOTestStand (écosystème NI plus étroit)
Workflows CI/CD et Git importantsOpenHTF
Instruments non-NI (Keysight, Rigol, R&S)Les deux (tous deux utilisent VISA)

Parcours de migration

Si vous êtes sur TestStand et envisagez une migration, le calendrier typique est de 4-8 semaines par procédure de test. La plupart des équipes font fonctionner les deux systèmes en parallèle pendant la transition.

  1. Installez Python + OpenHTF à côté de TestStand sur une station.
  2. Convertissez une procédure de test (la plus simple). Exécutez les deux versions sur les mêmes DUT.
  3. Validez que les mesures correspondent entre les systèmes.
  4. Passez à la procédure suivante une fois les résultats confirmés.
  5. Décommissionnez TestStand lorsque toutes les procédures sont converties.

Le plus grand risque est de se précipiter. Convertissez une procédure à la fois. Le fonctionnement en parallèle est votre filet de sécurité.

Plus de guides

Mettez ce guide en pratique