
verify.yml Ansible suffit pour 80 % des cas, mais quand vos assertions deviennent complexes (logique conditionnelle, agrégation, comparaison cross-host), testinfra offre une API Python beaucoup plus expressive. Cette page explique quand basculer vers testinfra, comment le configurer comme verifier Molecule, et comment écrire des tests Python idiomatiques.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Quand préférer testinfra à verify.yml.
- Configurer
verifier: testinfradansmolecule.yml. - L’API testinfra :
host.package,host.service,host.socket,host.file,host.run. - Écrire des tests pytest lancés par Molecule.
- Combiner verify.yml et testinfra dans le même rôle.
Quand utiliser testinfra ?
Section intitulée « Quand utiliser testinfra ? »| Besoin | Outil recommandé |
|---|---|
| « Le service nginx est-il running ? » | verify.yml (1 assertion) |
| « Le port 80 est-il ouvert ? » | verify.yml |
| « La conf nginx contient-elle 5 directives spécifiques avec des conditions ? » | testinfra (Python plus lisible) |
| « Vérifier la cohérence cross-host (master/replica) » | testinfra |
| « Assertion conditionnelle complexe (si X alors Y sinon Z) » | testinfra |
| « Test d’idempotence custom » | testinfra |
Règle : si votre verify.yml dépasse 50 lignes ou nécessite des block: imbriqués → bascule vers testinfra.
Configurer testinfra comme verifier
Section intitulée « Configurer testinfra comme verifier »verifier: name: testinfra options: v: trueUne seule ligne suffit pour basculer du verifier ansible (verify.yml) à testinfra (Python). options.v: true active le mode verbose pour voir les tests détaillés.
Structure des tests
Section intitulée « Structure des tests »Molecule cherche les tests dans molecule/<scenario>/tests/test_*.py.
molecule/└── default/ ├── molecule.yml ├── converge.yml └── tests/ ← convention testinfra ├── test_webserver.py └── test_security.pyL’API testinfra
Section intitulée « L’API testinfra »def test_nginx_is_installed(host): """Vérifie que nginx est installé.""" assert host.package("nginx").is_installed
def test_nginx_is_running_and_enabled(host): """Service nginx démarré et activé au boot.""" nginx = host.service("nginx") assert nginx.is_running assert nginx.is_enabled
def test_nginx_listens_on_8080(host): """nginx écoute sur 8080.""" assert host.socket("tcp://0.0.0.0:8080").is_listening
def test_nginx_config_valid(host): """nginx -t renvoie OK.""" cmd = host.run("nginx -t") assert cmd.rc == 0
def test_index_html_content(host): """Page d'accueil contient le custom message.""" f = host.file("/usr/share/nginx/html/index.html") assert f.exists assert f.user == "root" assert "testinfra-tested" in f.content_stringFixture host automatique — Molecule l’injecte. Pas besoin de configurer pytest.
API testinfra — les modules essentiels
Section intitulée « API testinfra — les modules essentiels »| Module | Exemple | Cas d’usage |
|---|---|---|
host.package(name) | .is_installed, .version | Vérifier paquet |
host.service(name) | .is_running, .is_enabled | Vérifier service systemd |
host.socket(uri) | .is_listening | Vérifier port en écoute |
host.file(path) | .exists, .mode, .user, .content_string | Vérifier fichier |
host.run(cmd) | .rc, .stdout, .stderr | Exécuter commande arbitraire |
host.user(name) | .exists, .uid, .gid, .shell | Vérifier utilisateur Linux |
host.process | .filter(comm='nginx') | Vérifier process en cours |
host.iptables | rules | Inspecter iptables |
host.systemd | unit-level checks | systemd avancé |
Comparaison verify.yml vs testinfra
Section intitulée « Comparaison verify.yml vs testinfra »Cas simple — verify.yml gagne (concision)
Section intitulée « Cas simple — verify.yml gagne (concision) »- name: Vérifier nginx running ansible.builtin.systemd: name: nginx state: started check_mode: true register: r failed_when: r is changed# testinfra (équivalent)def test_nginx_running(host): assert host.service("nginx").is_runningtestinfra plus court, plus lisible. Pour des cas simples, c’est juste plus propre.
Cas complexe — testinfra gagne (expressivité)
Section intitulée « Cas complexe — testinfra gagne (expressivité) »def test_nginx_config_consistency(host): """Test complexe : 5 directives à vérifier avec logique.""" config = host.file("/etc/nginx/nginx.conf").content_string
# Worker processes auto si CPU > 4 cœurs cpus = int(host.run("nproc").stdout.strip()) if cpus > 4: assert "worker_processes auto" in config else: assert "worker_processes 4" in config
# gzip activé en HTTPS, désactivé en HTTP if "ssl on" in config: assert "gzip on" in config else: # En HTTP-only, gzip désactivé pour les attaques BREACH assert "gzip on" not in config or "gzip_disable" in config
# SSL TLS 1.2 minimum if "listen 443" in config: assert "ssl_protocols TLSv1.2 TLSv1.3" in config assert "TLSv1 " not in config assert "TLSv1.1" not in configFaire ce genre de test en verify.yml serait inhumain — Python brille ici.
Utiliser testinfra hors Molecule
Section intitulée « Utiliser testinfra hors Molecule »testinfra n’est pas couplé à Molecule. Sur un projet playbook-based (sans rôle, ou sur des machines déjà provisionnées), on peut le lancer directement avec pytest. Deux backends utiles selon le contexte.
Backend local:// — tester le control node lui-même
Section intitulée « Backend local:// — tester le control node lui-même »Pour vérifier que la machine d’exécution (poste dev, CI runner, bastion) respecte une baseline :
def test_python3_is_installed(host): assert host.package("python3").is_installed
def test_passwd_permissions(host): f = host.file("/etc/passwd") assert f.user == "root" assert f.mode == 0o644pytest -v --hosts=local:// tests/testinfra exécute les commandes localement — utile pour valider un poste, un container CI, un environnement de build.
Backend ansible:// — tester via inventaire Ansible
Section intitulée « Backend ansible:// — tester via inventaire Ansible »Pour tester des machines distantes déjà déclarées dans un inventaire Ansible (production, staging, lab Vagrant) :
[webservers]web1.lab ansible_user=ansibleweb2.lab ansible_user=ansibledef test_nginx_listens_on_80(host): assert host.socket("tcp://0.0.0.0:80").is_listening
def test_inventory_facts_available(host): facts = host.ansible("setup")["ansible_facts"] assert facts["ansible_distribution"] in ("Ubuntu", "Rocky", "AlmaLinux")pytest -v --hosts="ansible://webservers" \ --ansible-inventory=inventory.ini \ --force-ansible tests/host.ansible("setup") récupère les facts comme un play Ansible — pratique pour brancher la logique de test selon la distribution sans recoder la détection.
Détection de drift par test
Section intitulée « Détection de drift par test »Un cas avancé : vérifier que la version installée correspond à la source de vérité (variables d’inventaire). Si un admin patche manuellement, le test échoue et signale la dérive.
def test_nginx_version_matches_inventory(host): facts = host.ansible.get_variables() expected = facts.get("nginx_version") assert expected, "nginx_version manquant dans l'inventaire"
installed = host.package("nginx").version assert installed.startswith(expected), ( f"Drift détecté : installé={installed} attendu={expected}" )À brancher dans une CI nocturne — toute dérive entre le code Ansible et les machines réelles est détectée et notifiée. Voir aussi Baselines et drift.
Pratiquer dans le lab
Section intitulée « Pratiquer dans le lab »Cette page a un lab d’accompagnement : labs/tests/testinfra/ dans
stephrobert/ansible-training.
Le lab configure testinfra comme verifier Molecule avec 6 tests Python sur le rôle webserver. 5 tests structure pour valider la config (verifier testinfra, dossier tests/, fixture host, ≥4 fonctions test).
cd ~/Projets/ansible-training/labs/tests/testinfra/
cat molecule/default/tests/test_webserver.pypytest -v challenge/tests/ # 5 tests structurePièges courants
Section intitulée « Pièges courants »| Symptôme | Cause | Fix |
|---|---|---|
host fixture not found | Pas dans tests/ Molecule ou pytest standalone | Lancer via molecule verify, pas pytest direct |
host.run retourne rc != 0 | Permissions sudo manquantes | Tests testinfra héritent de become Molecule |
host.file().content_string plante | Fichier binaire | Utiliser .content (bytes) au lieu de .content_string |
| Tests passent sur RHEL, échouent sur Debian | Path différent | Charger host.system_info.distribution puis brancher |
host.socket('tcp://0.0.0.0:80') faux négatif | nginx écoute sur IPv6 uniquement | Tester aussi tcp://:::80 |
À retenir
Section intitulée « À retenir »verify.yml= défaut 2026. testinfra complément pour assertions complexes.verifier: testinfra+options.v: truedansmolecule.yml.- API :
host.package,host.service,host.socket,host.file,host.run. - Fixture
hostinjectée automatiquement par Molecule. - Combinable :
verify.ymlpour le simple + testinfra pour le complexe dans le même scenario.