Aller au contenu
Infrastructure as Code medium

ChefSpec : tester ses recettes Chef sans machine

10 min de lecture

Logo Chef

ChefSpec vérifie qu'une recette déclare les bonnes ressources, en mémoire, sans lancer aucune machine. Il simule la convergence de Chef puis vous laisse affirmer « cette recette installe bien nginx », « ce template notifie bien le rechargement du service ». Un cycle complet tourne en une fraction de seconde, ce qui en fait le premier filet de sécurité avant toute convergence sur une vraie VM. Ce guide teste le cookbook webstack du volet précédent. Public visé : lecteur ayant structuré un cookbook, à l'aise avec les ressources Chef.

  • Écrire un test ChefSpec pour une recette avec spec_helper et un SoloRunner.
  • Vérifier qu'une ressource est déclarée avec ses propriétés (mode, contenu).
  • Tester une notification entre ressources sans rien exécuter.
  • Éviter le rapport de couverture déprécié, que Cookstyle rejette.
  • Situer ChefSpec, test unitaire, face à InSpec, test d'intégration.

Aucune VM n'est nécessaire : ChefSpec s'exécute entièrement sur votre poste. Testé avec ChefSpec 9.3.8 (CINC Workstation), cookbook webstack 1.0.0.

Un cookbook mérite deux niveaux de tests, qui répondent à deux questions différentes. ChefSpec répond à « ma recette déclare-t-elle les bonnes ressources, avec les bonnes propriétés et les bonnes notifications ? ». Il compile la recette et joue la convergence en mémoire, sans toucher à un système. InSpec répond à « la machine est-elle réellement dans l'état voulu après convergence ? » et s'exécute sur la vraie VM.

ChefSpecInSpec
NiveauUnitaireIntégration
VérifieCe que la recette déclareL'état réel de la machine
CibleEn mémoire, sur le posteLa VM convergée
VitesseFraction de secondeDizaines de secondes
AttrapeErreur de logique de recetteÉcart entre intention et résultat

Les deux sont complémentaires : ChefSpec avant de converger, pour un retour immédiat sur la logique ; InSpec après knife zero converge, pour prouver le résultat sur une vraie machine. Ce guide couvre le premier.

ChefSpec s'appuie sur RSpec, le framework de test Ruby. On place les tests dans un dossier spec/ à la racine du cookbook. Un fichier spec_helper.rb charge ChefSpec une fois pour tous les tests.

  1. Créer le helper spec/spec_helper.rb :

    require "chefspec"
  2. Écrire le test de la recette install dans spec/unit/recipes/install_spec.rb :

    require "spec_helper"
    describe "webstack::install" do
    let(:chef_run) { ChefSpec::SoloRunner.new(platform: "ubuntu", version: "24.04").converge(described_recipe) }
    it "met a jour le cache apt" do
    expect(chef_run).to update_apt_update("mise a jour du cache")
    end
    it "installe le paquet nginx" do
    expect(chef_run).to install_package("nginx")
    end
    end

Trois éléments portent tout le test. Le SoloRunner simule un nœud : on lui donne une plateforme (ubuntu 24.04) pour que Chef connaisse les attributs par défaut de ce système, puis il converge la recette en mémoire. described_recipe reprend automatiquement le nom du describe (ici webstack::install). Enfin, les matchers comme install_package ou update_apt_update affirment qu'une ressource est déclarée avec la bonne action : le matcher install_package vérifie une ressource package dont l'action est :install.

On exécute les tests avec chef exec rspec, qui utilise le RSpec embarqué dans CINC Workstation. Le drapeau --format documentation affiche chaque test par son intitulé.

Fenêtre de terminal
cd ~/chef-parc/cookbooks/webstack
chef exec rspec --format documentation

La sortie liste les tests réussis :

webstack::install
met a jour le cache apt
installe le paquet nginx
Finished in 0.16 seconds (files took 1.25 seconds to load)
2 examples, 0 failures

Deux vérifications en 0,16 seconde, sans qu'aucune machine n'ait démarré. C'est cette rapidité qui permet de relancer les tests à chaque modification de la recette.

La recette config est plus riche : un répertoire, un template, un fichier et un service. ChefSpec sait vérifier chaque ressource et ses propriétés. On crée spec/unit/recipes/config_spec.rb :

