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.
  • Mesurer la couverture des ressources d'un cookbook.
  • 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.

ChefSpec sait dire quelles ressources du cookbook ne sont couvertes par aucun test. On active le rapport dans spec_helper.rb :

require "chefspec"
RSpec.configure do |config|
config.before(:suite) { ChefSpec::Coverage.start! }
end
at_exit { ChefSpec::Coverage.report! }

Au prochain chef exec rspec, un rapport s'ajoute à la fin :

8 examples, 0 failures
ChefSpec Coverage report generated...
Total Resources: 6
Touched Resources: 6
Touch Coverage: 100.0%

Les 6 ressources du cookbook sont toutes touchées par au moins un test. Quand la couverture n'est pas complète, ChefSpec liste les ressources non testées avec leur fichier et leur ligne, ce qui pointe exactement où écrire le test manquant.

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/ pour charger ChefSpec et activer la couverture.

  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 révèle les ressources oubliées par les tests.
  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