<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Testing on Guillaume Delré</title><link>https://guillaumedelre.github.io/fr/tags/testing/</link><description>Recent content in Testing on Guillaume Delré</description><generator>Hugo</generator><language>fr-FR</language><lastBuildDate>Sat, 16 May 2026 10:00:00 +0000</lastBuildDate><atom:link href="https://guillaumedelre.github.io/fr/tags/testing/index.xml" rel="self" type="application/rss+xml"/><item><title>Quinze minutes avant le premier test</title><link>https://guillaumedelre.github.io/fr/2026/05/16/quinze-minutes-avant-le-premier-test/</link><pubDate>Sat, 16 May 2026 10:00:00 +0000</pubDate><guid>https://guillaumedelre.github.io/fr/2026/05/16/quinze-minutes-avant-le-premier-test/</guid><description>Part 5 of 8 in &amp;quot;Symfony vers le Cloud : Douze Facteurs, Treize Services&amp;quot;: Comment un pipeline CI qui provisionnait une VM Azure par run — sans RabbitMQ, MinIO ni Varnish — est devenu un pipeline qui assemble l&amp;#39;environnement de production depuis les mêmes images qu&amp;#39;il livre.</description><category>symfony-to-the-cloud</category><content:encoded><![CDATA[<p>Le pipeline avait deux stages qui n&rsquo;avaient rien à voir avec le code : <code>provision</code> et <code>deprovision</code>. Entre eux, dans l&rsquo;ordre : <code>phpunit</code>, <code>phpmetrics</code>, <code>behat</code>.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">stages</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#ae81ff">build</span>
</span></span><span style="display:flex;"><span>  - <span style="color:#ae81ff">provision</span>
</span></span><span style="display:flex;"><span>  - <span style="color:#ae81ff">phpunit</span>
</span></span><span style="display:flex;"><span>  - <span style="color:#ae81ff">phpmetrics</span>
</span></span><span style="display:flex;"><span>  - <span style="color:#ae81ff">behat</span>
</span></span><span style="display:flex;"><span>  - <span style="color:#ae81ff">deprovision</span>
</span></span><span style="display:flex;"><span>  - <span style="color:#ae81ff">deploy</span>
</span></span></code></pre></div><p>Avant que la première assertion s&rsquo;exécute, quinze minutes s&rsquo;étaient écoulées. Terraform avait cloné un dépôt d&rsquo;infrastructure, s&rsquo;était authentifié sur Azure, avait appliqué une configuration de VM. Ansible s&rsquo;était connecté à la nouvelle VM, avait installé PHP, configuré l&rsquo;application, câblé une base de données et une instance Redis. Ensuite les tests tournaient. Ensuite Terraform détruisait ce qu&rsquo;Ansible avait construit.</p>
<p>Pour chaque pipeline. Depuis chaque branche. Pour chaque pull request, de l&rsquo;ouverture au merge.</p>
<h2 id="ce-que-ces-quinze-minutes-ne-contenaient-pas">Ce que ces quinze minutes ne contenaient pas</h2>
<p>Le stage <code>provision</code> mettait en place deux services : PostgreSQL et Redis. Trois services dont l&rsquo;application dépendait en production étaient absents : RabbitMQ, MinIO et Varnish.</p>
<p>RabbitMQ traitait tout le travail asynchrone — 56 consumers sur 14 microservices. MinIO gérait le stockage de médias. Varnish était devant le cache HTTP. En CI, aucun d&rsquo;eux n&rsquo;existait. Les tests qui couvraient les files de messages ou le stockage de fichiers avaient deux options : ignorer ces chemins, ou les laisser non testés jusqu&rsquo;au staging. Varnish est un cas à part : les tests tapent directement dans l&rsquo;application et contournent intentionnellement la couche de cache, son absence en CI est donc un choix délibéré plutôt qu&rsquo;un manque.</p>
<p>C&rsquo;est le problème que le <a href="https://12factor.net/dev-prod-parity" target="_blank" rel="noopener noreferrer">Facteur X</a>
 décrit comme l&rsquo;écart d&rsquo;environnement. L&rsquo;écart ici n&rsquo;était pas une question de configuration — il était structurel. La VM était construite par Ansible depuis un script dans un dépôt séparé. Ce n&rsquo;était pas une image de container. Elle n&rsquo;était pas versionnée aux côtés de l&rsquo;application. Si une branche modifiait la topologie de messages RabbitMQ, il n&rsquo;y avait aucun moyen de tester cette modification en CI. Le changement de topologie et le code qui en dépendait ne se rencontreraient qu&rsquo;en staging.</p>
