Getting Started

Construire un séquenceur de test

Apprenez à construire un séquenceur de test avec OpenHTF en utilisant l'ordonnancement des phases, la logique de saut, les PhaseGroups, les séquences.

JJulien Buteau
intermediate10 min de lecture14 mars 2026

Un séquenceur de test exécute des phases ordonnées, décide lesquelles ignorer, collecte les entrées opérateur et enregistre le résultat. Ce guide montre comment en construire un avec OpenHTF et enregistrer les résultats structurés dans TofuPilot.

Prérequis

  • Python 3.8+
  • Compte TofuPilot et clé API
  • OpenHTF installé : pip install openhtf tofupilot

Ce que fait un séquenceur de test

Un séquenceur est plus qu'une liste d'étapes de test. Il contrôle :

ResponsabilitéExemple
Ordonnancement des phasesMise sous tension avant les tests fonctionnels
Logique de sautIgnorer la calibration RF si la variante matérielle n'a pas d'antenne
Interaction opérateurDemander le numéro de série, confirmer l'inspection visuelle
Agrégation des résultatsUn seul résultat réussite/échec à partir de toutes les phases
TeardownMise hors tension même quand une phase échoue

OpenHTF correspond directement à ces responsabilités. Chaque phase est une fonction Python. L'objet test les séquence, évalue les mesures et transmet les résultats à TofuPilot.

Étape 1 : Définir les phases

Chaque phase est une fonction décorée. Ajoutez des mesures pour capturer les résultats numériques avec des limites.

sequencer.py
import openhtf as htf
from openhtf.util import units

@htf.measures(
    htf.Measurement('supply_voltage')
    .in_range(minimum=4.75, maximum=5.25)
    .with_units(units.VOLT)
)
def power_on_test(test):
    voltage = read_supply_voltage()
    test.measurements.supply_voltage = voltage

@htf.measures(
    htf.Measurement('loop_back_result').equals(True)
)
def uart_loopback_test(test):
    result = send_uart_loopback()
    test.measurements.loop_back_result = result

@htf.measures(
    htf.Measurement('output_current')
    .in_range(minimum=0.9, maximum=1.1)
    .with_units(units.AMPERE)
)
def load_test(test):
    current = measure_output_current()
    test.measurements.output_current = current

Étape 2 : Utiliser les PhaseGroups pour le setup et le teardown

PhaseGroup garantit que le teardown s'exécute même quand une phase de test échoue. C'est essentiel pour les tests matériels qui maintiennent des relais, alimentations ou montages.

sequencer.py
from openhtf import PhaseGroup

def power_on(test):
    enable_power_supply()
    test.logger.info('Alimentation activée')

def power_off(test):
    # S'exécute même si functional_tests échoue
    disable_power_supply()
    test.logger.info('Alimentation désactivée')

# Construire le groupe : setup -> main -> teardown
test_group = PhaseGroup(
    setup=[power_on],
    main=[power_on_test, uart_loopback_test, load_test],
    teardown=[power_off],
)

Les phases teardown s'exécutent quel que soit le résultat. Les échecs de setup ignorent main mais exécutent quand même teardown.

Étape 3 : Injecter des plugs pour l'accès matériel

Les plugs encapsulent la communication avec les instruments ou les montages. Injectez-les avec @htf.plug.

plugs/supply.py
import openhtf as htf

class PowerSupplyPlug(htf.plugs.BasePlug):
    def setUp(self):
        self._conn = open_supply_connection()

    def read_voltage(self):
        return self._conn.query('MEAS:VOLT?')

    def tearDown(self):
        self._conn.close()
sequencer.py
from openhtf.util import units
from plugs.supply import PowerSupplyPlug

@htf.plug(supply=PowerSupplyPlug)
@htf.measures(
    htf.Measurement('supply_voltage')
    .in_range(minimum=4.75, maximum=5.25)
    .with_units(units.VOLT)
)
def power_on_test(test, supply):
    test.measurements.supply_voltage = supply.read_voltage()

Notez la forme @htf.plug(name=PlugClass). N'utilisez pas les annotations de type pour l'injection de plugs.

Étape 4 : Ajouter la logique de saut pour l'exécution conditionnelle

Retournez htf.PhaseResult.SKIP pour ignorer une phase selon la variante du DUT.

sequencer.py
import openhtf as htf

@htf.measures(
    htf.Measurement('rf_output_power')
    .in_range(minimum=18.0, maximum=22.0)
)
def rf_calibration_test(test):
    if not has_rf_module():
        test.logger.info('Aucun module RF détecté, saut de la calibration RF')
        return htf.PhaseResult.SKIP
    power = measure_rf_output()
    test.measurements.rf_output_power = power

Gardez les décisions de saut basées sur les données plutôt que codées en dur. Lisez la configuration matérielle dans une phase précédente ou depuis un fichier de configuration.

Étape 5 : Construire des séquences multi-SKU

Les différentes variantes de produits nécessitent des ensembles de phases différents. Composez les séquences dynamiquement à partir d'une table de SKU.

sequencer.py
from openhtf import PhaseGroup

# Phases de base exécutées sur chaque SKU
BASE_PHASES = [power_on_test, uart_loopback_test]

# Phases supplémentaires par SKU
SKU_PHASES = {
    'MODEL_A': [load_test],
    'MODEL_B': [load_test, rf_calibration_test],
    'MODEL_C': [],  # base uniquement
}

def build_sequence(sku: str):
    extra = SKU_PHASES.get(sku, [])
    return PhaseGroup(
        setup=[power_on],
        main=BASE_PHASES + extra,
        teardown=[power_off],
    )

Étape 6 : Enregistrer les résultats dans TofuPilot

Encapsulez l'exécution du test avec TofuPilot. Il capture chaque phase, mesure et résultat.

sequencer.py
import openhtf as htf
from tofupilot.openhtf import TofuPilot

def main(sku: str = 'MODEL_A'):
    sequence = build_sequence(sku)

    test = htf.Test(
        sequence,
        test_name=f'Functional Test {sku}',
    )

    with TofuPilot(test):
        test.execute(test_start=lambda: input('Entrez le numéro de série : '))

if __name__ == '__main__':
    import sys
    sku = sys.argv[1] if len(sys.argv) > 1 else 'MODEL_A'
    main(sku)

Référence des résultats de phase

Valeur de retourEffet
htf.PhaseResult.CONTINUEPhase réussie, continuer la séquence
htf.PhaseResult.SKIPPhase ignorée, continuer la séquence
htf.PhaseResult.STOPPhase échouée, arrêter la séquence (exécuter le teardown)
Mesure hors limitesLa phase échoue automatiquement
Exception non géréeLa phase échoue, le teardown s'exécute

Exemple complet

sequencer.py
import sys
import openhtf as htf
from openhtf import PhaseGroup
from openhtf.util import units
from tofupilot.openhtf import TofuPilot

# -- Phases --

@htf.measures(
    htf.Measurement('supply_voltage')
    .in_range(minimum=4.75, maximum=5.25)
    .with_units(units.VOLT)
)
def power_on_test(test):
    test.measurements.supply_voltage = read_supply_voltage()

@htf.measures(
    htf.Measurement('uart_loopback').equals(True)
)
def uart_loopback_test(test):
    test.measurements.uart_loopback = send_uart_loopback()

@htf.measures(
    htf.Measurement('output_current')
    .in_range(minimum=0.9, maximum=1.1)
    .with_units(units.AMPERE)
)
def load_test(test):
    test.measurements.output_current = measure_output_current()

@htf.measures(
    htf.Measurement('rf_output_power')
    .in_range(minimum=18.0, maximum=22.0)
)
def rf_calibration_test(test):
    if not has_rf_module():
        return htf.PhaseResult.SKIP
    test.measurements.rf_output_power = measure_rf_output()

def power_on(test):
    enable_power_supply()

def power_off(test):
    disable_power_supply()

# -- Construction de la séquence --

BASE_PHASES = [power_on_test, uart_loopback_test]
SKU_PHASES = {
    'MODEL_A': [load_test],
    'MODEL_B': [load_test, rf_calibration_test],
}

def build_sequence(sku):
    return PhaseGroup(
        setup=[power_on],
        main=BASE_PHASES + SKU_PHASES.get(sku, []),
        teardown=[power_off],
    )

# -- Point d'entrée --

def main():
    sku = sys.argv[1] if len(sys.argv) > 1 else 'MODEL_A'
    test = htf.Test(build_sequence(sku), test_name=f'Functional Test {sku}')
    with TofuPilot(test):
        test.execute(test_start=lambda: input('Entrez le numéro de série : '))

if __name__ == '__main__':
    main()

Plus de guides

Mettez ce guide en pratique