require "spec_helper"
describe "webstack::config" do
let(:chef_run) { ChefSpec::SoloRunner.new(platform: "ubuntu", version: "24.04").converge(described_recipe) }
it "cree le repertoire racine" do
expect(chef_run).to create_directory("/var/www/webstack").with(mode: "0755")
end
it "depose le template de configuration nginx" do
expect(chef_run).to create_template("/etc/nginx/conf.d/webstack.conf")
end
it "cree la page index" do
expect(chef_run).to create_file("/var/www/webstack/index.html").with(content: "webstack\n")
end
it "active et demarre nginx" do
expect(chef_run).to enable_service("nginx")
expect(chef_run).to start_service("nginx")
end
end

La méthode .with(...) est la clé : elle ne vérifie pas seulement que la ressource existe, mais qu'elle porte les bonnes propriétés. Ici on prouve que le répertoire a le mode 0755 et que la page d'accueil contient bien le texte attendu. Un service se teste par deux matchers distincts, enable_service et start_service, car la recette déclare les deux actions.

Le point le plus utile de ChefSpec est de tester ce qu'un simple coup d'oeil au code ne garantit pas : les notifications. La recette recharge nginx uniquement quand le template change. On vérifie ce lien sans exécuter quoi que ce soit, en ajoutant à config_spec.rb :

it "le template notifie le rechargement de nginx" do
resource = chef_run.template("/etc/nginx/conf.d/webstack.conf")
expect(resource).to notify("service[nginx]").to(:reload).delayed
end

On récupère la ressource template, puis on affirme qu'elle notifie service[nginx] avec l'action :reload en mode :delayed. Si quelqu'un remplace un jour :reload par :restart, ou passe la notification en :immediately, ce test échoue immédiatement, avant même de toucher une machine.

Beaucoup de tutoriels vous feront ajouter un rapport de couverture dans spec_helper.rb, avec ChefSpec::Coverage.start! et un at_exit appelant ChefSpec::Coverage.report!. Ne le faites pas : cette fonctionnalité est dépréciée par Chef. Elle fonctionne encore, mais Cookstyle la signale comme une faute :

Chef/Deprecations/ChefSpecCoverageReport: Don't use the deprecated ChefSpec
coverage report functionality in your specs.

La contradiction serait absurde : vous ajouteriez volontairement une ligne que le linter du même écosystème vous demandera de retirer, et qui fera échouer votre CI dès que vous brancherez Cookstyle.

La bonne façon de savoir si un cookbook est bien testé n'est donc pas un pourcentage, mais une relecture : pour chaque ressource déclarée dans vos recettes, existe-t-il une attente correspondante dans les specs ? Sur un cookbook de quelques ressources, la question se tranche en un coup d'œil, et elle vous dit quoi tester plutôt que combien.

Un test n'a de valeur que s'il échoue quand il le doit. Simulez une régression en affirmant, à tort, que la recette installe apache2 :

it "installe apache2" do
expect(chef_run).to install_package("apache2")
end

ChefSpec refuse et explique clairement l'écart :

Failures:
1) webstack::install installe apache2
Failure/Error: expect(chef_run).to install_package("apache2")
expected "package[apache2]" with action :install to be in Chef run.
1 example, 1 failure

C'est exactement ce qu'on attend d'un test : si une modification casse la logique de la recette, ChefSpec le signale en une fraction de seconde, avant la moindre convergence sur une VM. Retirez ensuite ce test volontairement faux.

La convention Chef place les tests unitaires sous spec/unit/recipes/, un fichier *_spec.rb par recette. Cette arborescence est reconnue automatiquement par chef exec rspec, et un outil d'intégration continue la retrouve sans configuration. Gardez un spec_helper.rb unique à la racine de spec/, dont le seul rôle est de charger ChefSpec.

Vérifiez que l'essentiel de ce guide est acquis. Les questions portent uniquement sur ce qui vient d'être expliqué ici.

Contrôle de connaissances

Validez vos connaissances avec ce quiz interactif

6 questions
6 min.
70% requis

Informations

  • Le chronomètre démarre au clic sur Démarrer
  • Questions à choix multiples, vrai/faux et réponses courtes
  • Vous pouvez naviguer entre les questions
  • Les résultats détaillés sont affichés à la fin

Lance le quiz et démarre le chronomètre

  1. ChefSpec teste ce qu'une recette déclare, en mémoire, sans lancer de VM.
  2. Un test tient en trois éléments : un SoloRunner, described_recipe, et des matchers (install_package, create_template, ...).
  3. .with(...) vérifie les propriétés d'une ressource, pas seulement son existence.
  4. Les notifications sont testables unitairement : notify(...).to(:reload).delayed.
  5. Le rapport de couverture est déprécié : Cookstyle le rejette, ne l'activez pas.
  6. ChefSpec est le test unitaire (avant convergence) ; InSpec est le test d'intégration (après knife zero converge sur la vraie VM).

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn