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 phases | Mise sous tension avant les tests fonctionnels |
| Logique de saut | Ignorer la calibration RF si la variante matérielle n'a pas d'antenne |
| Interaction opérateur | Demander le numéro de série, confirmer l'inspection visuelle |
| Agrégation des résultats | Un seul résultat réussite/échec à partir de toutes les phases |
| Teardown | Mise 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.
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.
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.
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()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.
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 = powerGardez 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.
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.
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 retour | Effet |
|---|---|
htf.PhaseResult.CONTINUE | Phase réussie, continuer la séquence |
htf.PhaseResult.SKIP | Phase ignorée, continuer la séquence |
htf.PhaseResult.STOP | Phase échouée, arrêter la séquence (exécuter le teardown) |
| Mesure hors limites | La phase échoue automatiquement |
| Exception non gérée | La phase échoue, le teardown s'exécute |
Exemple complet
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()