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é | OpenHTF | NI TestStand |
|---|---|---|
| Langage | Python | LabVIEW, C, .NET, Python |
| Licence | Apache 2.0 (gratuit) | 4 310 $/poste/an |
| Plateforme | Linux, macOS, Windows | Windows uniquement |
| Éditeur de test | N'importe quel éditeur de code | Sequence Editor propriétaire |
| Contrôle de version | Git (fichiers .py simples) | Difficile (fichiers .seq binaires) |
| Mesures structurées | Intégrées (nom, valeur, limites, unités) | Intégrées (Numeric Limit, String Value) |
| Saisie numéro de série | Invite intégrée | Intégrée (Process Model) |
| DUT parallèle | Limité | Natif |
| Pilotes instruments | PyVISA, pyserial, nidaqmx | NI VISA, IVI, pilotes NI |
| Journalisation BDD | TofuPilot (1 ligne) | Intégrée (schéma complexe) |
| Analyses (FPY, Cpk) | TofuPilot (automatique) | Requêtes personnalisées ou outil tiers |
| Intégration CI/CD | Native (Python) | Limitée (ajoutée 2025 Q2) |
| Génération de rapports | TofuPilot (automatique) | XML/HTML intégrés |
| Communauté | Petite (~640 étoiles GitHub) | Grande (forums NI, cours de formation) |
| Courbe d'apprentissage | Moyenne | Élevée |
Analyse des coûts
| Métrique | OpenHTF + TofuPilot | NI TestStand |
|---|---|---|
| 5 postes, 1 an | 0 $ (TofuPilot Lab est gratuit) | 21 550 $ |
| 20 postes, 1 an | 0 $ | 86 200 $ |
| Déploiement runtime | Gratuit | Licences runtime supplémentaires |
| Formation | Auto-formation (docs openhtf.com) | Cours NI (2 000 $+) |
| Dépendance fournisseur | Aucune | É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
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.
| TestStand | OpenHTF |
|---|---|
| Fichier de séquence (.seq) | Script de test Python (.py) |
| Sequence Editor (interface graphique) | N'importe quel éditeur de code (VS Code, PyCharm) |
| Step | Fonction de phase |
| Numeric Limit Test | htf.Measurement("name").in_range(low, high) |
| String Value Test | htf.Measurement("name").equals("expected") |
| Pass/Fail Test | htf.Measurement("name").equals(True) |
| Code Module (DLL, VI) | Classe Plug |
| FileGlobals / StationGlobals | Attributs d'instance du Plug |
| Process Model | Intégration TofuPilot |
| Groupes Setup / Cleanup | Premières/dernières fonctions de phase, ou PhaseGroups |
| UUT Serial Number | test.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 / IVI | PyVISA + backend NI-VISA |
| NI DAQmx | nidaqmx (package Python officiel NI) |
| NI Switch | niswitch (package Python officiel NI) |
| NI DMM | nidmm (package Python officiel NI) |
| Serial / UART | pyserial |
| DLL personnalisée | ctypes 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).
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.
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_offsetBase 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 :
| Table | Contient | Se joint à |
|---|---|---|
UUT_RESULT | Numéro de série, pass/fail global, horodatages | Niveau supérieur |
STEP_RESULT | Nom de l'étape, statut de l'étape | UUT_RESULT.ID |
PROP_RESULT | Métadonnées de la propriété | STEP_RESULT.ID |
PROP_NUMERICLIMIT | Limites numériques (bas, haut) | PROP_RESULT.ID |
PROP_NUMERIC | Valeur numérique mesurée | PROP_RESULT.ID |
PROP_STRINGVALUE | Résultats de comparaison de chaînes | PROP_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 TestStand | TofuPilot |
|---|---|
| JOIN de 6 tables pour les résultats d'une unité | Recherche par numéro de série, historique complet |
| SQL personnalisé pour le FPY | Tendances FPY, mises à jour en temps réel |
| Pas de Cpk sans code personnalisé | Cpk par mesure, automatique |
| Pas de cartes de contrôle | Cartes de contrôle avec UCL/LCL |
| Pas de Pareto des défaillances | Pareto des défaillances avec exploration détaillée |
| Schéma verrouillé sur la conception de NI | Données structurées, accès API REST |
| SQL Server/Oracle/Access | Cloud 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é | TestStand | OpenHTF |
|---|---|---|
| Committer des fichiers | Oui (panneau Git, 2025 Q2) | Oui (n'importe quel client Git) |
| Diff des changements | Niveau fichier uniquement (Diff Utility pour comparaison visuelle) | Ligne par ligne (git diff) |
| Revue de pull request | Impossible (binaire) | Revue de code standard |
| Branche par fonctionnalité | Les fichiers sont suivis, le contenu ne fusionne pas | Standard |
| Linting CI/CD | Impossible | flake8, mypy, ruff |
| Test automatisé des tests | Difficile | pytest sur la logique de test |
| Historique blame | Non | git 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é :
@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/CD | TestStand | OpenHTF |
|---|---|---|
| Exécution en CI | Oui (2025 Q2, runner Windows requis) | Oui (tout OS, tout CI) |
| Licence pour CI | Incluse avec 1+ licence dev (2025 Q2) | Gratuit |
| Analyse statique | Impossible (.seq binaire) | flake8, mypy, ruff, pylint |
| Test unitaire sur la logique de test | Difficile | pytest |
| Lint des noms de mesure | Impossible | Règles personnalisées |
| Runner Docker | Non (Windows requis) | Oui |
# 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énario | Meilleur choix |
|---|---|
| Nouveau projet, équipe Python | OpenHTF + TofuPilot |
| Investissement matériel NI existant, grande entreprise | TestStand (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'emploi | OpenHTF + TofuPilot |
| Intégration poussée NI PXI/CompactRIO | TestStand (écosystème NI plus étroit) |
| Workflows CI/CD et Git importants | OpenHTF |
| 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.
- Installez Python + OpenHTF à côté de TestStand sur une station.
- Convertissez une procédure de test (la plus simple). Exécutez les deux versions sur les mêmes DUT.
- Validez que les mesures correspondent entre les systèmes.
- Passez à la procédure suivante une fois les résultats confirmés.
- 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é.