<p>Le script de provisioning Ansible lui-même fait partie du problème :</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">launch_vm</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">stage</span>: <span style="color:#ae81ff">provision</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">script</span>:
</span></span><span style="display:flex;"><span>    - <span style="color:#ae81ff">git clone git@gitlab.internal/infra/ci-vm.git</span>
</span></span><span style="display:flex;"><span>    - <span style="color:#ae81ff">cd ci-vm</span>
</span></span><span style="display:flex;"><span>    - <span style="color:#ae81ff">az login --service-principal -u $ARM_CLIENT_ID ...</span>
</span></span><span style="display:flex;"><span>    - <span style="color:#ae81ff">terraform apply -var &#34;prefix=${CI_PIPELINE_ID}-vm&#34; ...</span>
</span></span><span style="display:flex;"><span>    - <span style="color:#ae81ff">sleep 45</span>
</span></span><span style="display:flex;"><span>    - <span style="color:#ae81ff">ansible-playbook behat/test-env.yml ...</span>
</span></span></code></pre></div><p>Le <code>sleep 45</code> est là parce qu&rsquo;Ansible a besoin que la VM finisse de booter avant de pouvoir s&rsquo;y connecter. Ce n&rsquo;est pas un oubli — c&rsquo;est le délai minimum qu&rsquo;une VM fraîchement provisionnée nécessite avant que SSH fonctionne. C&rsquo;est inscrit dans le processus.</p>
<h2 id="ce-qui-la-remplacé">Ce qui l&rsquo;a remplacé</h2>
<p>Le nouveau pipeline n&rsquo;a pas de stage <code>provision</code>. Il n&rsquo;a pas de stage <code>deprovision</code>. L&rsquo;environnement, ce sont les images, et les images existent avant que les tests commencent.</p>
<p>Chaque job de test déclare ses dépendances comme des services Docker :</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">services</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">$REGISTRY_URL/platform/rabbitmq:$CI_COMMIT_REF_SLUG</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">alias</span>: <span style="color:#ae81ff">rabbitmq</span>
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">$REGISTRY_URL/platform/minio:$CI_COMMIT_REF_SLUG</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">alias</span>: <span style="color:#ae81ff">minio</span>
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">redis:7.4.1</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">alias</span>: <span style="color:#ae81ff">redis</span>
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">$ARTIFACTORY_URL/postgresql:13</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">alias</span>: <span style="color:#ae81ff">postgresql</span>
</span></span></code></pre></div><p>Les services démarrent en parallèle quand le job commence. Avant que le script de test tourne, un <code>before_script</code> attend qu&rsquo;ils soient tous prêts :</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">before_script</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#ae81ff">$CI_PROJECT_DIR/dockerize</span>
</span></span><span style="display:flex;"><span>      -<span style="color:#ae81ff">wait tcp://postgresql:5432</span>
</span></span><span style="display:flex;"><span>      -<span style="color:#ae81ff">wait tcp://rabbitmq:5672</span>
</span></span><span style="display:flex;"><span>      -<span style="color:#ae81ff">wait tcp://minio:9000</span>
</span></span><span style="display:flex;"><span>      -<span style="color:#ae81ff">wait tcp://redis:6379</span>
</span></span><span style="display:flex;"><span>      -<span style="color:#ae81ff">timeout 120s</span>
</span></span></code></pre></div><p>Du démarrage du pipeline à la première assertion : quatre-vingt-dix secondes — en supposant que les images sont déjà dans le cache du runner ; un cold pull rallonge les choses, mais devient négligeable une fois que le pipeline a tourné une fois sur une branche donnée.</p>
<h2 id="ce-que-signifie-ci_commit_ref_slug">Ce que signifie <code>$CI_COMMIT_REF_SLUG</code></h2>
<p>Le timing est le résultat visible. Ce qui le produit est plus intéressant encore : les noms des images.</p>
<p><code>$REGISTRY_URL/platform/rabbitmq:$CI_COMMIT_REF_SLUG</code> n&rsquo;est pas l&rsquo;image officielle RabbitMQ de Docker Hub. C&rsquo;est une image construite par le même pipeline, depuis la même branche, au même commit que le code testé. L&rsquo;image RabbitMQ embarque la topologie : un <code>definitions.json</code> avec chaque exchange, chaque queue, chaque binding, chaque configuration de dead-letter — versionné dans git aux côtés de l&rsquo;application qui en dépend.</p>
<p>Si une branche modifie la topologie de messages, le pipeline CI construit une nouvelle image RabbitMQ qui inclut ces modifications, puis exécute les tests contre elle. Le changement de topologie et le code qui en dépend sont testés ensemble, au même commit, avant que quoi que ce soit n&rsquo;atteigne le staging.</p>
<p>La même logique s&rsquo;applique à MinIO, décrite dans le <a href="/2026/05/14/the-ghost-of-the-ci-runner/">premier article de cette série</a>
 : l&rsquo;image MinIO embarque des fixtures de test préchargées. L&rsquo;environnement CI n&rsquo;a pas besoin d&rsquo;une étape de setup pour peupler le stockage. L&rsquo;état est intégré à l&rsquo;artefact.</p>
