Demandez comment stocker les résultats de test en production et la réponse la plus fréquente dans les forums sera qu'il n'existe pas de norme du secteur. Cette réponse est à moitié vraie. Il n'existe pas de norme unique couvrant le test fonctionnel au niveau carte comme STDF couvre le test semi-conducteur. Mais il existe des approches bien comprises, chacune avec un point de rupture connu.
Ce guide en couvre quatre, dans l'ordre où les équipes les rencontrent, et indique clairement où chacune cesse de fonctionner.
Ce qu'il faut réellement stocker
Avant de choisir un format, clarifiez les entités. Presque toutes les équipes convergent vers les mêmes cinq :
| Entité | Définition |
|---|---|
| Unité | Un objet physique avec un numéro de série |
| Run | Une exécution d'une procédure de test sur une unité |
| Étape | Une phase d'un run, avec son propre pass/fail |
| Mesure | Une valeur nommée avec unité et limites |
| Procédure | La définition versionnée de ce qui a été testé |
Les deux oubliées au début et coûteuses ensuite : la version de procédure et les relations de sous-ensembles. Si vous ne pouvez pas répondre à « quelle version du test a produit ce résultat » ou « quelle carte est entrée dans cet assemblage final », vous reconstruirez votre schéma.
Approche 1 : fichiers plats
CSV ou JSON écrits sur un partage réseau, un fichier par run.
import csvfrom datetime import datetime, timezonedef log_run(serial, procedure, measurements, passed): stamp = datetime.now(timezone.utc).isoformat() path = f"//test-share/results/{serial}_{stamp}.csv" with open(path, "w", newline="") as f: writer = csv.writer(f) writer.writerow(["serial", "procedure", "measurement", "value", "unit", "low", "high", "passed"]) for m in measurements: writer.writerow([ serial, procedure, m["name"], m["value"], m["unit"], m["low"], m["high"], m["passed"], ])Fonctionne jusqu'à : la première question transversale. Le rendement du mois dernier implique d'analyser des milliers de fichiers. Un changement de schéma rend les anciens fichiers incohérents. Deux stations qui écrivent simultanément finissent par corrompre quelque chose. Personne ne retrouve le fichier d'un numéro de série sans une convention de nommage qui devient elle-même un schéma.
Plafond réaliste : une station, faible volume, ou un montage vraiment temporaire.
Approche 2 : un schéma SQL maison
L'étape suivante naturelle, et la bonne réponse pour beaucoup d'équipes.
CREATE TABLE unit ( id BIGSERIAL PRIMARY KEY, serial_number TEXT NOT NULL UNIQUE, part_number TEXT NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT now());CREATE TABLE run ( id BIGSERIAL PRIMARY KEY, unit_id BIGINT NOT NULL REFERENCES unit(id), procedure_id TEXT NOT NULL, procedure_version TEXT NOT NULL, station_id TEXT NOT NULL, started_at TIMESTAMPTZ NOT NULL, duration_s NUMERIC NOT NULL, passed BOOLEAN NOT NULL);CREATE TABLE measurement ( id BIGSERIAL PRIMARY KEY, run_id BIGINT NOT NULL REFERENCES run(id) ON DELETE CASCADE, step_name TEXT NOT NULL, name TEXT NOT NULL, value DOUBLE PRECISION, unit TEXT, lower_limit DOUBLE PRECISION, upper_limit DOUBLE PRECISION, passed BOOLEAN NOT NULL);CREATE INDEX ON run (procedure_id, started_at DESC);CREATE INDEX ON measurement (run_id);CREATE INDEX ON measurement (name, run_id);Le rendement au premier passage devient alors une requête plutôt qu'un script :
SELECT date_trunc('day', first_run.started_at) AS day, count(*) FILTER (WHERE first_run.passed) * 100.0 / count(*) AS fpy_percent, count(*) AS units_testedFROM ( SELECT DISTINCT ON (unit_id) unit_id, started_at, passed FROM run WHERE procedure_id = 'FCT-001' ORDER BY unit_id, started_at ASC) AS first_runGROUP BY dayORDER BY day DESC;Notez le DISTINCT ON et le tri ascendant. Le rendement au premier passage désigne le premier run par unité, pas n'importe quel run réussi. Se tromper là-dessus est le bug de reporting le plus courant des systèmes maison, et il gonfle toujours le chiffre.
Fonctionne jusqu'à : ce que le retard d'analyses croisse plus vite que vous ne l'absorbez. Cpk, cartes de contrôle avec limites correctes, Pareto des modes de défaillance, détection de dérive, comparaison par station et rapports opérateurs sont chacun un petit projet. Puis quelqu'un demande les mêmes chiffres sur trois sites à connectivité intermittente.
Plafond réaliste : viable longtemps si quelqu'un en assume la charge. Le coût est de l'ingénierie continue, pas de la licence.
Approche 3 : normes du secteur
Deux normes comptent, aucune ne couvre tout.
STDF (Standard Test Data Format) est la norme semi-conducteur de Teradyne, largement supportée par les ATE. Binaire, compacte, elle suppose wafers, dies et bins. Si vous testez du silicium, utilisez-la. Si vous testez des cartes assemblées, son modèle ne convient pas et le forcer coûte plus qu'il ne rapporte.
ATML (Automatic Test Markup Language) est la famille IEEE 1671, en XML, conçue pour la description et l'échange de résultats, surtout en aéronautique et défense. Complète et verbeuse. Surtout utile quand un contrat l'exige.
Il y a aussi WSJF et WSXF, spécifiques à WATS plutôt que normes ouvertes, à connaître surtout parce que migrer depuis WATS implique de les traiter.
Résumé honnête : pour le test fonctionnel au niveau carte, aucune norme n'a atteint l'adoption de STDF en semi-conducteur. La plupart des équipes finissent avec un schéma propriétaire, le leur ou celui d'un éditeur.
Approche 4 : une plateforme de données de test
Utiliser un système conçu pour ça, où le schéma et les analyses viennent avec.
import openhtf as htffrom openhtf.util import unitsfrom tofupilot.openhtf import upload@htf.measures( htf.Measurement("rail_3v3") .in_range(3.2, 3.4) .with_units(units.VOLT), htf.Measurement("current_draw") .in_range(0.1, 0.5) .with_units(units.AMPERE),)def power_rail_test(test): test.measurements.rail_3v3 = 3.31 test.measurements.current_draw = 0.24def main(): test = htf.Test( power_rail_test, procedure_id="FCT-001", part_number="PCBA-200", ) test.add_output_callbacks(upload()) test.execute(test_start=lambda: input("Scanner le numéro de série : "))if __name__ == "__main__": main()La définition de mesure porte le nom, les limites et les unités : le stockage et l'évaluation pass/fail découlent du code de test au lieu d'être maintenus séparément. Rendement, Cpk, cartes de contrôle et Pareto sont calculés plutôt qu'interrogés.
Les options de cette catégorie incluent WATS, l'acteur historique, et TofuPilot. Les deux gèrent les entités ci-dessus. Ils diffèrent surtout par le modèle de déploiement, la forme de la licence, et le fait que le code de test vive ou non dans votre dépôt Git.
Fonctionne jusqu'à : un besoin réellement hors du modèle du produit. À ce moment vous revenez à l'approche 2, idéalement avec l'export de l'éditeur.
Choisir
| Situation | Approche |
|---|---|
| Une station, volume prototype | Fichiers plats, mais préparez la sortie |
| Plusieurs stations, un ingénieur pour en assumer la charge | Schéma SQL |
| Test semi-conducteur | STDF |
| Format imposé par contrat | ATML ou ce que le contrat exige |
| Test au niveau carte, analyses souhaitées sans les construire | Plateforme de données de test |
Les erreurs les plus coûteuses
Ne pas versionner la procédure. Quand les limites changent, les anciens résultats deviennent ininterprétables si vous n'avez pas enregistré quelle version les a produits.
Stocker le pass/fail sans la valeur. Un booléen dit qu'une unité a échoué. La valeur mesurée dit qu'elle dérivait depuis trois semaines.
Ignorer les sous-ensembles. La traçabilité du numéro de série de l'assemblage final jusqu'aux cartes se rajoute à grands frais. Modélisez-la dès le premier jour même sans l'utiliser.
Calculer le rendement sur n'importe quel run réussi. Le rendement au premier passage, c'est le premier run par unité. Compter les retests le gonfle, parfois fortement.
Aucune discipline de fuseau horaire. Stockez en UTC. Sinon trois sites dans trois fuseaux ne s'accorderont pas sur le jour d'une défaillance.