Getting Started

Structurer un script de test production

Apprenez à organiser un script de test OpenHTF pour la production, incluant l'ordre des phases, la gestion des plugs, la configuration et le support.

JJulien Buteau
beginner11 min de lecture14 mars 2026

Un script de test de production s'exécute des centaines de fois par jour. Il doit être fiable, maintenable et facile à utiliser pour les opérateurs. Ce guide explique comment structurer un script de test OpenHTF prêt pour la ligne de production, pas seulement pour votre banc de développement.

Anatomie d'un script de test

Chaque script de test de production suit la même structure :

structure.txt
imports
classes de plugs (pilotes d'instruments)
fonctions de phase (étapes de test)
assemblage du test (tout connecter)
main (point d'entrée)

Respectez cet ordre. C'est ce que chaque ingénieur test de votre équipe s'attend à trouver.

Étape 1 : Définir vos plugs

Les plugs gèrent les connexions aux instruments. Un plug par type d'instrument. setUp() ouvre la connexion. tearDown() la ferme. OpenHTF gère le cycle de vie automatiquement.

production/plugs.py
import openhtf as htf
from openhtf.plugs import BasePlug


class PowerSupplyPlug(BasePlug):
    """Contrôle de l'alimentation de banc."""

    def setUp(self):
        self.output_on = False
        # Remplacez : rm = pyvisa.ResourceManager("@py")
        # self.instr = rm.open_resource("TCPIP::192.168.1.101::INSTR")

    def enable(self):
        self.output_on = True
        # Remplacez : self.instr.write(":OUTP ON")

    def disable(self):
        self.output_on = False
        # Remplacez : self.instr.write(":OUTP OFF")

    def tearDown(self):
        self.disable()


class DmmPlug(BasePlug):
    """Multimètre numérique pour les mesures de tension et de courant."""

    def setUp(self):
        self._readings = iter([3.31, 5.01, 0.12])
        # Remplacez : rm = pyvisa.ResourceManager("@py")
        # self.instr = rm.open_resource("TCPIP::192.168.1.100::INSTR")

    def read_voltage(self) -> float:
        return next(self._readings)
        # Remplacez : return float(self.instr.query(":MEAS:VOLT:DC?"))

    def read_current(self) -> float:
        return next(self._readings)
        # Remplacez : return float(self.instr.query(":MEAS:CURR:DC?"))

    def tearDown(self):
        pass
        # Remplacez : self.instr.close()

Règles pour les plugs :

  • Un plug par type d'instrument (pas par instance d'instrument)
  • tearDown() doit toujours laisser l'instrument dans un état sûr (sortie désactivée, connexion fermée)
  • Ne mettez pas les limites de mesure dans les plugs. Les plugs lisent les valeurs brutes. Les phases appliquent les limites.

Étape 2 : Écrire les fonctions de phase

Chaque phase teste une chose logique. Gardez les phases ciblées et indépendantes.

production/phases.py
import openhtf as htf
from openhtf.util import units


@htf.measures(
    htf.Measurement("rail_3v3")
    .in_range(3.2, 3.4)
    .with_units(units.VOLT)
    .doc("Rail d'alimentation 3.3V"),
    htf.Measurement("rail_5v0")
    .in_range(4.8, 5.2)
    .with_units(units.VOLT)
    .doc("Rail d'alimentation 5.0V"),
)
@htf.plug(psu=PowerSupplyPlug, dmm=DmmPlug)
def test_power_rails(test, psu, dmm):
    """Alimenter le DUT et vérifier les rails de tension."""
    psu.enable()
    test.measurements.rail_3v3 = dmm.read_voltage()
    test.measurements.rail_5v0 = dmm.read_voltage()


@htf.measures(
    htf.Measurement("idle_current")
    .in_range(0.05, 0.20)
    .with_units(units.AMPERE)
    .doc("Consommation de courant au repos de la carte"),
)
@htf.plug(dmm=DmmPlug)
def test_current(test, dmm):
    """Mesurer la consommation de courant au repos."""
    test.measurements.idle_current = dmm.read_current()

Règles pour les phases :

  • Un bloc @htf.measures par phase. Déclarez tout ce que la phase va mesurer.
  • Utilisez le décorateur @htf.plug(), pas les annotations de type, pour l'injection des plugs.
  • Les noms de phase doivent décrire ce qu'elles testent : test_power_rails, pas phase_1.
  • Ajoutez .doc() à chaque mesure. Cela apparaît dans TofuPilot et les rapports.

Étape 3 : Ordonner correctement les phases

L'ordre des phases est important en production. Testez d'abord l'élément le plus critique. Si les rails d'alimentation échouent, ne perdez pas de temps à tester la communication.

OrdrePhasePourquoi cet ordre
1test_power_railsSi l'alimentation échoue, tout le reste échouera aussi
2test_currentCourant élevé = court-circuit = arrêter avant d'endommager
3test_communicationVérifier que le firmware est actif avant les tests fonctionnels
4test_functionalComportement au niveau de la carte
5test_calibrationRéglage fin (uniquement si les étapes précédentes réussissent)

Étape 4 : Assembler le test

production/main.py
import openhtf as htf
from tofupilot.openhtf import TofuPilot


def main():
    test = htf.Test(
        test_power_rails,
        test_current,
        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()

procedure_id identifie le type de test. part_number identifie le produit. Les deux apparaissent dans TofuPilot pour le filtrage et les analyses.

Étape 5 : Supporter plusieurs SKU

Lorsque la même station de test teste différents produits, utilisez une fonction factory.

production/multi_sku.py
import openhtf as htf
from tofupilot.openhtf import TofuPilot


SKU_CONFIG = {
    "PCBA-100": {
        "procedure_id": "FCT-001",
        "phases": [test_power_rails, test_current],
    },
    "PCBA-200": {
        "procedure_id": "FCT-002",
        "phases": [test_power_rails, test_current],
    },
}


def create_test(part_number: str) -> htf.Test:
    """Créer un test configuré pour un SKU spécifique."""
    config = SKU_CONFIG[part_number]
    return htf.Test(
        *config["phases"],
        procedure_id=config["procedure_id"],
        part_number=part_number,
    )


def main():
    part_number = input("Scanner le numéro de pièce : ").strip()
    if part_number not in SKU_CONFIG:
        print(f"Numéro de pièce inconnu : {part_number}")
        return

    test = create_test(part_number)
    with TofuPilot(test):
        test.execute(test_start=lambda: input("Scanner le numéro de série : "))

Organisation des fichiers

Pour une station de test mono-produit, un seul fichier suffit. Pour du multi-produit ou des tests complexes, divisez en modules :

project_structure.txt
fct/
├── __init__.py
├── plugs/
│   ├── __init__.py
│   ├── power_supply.py      # PowerSupplyPlug
│   ├── dmm.py                # DmmPlug
│   └── uart.py               # UartPlug
├── phases/
│   ├── __init__.py
│   ├── power.py              # test_power_rails
│   ├── current.py            # test_current
│   └── communication.py      # test_communication
├── config.py                  # Configurations SKU, limites
└── main.py                    # Assemblage du test et point d'entrée

Quand diviser : Si votre fichier de test dépasse 300 lignes, divisez-le. Si vous avez plus de 5 plugs, séparez-les dans un répertoire plugs/. Si vous testez plus de 3 SKU, déplacez la configuration dans son propre fichier.

Erreurs courantes

ErreurProblèmeSolution
Mettre les limites dans les plugsImpossible de réutiliser le plug pour différents produitsGardez les limites dans @htf.measures
Utiliser les annotations de type pour l'injection des plugsdut: DutPlug n'injecte pasUtilisez le décorateur @htf.plug(dut=DutPlug)
Ne pas appeler tearDown() dans les plugsInstruments laissés dans un état inconnuImplémentez toujours tearDown()
Tout tester dans une seule phaseUn échec masque les autresDivisez en phases ciblées
Coder en dur les adresses des instrumentsImpossible de déplacer vers une autre stationUtilisez un fichier de configuration ou des variables d'environnement
Pas de timeout sur les phasesUn test bloqué immobilise la stationAjoutez @htf.PhaseOptions(timeout_s=30)

Plus de guides

Mettez ce guide en pratique