<p>Le runner de tests lui-même suit le même pattern. Chaque job utilise une variante debug de l&rsquo;image applicative — construite depuis la même branche, au même commit — avec les dépendances de test incluses :</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">image</span>: <span style="color:#ae81ff">$REGISTRY_URL/platform/$service:$CI_COMMIT_REF_SLUG-debug</span>
</span></span></code></pre></div><p>Tout l&rsquo;environnement s&rsquo;assemble depuis des artefacts construits au même point de l&rsquo;historique git.</p>
<h2 id="ce-que-ça-a-demandé-dabandonner">Ce que ça a demandé d&rsquo;abandonner</h2>
<p>Behat et la VM provisionnée étaient couplés. La suite de tests Behat tournait contre un serveur HTTP sur la VM ; supprimer la VM signifiait supprimer Behat.</p>
<p>Ça s&rsquo;est révélé moins bloquant que ça n&rsquo;en avait l&rsquo;air. La suite Behat vivait dans un dépôt séparé, nécessitait la VM pour tourner, et avait accumulé une charge de maintenance significative. PHPUnit, tournant dans le container applicatif avec les services Docker, couvrait les mêmes scénarios par un chemin plus direct : tests fonctionnels qui exercent la couche HTTP, tests unitaires pour les composants individuels, suites organisées par domaine fonctionnel et générées dynamiquement en jobs CI parallèles.</p>
<p>La couche BDD a disparu. La couverture de tests est restée — et pouvait désormais tourner contre les vrais services.</p>
<h2 id="le-facteur-x-appliqué">Le Facteur X, appliqué</h2>
<p>Le <a href="https://12factor.net/dev-prod-parity" target="_blank" rel="noopener noreferrer">Facteur X</a>
 se lit souvent comme &ldquo;utilise la même base de données en local qu&rsquo;en production.&rdquo; C&rsquo;est la version la plus simple. La version plus profonde concerne l&rsquo;écart entre ce qu&rsquo;on teste et ce qu&rsquo;on livre.</p>
<p>L&rsquo;écart dans l&rsquo;ancien pipeline était large : une VM configurée manuellement, privée de services clés, reconstruite de zéro à chaque run. L&rsquo;écart dans le nouveau pipeline est étroit : le CI assemble l&rsquo;environnement depuis les mêmes images que la production, construites au même commit que le code sous test.</p>
<p>Les quinze minutes de Terraform et Ansible n&rsquo;étaient pas seulement lentes. Elles construisaient quelque chose qui n&rsquo;était pas ce que la production faisait tourner, à chaque fois, avant que le moindre test puisse commencer. Les quatre-vingt-dix secondes de <code>docker pull</code> construisent exactement ce que la production fait tourner — et les tests qui suivent testent ça, pas une approximation.</p>
]]></content:encoded></item></channel></rss>