<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
  <channel>
    <title><![CDATA[Piotr Przybyl - Elasticsearch Labs]]></title>
    <description><![CDATA[Articles and tutorials from the Search team at Elastic]]></description>
    <copyright><![CDATA[© 2026. Elasticsearch B.V. All Rights Reserved]]></copyright>
    <image>
      <title><![CDATA[Piotr Przybyl - Elasticsearch Labs]]></title>
      <url>https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1121c0bf0e8a6e65/6a88da6340a1841030ef456f/search-labs-thumbnail.png</url>
      <link>https://www.elastic.co/fr/search-labs/author/piotr-przybyl</link>
    </image>
    <link>https://www.elastic.co/fr/search-labs/author/piotr-przybyl</link>
    <atom:link href="https://www.elastic.co/fr/search-labs/rss/author/piotr-przybyl.xml" rel="self" type="application/rss+xml"/>
    <language><![CDATA[fr]]></language>
    <lastBuildDate>Mon, 28 Sep 2026 04:16:14 GMT</lastBuildDate>
  <item>
    <title><![CDATA[Shards et répliques Elasticsearch : Un guide pratique]]></title>
    <description><![CDATA[Maîtriser les concepts de shards et de réplicas Elasticsearch et apprendre à les optimiser.]]></description>
    <content:encoded><![CDATA[<p>Elasticsearch renforce la puissance de Lucene en construisant un système distribué au-dessus de celui-ci, qui répond aux problèmes d'évolutivité et de tolérance aux pannes. Il expose également une API REST basée sur JSON, ce qui rend l'interopérabilité avec d'autres systèmes très simple.</p><p>Les systèmes distribués comme Elasticsearch peuvent être très complexes, avec de nombreux facteurs qui peuvent affecter leurs performances et leur stabilité. Les <strong>Shards</strong> font partie des concepts les plus fondamentaux d'Elasticsearch, et la compréhension de leur fonctionnement vous permettra de gérer efficacement un cluster Elasticsearch.</p><p>Cet article explique ce qu'est un serveur primaire et un serveur réplique, leur impact sur un cluster Elasticsearch et les outils qui permettent de les adapter à des besoins différents.</p><h2>Comprendre les tessons</h2><p>Les données contenues dans un index Elasticsearch peuvent prendre des proportions considérables. Afin de rester gérable, chaque donnée est conservée dans un index, et les index sont un index divisé en un certain nombre de <strong>morceaux.</strong> Chaque tesson Elasticsearch est un index Apache Lucene, chaque index Lucene individuel contenant un sous-ensemble des documents de l'index Elasticsearch. Le fractionnement des indices de cette manière permet de contrôler l'utilisation des ressources. Un index Apache Lucene a une limite de 2 147 483 519 (2³¹ - 129) documents.</p><p>Parfois, les indices doivent être déplacés d'un nœud à l'autre à des fins de rééquilibrage. Étant donné que ce processus peut être à la fois long et coûteux en ressources, les indices ne doivent pas devenir trop volumineux, ce qui permet de maintenir le temps de récupération à un niveau raisonnable. En outre, comme les indices sont composés de segments Lucene qui doivent être constamment fusionnés, il est important que les segments ne deviennent pas trop grands. Pour ces raisons, Elasticsearch divise les données d'index en morceaux plus petits et plus faciles à gérer, appelés <strong>shards primaires</strong>, qui peuvent être plus facilement distribués sur un certain nombre de machines. Les shards <strong>répliqués</strong> sont simplement une copie exacte d'un shard primaire correspondant et nous verrons leur fonction plus loin dans cet article.</p><p>Il est important de disposer d'un nombre adéquat de fragments pour garantir les performances. Il est donc judicieux de planifier à l'avance. Lorsque les requêtes sont exécutées en parallèle sur différents nuages, elles s'exécutent plus rapidement qu'un index composé d'un seul nuage, mais uniquement si chaque nuage est situé sur un nœud différent et s'il y a suffisamment de nœuds dans la grappe. En même temps, cependant, les ensembles consomment de la mémoire et de l'espace disque, à la fois en termes de données indexées et de métadonnées de grappe. Le fait d'avoir un trop grand nombre de shards (également appelé oversharding) peut ralentir les requêtes, les demandes d'indexation et les opérations de gestion, c'est pourquoi il est essentiel de maintenir un bon équilibre.</p><p>Le nombre de groupes primaires est défini au moment de la création de l'index <strong>pour cette instance d'index spécifique</strong>. Si vous avez besoin ultérieurement d'un nombre différent d'unités primaires, vous pouvez utiliser les<strong> API de redimensionnement :</strong>division(plus d'unités<a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-indices-shrink">primaires),</a> <a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-indices-split">réduction</a> (moins d'unités primaires) ou <a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-indices-clone">clonage</a> (le même nombre d'unités primaires avec de nouveaux paramètres pour les réplicas). Ces opérations copient des segments Lucene et <strong>évitent une réindexation complète de tous les documents</strong>. Lors de la création d'un index, vous pouvez définir le nombre de shards primaires et de shards répliqués comme paramètres de l'index :</p>PUT /sensor
{
   "settings" : {
       "index" : {
           "number_of_shards" : 6,
           "number_of_replicas" : 2
       }
   }
}<p>(Si vous ne spécifiez pas le nombre de shards ou de répliques, la valeur par défaut est 1, à partir d'Elasticsearch 7.0). Le nombre idéal d'unités de stockage doit être déterminé en fonction de la quantité de données contenues dans un index. En règle générale, <a href="https://www.elastic.co/docs/deploy-manage/production-guidance/optimize-performance/size-shards">un fonds optimal doit contenir de 10 à 50 Go de données</a>, avec moins de 200 millions de documents par fonds. Par exemple, si vous prévoyez d'accumuler environ 300 Go de journaux d'application par jour, il serait raisonnable d'avoir environ 10 fichiers dans cet index, à condition que vous disposiez d'un nombre suffisant de nœuds pour les héberger.</p><p>Au cours de leur vie, les tessons peuvent passer par un certain nombre d'états, notamment</p><ul><li><p><strong>Initialisation :</strong> Un état initial avant que le tesson puisse être utilisé.</p></li><li><p><strong>Démarré :</strong> État dans lequel le groupe de stockage est actif et peut recevoir des demandes.</p></li><li><p><strong>Relocalisation :</strong> Un état qui se produit lorsque les shards sont en train d'être déplacés vers un autre nœud. Cela peut s'avérer nécessaire dans certaines conditions, par exemple lorsque le nœud sur lequel ils se trouvent manque d'espace disque.</p></li><li><p><strong>Non assigné :</strong> État d'un tesson qui n'a pas été affecté. Une raison est fournie lorsque cela se produit, par exemple, si le nœud hébergeant le dépôt n'est plus dans le cluster <em>(NODE_LEFT)</em> ou en raison d'une restauration dans un index fermé <em>(EXISTING_INDEX_RESTORED)</em>.</p></li></ul><p>Pour afficher tous les shards, leur état et d'autres métadonnées, vous pouvez utiliser la requête suivante :</p>GET _cat/shards<p>Pour visualiser les dépôts d'un index spécifique, vous pouvez ajouter le nom de l'index à l'URL, par exemple, sensor :</p>GET _cat/shards/sensor<p>Cette commande produit une sortie, comme dans l'exemple suivant. Par défaut, les colonnes affichées comprennent le nom de l'index, le nom (i.e. ) du dépôt, s'il s'agit d'un dépôt primaire ou d'une réplique, son état, le nombre de documents, la taille sur le disque, ainsi que l'adresse IP et l'ID du nœud où se trouve le dépôt.</p>sensor 5 p STARTED    0  283b 127.0.0.1 ziap
sensor 5 r UNASSIGNED                  
sensor 2 p STARTED    1 3.7kb 127.0.0.1 ziap
sensor 2 r UNASSIGNED                  
sensor 3 p STARTED    3 7.2kb 127.0.0.1 ziap
sensor 3 r UNASSIGNED                  
sensor 1 p STARTED    1 3.7kb 127.0.0.1 ziap
sensor 1 r UNASSIGNED                  
sensor 4 p STARTED    2 3.8kb 127.0.0.1 ziap
sensor 4 r UNASSIGNED                  
sensor 0 p STARTED    0  283b 127.0.0.1 ziap
sensor 0 r UNASSIGNED<h2>Comprendre les répliques</h2><p>Alors que chaque nuage contient une seule copie des données, un index peut contenir plusieurs copies du nuage. Il y a donc deux types de tessons, le <strong>tesson primaire</strong> et une copie, ou <strong>réplique</strong>. Chaque réplique d'un groupe de données primaire est toujours située sur un nœud différent, ce qui garantit la haute disponibilité de vos données en cas de défaillance d'un nœud. Outre la redondance et leur rôle dans la prévention des pertes de données et des temps d'arrêt, les répliques peuvent également contribuer à améliorer les performances de recherche en permettant aux requêtes d'être traitées en parallèle avec le shard principal, et donc plus rapidement.</p><p>Il existe des différences importantes dans la manière dont se comportent les disques primaires et les disques répliques. Bien qu'ils soient tous deux capables de traiter les requêtes, les demandes d'indexation (c.-à-d. les demandes d'accès à la base de données) ne sont pas traitées. l'ajout de données à l'index) doivent d'abord passer par les disques primaires avant d'être répliqués dans les disques répliques. Comme nous l'avons vu plus haut, si un shard primaire devient indisponible, par exemple en raison d'une déconnexion de nœud ou d'une défaillance matérielle, un réplica est promu pour reprendre son rôle.</p><p>Si les répliques peuvent être utiles en cas de défaillance d'un nœud, il est important de ne pas en avoir trop, car elles consomment de la mémoire, de l'espace disque et de la puissance de calcul lors de l'indexation. Une autre différence entre les shards primaires et les réplicas est que le nombre de shards primaires ne peut pas être modifié après la création de l'index, alors que le nombre de réplicas peut être modifié dynamiquement à tout moment en mettant à jour les paramètres de l'index.</p><p>Un autre facteur à prendre en compte pour les répliques est le nombre de nœuds disponibles. Les répliques sont toujours placées sur des nœuds différents de ceux du groupe principal, car deux copies des mêmes données sur le même nœud n'offriraient aucune protection en cas de défaillance de ce nœud. Par conséquent, pour qu'un système prenne en charge <em>n</em> répliques, il doit y avoir au moins <em>n + 1</em> nœuds dans la grappe. Par exemple, s'il y a deux nœuds dans un cluster et qu'un index est configuré avec six répliques, une seule réplique sera allouée. En revanche, un système à sept nœuds est parfaitement capable de gérer un shard primaire et six répliques.</p><h2>Optimisation des grappes et des répliques</h2><p>Même après la création d'un index avec le bon équilibre entre les unités primaires et les unités répliquées, celles-ci doivent être surveillées, car la dynamique autour d'un index évolue au fil du temps. Par exemple, lorsqu'il s'agit de séries chronologiques, les indices contenant des données récentes sont généralement plus actifs que les indices plus anciens. Sans réglage de ces indices, ils consommeraient tous la même quantité de ressources, malgré leurs exigences très différentes.</p><p>L'API de l'indice de reconduction peut être utilisée pour séparer les indices les plus récents des plus anciens. Il peut être configuré pour créer automatiquement un nouvel index lorsqu'un certain seuil - taille d'un index sur le disque, nombre de documents ou âge - est atteint. Cette API est également utile pour contrôler la taille des fichiers. Étant donné que le nombre de groupes ne peut pas être facilement modifié après la création de l'index, les groupes continueront d'accumuler des données si aucune condition de transfert n'est remplie. Pour les index plus anciens qui ne nécessitent que des accès peu fréquents, le rétrécissement et la fusion forcée d'un index sont deux moyens différents de réduire leur empreinte mémoire et disque. La première permet de réduire le nombre d'unités dans un index, tandis que la seconde réduit le nombre de segments Lucene et libère l'espace utilisé par les documents qui ont été supprimés.</p><h2>Shards primaires et répliques comme base d'Elasticsearch</h2><p>Elasticsearch s'est forgé une solide réputation en tant que plateforme distribuée de stockage, de recherche et d'analyse pour d'énormes volumes de données. Toutefois, à une telle échelle, des problèmes se posent inévitablement. C'est pourquoi il est si important et fondamental pour Elasticsearch de comprendre le fonctionnement des shards primaires et répliqués, car cela permet d'optimiser la fiabilité et les performances de la plateforme.</p><p>Il est essentiel de savoir comment ils fonctionnent et comment les optimiser pour obtenir un cluster Elasticsearch plus robuste et plus performant. Si vous rencontrez régulièrement des réponses lentes aux requêtes ou des pannes, ces connaissances peuvent être la clé pour surmonter ces obstacles.</p><p>Suivez la documentation officielle d'Elasticsearch pour en savoir plus sur les <a href="https://www.elastic.co/docs/deploy-manage/distributed-architecture/clusters-nodes-shards">clusters, les nœuds et les shards</a>, sur <a href="https://www.elastic.co/docs/deploy-manage/production-guidance/optimize-performance/size-shards">la taille des shards</a>, sur l <a href="https://www.elastic.co/docs/deploy-manage/distributed-architecture/shard-allocation-relocation-recovery">'allocation des shards et sur la récupération.</a></p><p>Ce sujet est également disponible sous forme de cours d'introduction sur la <a href="https://youtu.be/sAySPSyL2qE">chaîne YouTube de la communauté Elastic</a>.</p><p>Enfin, si vous ne voulez pas vous préoccuper des nœuds, des unités de stockage ou des répliques, vous pouvez essayer <a href="https://www.elastic.co/docs/deploy-manage/deploy/elastic-cloud/serverless">Elastic Cloud Serverless</a>. Cette offre Elastic Cloud est entièrement gérée par Elastic et automatisée pour évoluer avec votre charge de travail. Un essai gratuit peut vous aider à vous familiariser avec d'autres avantages de l'approche sans serveur.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elasticsearch-shards-and-replicas-guide</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elasticsearch-shards-and-replicas-guide</guid>
    <category><![CDATA[Les bases]]></category>
    <dc:creator><![CDATA[Piotr Przybyl]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt71dd92d939d383a2/6a17e9d53e03d769c44f2cc7/7775c44f01f2516c4ff4cce6d6bbe9e7b2c38908-720x420.jpg" length="0" type="image/jpeg"/>
    <pubDate>Thu, 14 Aug 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Tester votre code Java avec des mocks et du vrai Elasticsearch]]></title>
    <description><![CDATA[Apprenez à écrire vos tests automatisés pour Elasticsearch, en utilisant des mocks et des Testcontainers.]]></description>
    <content:encoded><![CDATA[<p>Dans ce billet, nous allons présenter et expliquer deux façons de tester un logiciel en utilisant Elasticsearch comme dépendance d'un système externe. Nous couvrirons les tests utilisant des mocks ainsi que les tests d'intégration, nous montrerons quelques différences pratiques entre eux, et nous donnerons quelques conseils sur la façon de procéder pour chaque style.</p><h2>De bons tests pour la confiance dans le système</h2><p>Un bon test est un test qui augmente la confiance de chaque personne impliquée dans le processus de création et de maintenance d'un système informatique. Les tests ne sont pas censés être cool, rapides ou augmenter artificiellement la couverture du code. Les tests jouent un rôle essentiel à cet égard :</p><ul><li><p>Ce que nous voulons livrer va fonctionner en production.</p></li><li><p>Le système répond aux exigences et aux contrats.</p></li><li><p>Il n'y aura pas de régression à l'avenir.</p></li><li><p>Les développeurs (et les autres membres de l'équipe concernés) sont convaincus que ce qu'ils ont créé fonctionnera.</p></li></ul><p>Bien sûr, cela ne signifie pas que les tests ne peuvent pas être cool, rapides ou augmenter la couverture du code. Plus vite nous pouvons exécuter notre suite de tests, mieux c'est. C'est juste que dans la poursuite de la réduction de la durée globale de la suite de tests, nous ne devrions pas sacrifier la fiabilité, la maintenabilité et la confiance que les tests automatisés nous donnent.</p><p>De bons tests automatisés renforcent la confiance des différents membres de l'équipe :</p><ul><li><p>Les développeurs : ils peuvent confirmer que ce qu'ils font fonctionne (avant même que le code sur lequel ils travaillent ne quitte leur machine).</p></li><li><p>L'équipe d'assurance qualité : elle a moins de tests à effectuer manuellement.</p></li><li><p>Les opérateurs de systèmes et les SRE sont plus détendus, car les systèmes sont plus faciles à déployer et à entretenir.</p></li></ul><p>Dernier point, mais non des moindres : l'architecture d'un système. Nous aimons que les systèmes soient organisés, faciles à entretenir, et que l'architecture soit propre et utile. Cependant, il arrive parfois que l'architecture sacrifie trop à l'excuse connue sous le nom de ": elle est plus testable de cette façon". Il n'y a rien de mal à être très testable, mais lorsque le système est écrit principalement pour être testable au lieu de répondre aux besoins qui justifient son existence, on se retrouve dans une situation où c'est la queue qui l'emporte.</p><h2>Deux types de tests : Mocks &amp; dépendances</h2><p>Les tests peuvent être perçus et donc classés de différentes manières. Dans ce billet, je me concentrerai sur un seul aspect de la division des tests : l'utilisation de mocks (ou stubs, ou fakes, ou ...) par rapport à l'utilisation de vraies dépendances. Dans notre cas, la dépendance est Elasticsearch.</p><p>Les tests utilisant des mocks sont très rapides car ils n'ont pas besoin de démarrer des dépendances externes et tout se passe uniquement en mémoire. Dans le cadre des tests automatisés, on utilise de faux objets au lieu de vrais objets pour tester des parties d'un programme sans utiliser les dépendances réelles. C'est la raison pour laquelle ils sont nécessaires et qu'ils brillent dans tous les tests de réseaux de détection rapide, par exemple. la validation des données. Il n'est pas nécessaire de lancer une base de données et de l'appeler uniquement pour vérifier que les nombres négatifs dans une demande ne sont pas autorisés, par exemple.</p><p>Cependant, l'introduction des mocks a plusieurs implications :</p><ul><li><p>Il n'est pas possible de simuler facilement tout et tout le temps, et les simulations ont donc un impact sur l'architecture du système (ce qui est parfois très bien, parfois moins bien).</p></li><li><p>Les tests fonctionnant sur des mocks peuvent être rapides, mais leur développement peut prendre un certain temps, car les mocks reflétant profondément les systèmes qu'ils imitent ne sont généralement pas fournis gratuitement. Quelqu'un qui sait comment le système fonctionne doit écrire les mocks de la bonne manière, et cette connaissance peut provenir d'une expérience pratique, de l'étude de la documentation, etc.</p></li><li><p>Les objets fictifs doivent être entretenus. Lorsque votre système dépend d'une dépendance externe et que vous devez mettre à jour cette dépendance, quelqu'un doit s'assurer que les mocks imitant la dépendance sont également mis à jour avec tous les changements : cassures, documentés et non documentés (qui peuvent également avoir un impact sur notre système). Cela devient particulièrement pénible lorsque vous voulez mettre à jour une dépendance mais que votre suite de tests (qui n'utilise que des mocks) ne peut pas vous donner l'assurance que tous les cas testés sont garantis de fonctionner.</p></li><li><p>Il faut de la discipline pour s'assurer que les efforts sont consacrés au développement et aux tests du système, et non aux simulacres.</p></li></ul><p>Pour ces raisons, de nombreuses personnes préconisent d'aller exactement dans la direction opposée : ne jamais utiliser de mocks (ou de stubs, etc.), mais s'appuyer uniquement sur les dépendances réelles. Cette approche fonctionne très bien dans les démonstrations ou lorsque le système est minuscule et ne comporte que quelques cas de test générant une couverture importante. Ces tests peuvent être des tests d'intégration (grosso modo : vérification d'une partie d'un système par rapport à certaines dépendances réelles) ou des tests de bout en bout (utilisation simultanée de toutes les dépendances réelles et vérification du comportement du système à toutes les extrémités, tout en jouant les flux de travail de l'utilisateur qui définissent le système comme utilisable et performant). Un avantage évident de cette approche est que nous vérifions également (souvent involontairement) nos hypothèses sur les dépendances et la manière dont nous les intégrons dans le système sur lequel nous travaillons.</p><p>Cependant, lorsque les tests n'utilisent que des dépendances réelles, nous devons prendre en compte les aspects suivants :</p><ul><li><p>Certains scénarios de test n'ont pas besoin de la dépendance réelle (par exemple, pour vérifier les invariants statiques d'une demande).</p></li><li><p>Ces tests ne sont généralement pas exécutés par séries entières sur les machines des développeurs, car l'attente d'un retour d'information prendrait trop de temps.</p></li><li><p>Ils nécessitent plus de ressources sur les machines d'IC, et il faut parfois plus de temps pour régler les choses afin de ne pas perdre de temps sur &amp;.</p></li><li><p>Il n'est pas toujours facile d'initialiser les dépendances avec des données de test.</p></li><li><p>Les tests avec des dépendances réelles sont parfaits pour isoler le code avant une refonte majeure, une migration ou une mise à niveau des dépendances.</p></li><li><p>Il est plus probable qu'il s'agisse de tests opaques, c'est-à-dire qu'ils ne détaillent pas les aspects internes du système testé, mais s'intéressent à leurs résultats.</p></li></ul><h2>Le bon choix : utiliser les deux tests</h2><p>Au lieu de tester votre système avec un seul type de test, vous pouvez vous appuyer sur les deux types de test lorsque c'est utile et essayer d'améliorer votre utilisation des deux.</p><ul><li><p>Exécutez d'abord les tests basés sur des simulacres parce qu'ils sont beaucoup plus rapides, et seulement lorsque tous les tests sont réussis, exécutez les tests de dépendance plus lents seulement après.</p></li><li><p>Choisissez les mocks pour les scénarios où les dépendances externes ne sont pas vraiment nécessaires : lorsque le mocking prendrait trop de temps, le code devrait être massivement modifié juste pour cela ; comptez sur les dépendances externes.</p></li><li><p>Il n'y a rien de mal à tester un morceau de code en utilisant les deux approches, tant que cela a un sens.</p></li></ul><h2>Exemple de SystemUnderTest</h2><p>Pour les sections suivantes, nous allons utiliser un exemple qui se trouve <a href="https://github.com/pioorg/testing-elasticsearch">ici.</a> Il s'agit d'une petite application de démonstration écrite en Java 21, utilisant Maven comme outil de construction, s'appuyant sur le client Elasticsearch et utilisant le dernier ajout d'Elasticsearch, utilisant <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/esql.html">ES|QL</a> (le nouveau langage de requête procédural d'Elastic). Si Java n'est pas votre langage de programmation, vous devriez tout de même être en mesure de comprendre les concepts que nous allons aborder ci-dessous et de les transposer dans votre pile. C'est juste que l'utilisation d'un exemple de code réel rend certaines choses plus faciles à expliquer.</p><p>Le site <code>BookSearcher</code> nous aide à gérer la recherche et l'analyse de données, à savoir des livres dans notre cas (comme nous l'avons démontré dans l <a href="https://www.elastic.co/search-labs/blog/esql-queries-to-java-objects">'un des articles précédents)</a>.</p><ul><li><p>Il nécessite Elasticsearch exactement dans la version <code>8.15.x</code> comme seule dépendance (voir <code>isCompatibleWithBackend()</code>), par exemple parce que nous ne sommes pas sûrs que notre code soit compatible avec le futur, et nous sommes sûrs qu'il n'est pas compatible avec le passé. Avant de mettre à niveau Elasticsearch en production vers une version plus récente, nous devons d'abord le tester pour nous assurer que le comportement du système testé reste le même.</p></li><li><p>Nous pouvons l'utiliser pour rechercher le nombre de livres publiés au cours d'une année donnée (voir <code>numberOfBooksPublishedInYear</code>).</p></li><li><p>Nous pouvons également l'utiliser lorsque nous devons analyser notre ensemble de données et trouver les 20 auteurs les plus publiés entre deux années données (voir <code>mostPublishedAuthorsInYears</code>).</p></li></ul>public class BookSearcher {

    private final ElasticsearchClient esClient;

    public BookSearcher(ElasticsearchClient esClient) {
        this.esClient = esClient;
        if (!isCompatibleWithBackend()) {
            throw new UnsupportedOperationException("This is not compatible with backend");
        }
    }

    private boolean isCompatibleWithBackend() {
        try (ResultSet rs = esClient.esql().query(ResultSetEsqlAdapter.INSTANCE, """
            show info
            | keep version
            | dissect version "%{major}.%{minor}.%{patch}"
            | keep major, minor
            | limit 1""")) {
            if (!rs.next()) {
                throw new RuntimeException("No version found");
            }
            return rs.getInt(1) == 8 &amp;&amp; rs.getInt(2) == 15;
        } catch (SQLException | IOException e) {
            throw new RuntimeException(e);
        }
    }

    public int numberOfBooksPublishedInYear(int year) {
        try (ResultSet rs = esClient.esql().query(ResultSetEsqlAdapter.INSTANCE, """
            from books
            | where year == ?
            | stats published = count(*) by year
            | limit 1000""", year)) {

            if (rs.next()) {
                return rs.getInt("published");
            }
        } catch (SQLException | IOException e) {
            throw new RuntimeException(e);
        }
        return 0;
    }


    public List&lt;MostPublished&gt; mostPublishedAuthorsInYears(int minYear, int maxYear) {
        assert minYear &lt;= maxYear;
        String query = """
            from books
            | where year &gt;= ? and year &lt;= ?
            | stats first_published = min(year), last_published = max(year), times = count (*) by author
            | eval years_published = last_published - first_published
            | sort years_published desc
            | drop years_published
            | limit 20
            """;

        try {
            Iterable&lt;MostPublished&gt; published = esClient.esql().query(
                ObjectsEsqlAdapter.of(MostPublished.class),
                query,
                minYear,
                maxYear);

            List&lt;MostPublished&gt; mostPublishedAuthors = new ArrayList&lt;&gt;();
            for (MostPublished mostPublished : published) {
                mostPublishedAuthors.add(mostPublished);
            }
            return mostPublishedAuthors;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public record MostPublished(
        String author,
        @JsonProperty("first_published") int firstPublished,
        @JsonProperty("last_published") int lastPublished,
        int times
    ) {
        public MostPublished {
            assert author != null;
            assert firstPublished &lt;= lastPublished;
            assert times &gt; 0;
        }
    }
}
<h2>Tester avec des mocks pour commencer</h2><p>Pour créer les mocks utilisés dans nos tests, nous allons utiliser <a href="https://site.mockito.org/">Mockito</a>, une bibliothèque de mocking très populaire dans l'écosystème Java.</p><p>Nous pourrions commencer par ce qui suit, pour que les simulations soient réinitialisées avant chaque test :</p>public class BookSearcherMockingTest {

    ResultSet mockResultSet;
    ElasticsearchClient esClient;
    ElasticsearchEsqlClient esql;

    @BeforeEach
    void setUpMocks() {
        mockResultSet = mock(ResultSet.class);
        esClient = mock(ElasticsearchClient.class);
        esql = mock(ElasticsearchEsqlClient.class);

    }
}
<p>Comme nous l'avons dit précédemment, tout ne peut pas être facilement testé à l'aide de mocks. Mais il y a des choses que l'on peut (et que l'on doit probablement) faire. Essayons de vérifier que la seule version supportée d'Elasticsearch est <code>8.15.x</code> pour l'instant (à l'avenir, nous pourrions étendre la gamme une fois que nous aurons confirmé que notre système est compatible avec les versions futures) :</p>@Test
void canCreateSearcherWithES_8_15() throws SQLException, IOException{
    // when
    when(esClient.esql()).thenReturn(esql);
    when(esql.query(eq(ResultSetEsqlAdapter.INSTANCE), anyString())).thenReturn(mockResultSet);
    when(mockResultSet.next()).thenReturn(true).thenReturn(false);
    when(mockResultSet.getInt(1)).thenReturn(8);
    when(mockResultSet.getInt(2)).thenReturn(15);

    // then
    Assertions.assertDoesNotThrow(() -&gt; new BookSearcher(esClient));
}
<p>Nous pouvons vérifier de la même manière (simplement en renvoyant une version mineure différente) que notre site <code>BookSearcher</code> ne fonctionnera pas encore avec <code>8.16.x</code>, car nous ne sommes pas sûrs qu'il sera compatible avec lui :</p>@Test
void cannotCreateSearcherWithoutES_8_15() throws SQLException, IOException {
    // when
    when(esClient.esql()).thenReturn(esql);
    when(esql.query(eq(ResultSetEsqlAdapter.INSTANCE), anyString())).thenReturn(mockResultSet);
    when(mockResultSet.next()).thenReturn(true).thenReturn(false);
    when(mockResultSet.getInt(1)).thenReturn(8);
    when(mockResultSet.getInt(2)).thenReturn(16);

    // then
    Assertions.assertThrows(UnsupportedOperationException.class, () -&gt; new BookSearcher(esClient));
}
<p>Voyons maintenant comment nous pouvons obtenir quelque chose de similaire en testant contre un vrai Elasticsearch. Pour cela, nous allons utiliser le <a href="https://java.testcontainers.org/modules/elasticsearch/">module Elasticsearch de Testcontainers</a>, qui n'a qu'une seule exigence : il a besoin d'un accès à Docker, car il exécute des conteneurs Docker pour vous. D'un certain point de vue, Testcontainers est simplement un moyen d'exploiter les conteneurs Docker, mais au lieu de le faire dans votre Docker Desktop (ou similaire), dans votre CLI, ou dans des scripts, vous pouvez exprimer vos besoins dans le langage de programmation que vous connaissez. Cela permet de récupérer des images, de démarrer des conteneurs, de les ramasser après les tests, de copier des fichiers dans les deux sens, d'exécuter des commandes, d'examiner les journaux, etc. directement à partir de votre code de test.</p><p>Le talon pourrait ressembler à ceci :</p>@Testcontainers
public class BookSearcherIntTest {

    static final String ELASTICSEARCH_IMAGE = "docker.elastic.co/elasticsearch/elasticsearch:8.15.0";
    static final JacksonJsonpMapper JSONP_MAPPER = new JacksonJsonpMapper();

    RestClientTransport transport;
    ElasticsearchClient client;

    @Container
    ElasticsearchContainer elasticsearch = new ElasticsearchContainer(ELASTICSEARCH_IMAGE);

    @BeforeEach
    void setupClient() {
        transport = // setup transport here
        client = new ElasticsearchClient(transport);
    }

    @AfterEach
    void closeClient() throws IOException {
        if (transport != null) {
            transport.close();
        }
    }

}
<p>Dans cet exemple, nous nous appuyons sur l'<a href="https://java.testcontainers.org/test_framework_integration/junit_5/">intégration JUnit de Testcontainers</a> avec <code>@Testcontainers</code> et <code>@Container</code>, ce qui signifie que nous n'avons pas à nous soucier de démarrer Elasticsearch avant nos tests et de l'arrêter après. La seule chose à faire est de créer le client avant chaque test et de le fermer après chaque test (pour éviter les fuites de ressources, qui pourraient avoir un impact sur des suites de tests plus importantes).</p><p>Annoter un champ non statique avec <code>@Container</code> signifie qu'un nouveau conteneur sera démarré pour chaque test, nous n'avons donc pas à nous soucier des données périmées ou de la réinitialisation de l'état du conteneur. Cependant, avec de nombreux tests, cette approche pourrait ne pas donner de bons résultats, c'est pourquoi nous allons la comparer à d'autres solutions dans l'un des prochains articles.</p><p><strong>Remarque :</strong></p>En vous appuyant sur <code>docker.elastic.co</code> (le dépôt d'images Docker officiel d'Elastic), vous évitez d'épuiser vos limites sur le hub Docker.

Il est également recommandé d'utiliser la même version de votre dépendance dans votre environnement de test et de production, afin de garantir une compatibilité maximale. Nous recommandons également d'être précis dans la sélection de la version, pour cette raison, il n'y a pas de balise <code>latest</code> pour les images Elasticsearch.<h2>Connexion à Elasticsearch dans les tests</h2><p><a href="https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/current/index.html">Le client Java Elasticsearch</a> est capable de se connecter à Elasticsearch fonctionnant dans un conteneur de test même si la sécurité et SSL/TLS sont activés (ce qui est le cas par défaut pour les versions 8.x, c'est pourquoi nous n'avons pas eu à spécifier quoi que ce soit relatif à la sécurité dans la déclaration du conteneur). En supposant que l'Elasticsearch que vous utilisez en production dispose également de TLS et d'une certaine sécurité, il est recommandé d'opter pour une configuration de test d'intégration aussi proche que possible du scénario de production, et donc de ne pas les désactiver dans les tests.</p><p>Comment obtenir les données nécessaires à la connexion, en supposant que le conteneur soit affecté au champ ou à la variable <code>elasticsearch</code>:</p><ul><li><p><code>elasticsearch.getHost()</code> vous donnera l'hôte sur lequel le conteneur fonctionne (qui la plupart du temps sera probablement <code>"localhost"</code>, mais ne le codifiez pas en dur car parfois, en fonction de votre configuration, il peut s'agir d'un autre nom, donc l'hôte doit toujours être obtenu dynamiquement).</p></li><li><p><code>elasticsearch.getMappedPort(9200)</code> donnera le port hôte que vous devez utiliser pour vous connecter à Elasticsearch à l'intérieur du conteneur (parce qu'à chaque fois que vous démarrez le conteneur, le port extérieur est différent, donc cela doit être un appel dynamique également).</p></li><li><p>Sauf s'ils ont été écrasés, le nom d'utilisateur et le mot de passe par défaut sont respectivement <code>"elastic"</code> et <code>"changeme"</code>.</p></li><li><p>Si aucun certificat SSL/TLS n'a été spécifié lors de la configuration du conteneur et que la connectivité sécurisée n'est pas désactivée (ce qui est le comportement par défaut à partir des versions 8.x), un certificat auto-signé est généré. Pour lui faire confiance (par exemple comme le <a href="https://curl.se/docs/manpage.html#--cacert">fait cURL</a>), le certificat peut être obtenu en utilisant <code>elasticsearch.caCertAsBytes()</code> (qui renvoie <code>Optional&lt;byte[]&gt;</code>), ou une autre méthode pratique consiste à obtenir <code>SSLContext</code> en utilisant <code>createSslContextFromCa()</code>.</p></li></ul><p>Le résultat global pourrait ressembler à ceci :</p>BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials("elastic", "changeme"));

// Create a low level rest client
RestClient restClient = RestClient.builder(new HttpHost(elasticsearch.getHost(), elasticsearch.getMappedPort(9200), "https"))
    .setHttpClientConfigCallback(httpClientBuilder -&gt;
        httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider)
            .setSSLContext(elasticsearch.createSslContextFromCa())
    )
    .build();

// The RestClientTransport is mainly for serialization/deserialization
RestClientTransport transport = new RestClientTransport(restClient, new JacksonJsonpMapper());

// The official Java API Client for Elasticsearch
ElasticsearchClient client = new ElasticsearchClient(transport);
<p>Un autre exemple de création d'une instance de <code>ElasticsearchClient</code> se trouve dans le <a href="https://github.com/pioorg/testing-elasticsearch/blob/e800b4b2ab3d706efcafb9a8182480e69e475b86/src/test/java/testing_elasticsearch/BookSearcherIntTest.java#L61">projet de démonstration</a>.</p><p><strong>Remarque</strong>:</p>Pour la création de clients dans des environnements de production, veuillez vous référer à la <a href="https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/current/connecting.html#_verifying_https_with_a_certificate_fingerprint">documentation.</a><h2>Premier test d'intégration</h2><p>Notre tout premier test, qui consiste à vérifier que nous pouvons créer <code>BookSearcher</code> à l'aide de la version 8.15.x d'Elasticsearch, pourrait ressembler à ceci :</p>@Test
void canCreateClientWithContainerRunning_8_15() {
    Assertions.assertDoesNotThrow(() -&gt; new BookSearcher(client));
}
<p>Comme vous pouvez le constater, nous n'avons besoin de rien d'autre. Nous n'avons pas besoin de simuler la version renvoyée par Elasticsearch, la seule chose que nous devons faire est de fournir à <code>BookSearcher</code> un client connecté à une instance réelle d'Elasticsearch, qui a été démarrée pour nous par Testcontainers.</p><h2>Les tests d'intégration se soucient moins des aspects internes</h2><p>Faisons une petite expérience : supposons que nous devions cesser d'extraire des données de l'ensemble de résultats à l'aide d'indices de colonnes, mais que nous devions nous appuyer sur les noms de colonnes. Ainsi, dans la méthode <code>isCompatibleWithBackend</code>, au lieu de</p>return rs.getInt(1) == 8 &amp;&amp; rs.getInt(2) == 15;
<p>que nous allons avoir :</p>return rs.getInt("major") == 8 &amp;&amp; rs.getInt("minor") == 15;
<p>Lorsque nous réexécutons les deux tests, nous remarquons que le test d'intégration avec le vrai Elasticsearch passe toujours sans problème. Cependant, les tests utilisant des simulacres ont cessé de fonctionner, parce que nous avons simulé des appels tels que <code>rs.getInt(int)</code>, et non <code>rs.getInt(String)</code>. Pour les faire passer, nous devons maintenant soit les simuler à la place, soit les simuler tous les deux, en fonction des autres cas d'utilisation que nous avons dans notre suite de tests.</p><h2>Les tests d'intégration peuvent être un canon pour tuer une mouche</h2><p>Les tests d'intégration permettent de vérifier le comportement du système, même si des dépendances externes ne sont pas nécessaires. Cependant, cette utilisation est généralement une perte de temps et de ressources d'exécution. Examinons la méthode <code>mostPublishedAuthorsInYears(int minYear, int maxYear)</code>. Les deux premières lignes sont les suivantes :</p>assert minYear &lt;= maxYear;
String query = // here goes the query
<p>La première instruction vérifie une condition qui ne dépend pas d'Elasticsearch (ou de toute autre dépendance externe) de quelque manière que ce soit. Par conséquent, il n'est pas nécessaire de lancer des conteneurs pour vérifier simplement que si <code>minYear</code> est supérieur à <code>maxYear</code>, une exception est levée.</p><p>Un simple test de simulation, qui est également rapide et peu gourmand en ressources, est plus que suffisant pour s'en assurer. Après avoir mis en place les simulacres, nous pouvons simplement opter pour :</p>BookSearcher systemUnderTest = new BookSearcher(esClient);

Assertions.assertThrows(
    AssertionError.class,
    () -&gt; systemUnderTest.mostPublishedAuthorsInYears(2012, 2000)
);
<p>Lancer une dépendance, au lieu de faire du mocking, serait un gaspillage dans <a href="https://github.com/pioorg/testing-elasticsearch/blob/e800b4b2ab3d706efcafb9a8182480e69e475b86/src/test/java/testing_elasticsearch/BookSearcherMockingTest.java#L89">ce cas de test</a> car il n'y a aucune chance de faire un appel significatif pour cette dépendance.</p><p>Cependant, pour vérifier le comportement à partir de <code>String query = ...</code>, que la requête est écrite correctement, les résultats sont conformes aux attentes : la bibliothèque du client est capable d'envoyer des requêtes et des réponses correctes, il n'y a pas de changement de syntaxe et il est donc beaucoup plus facile d'utiliser un test d'intégration, par exemple :</p>@BeforeEach
void setupDataInContainer() {
    // here we initialise data in the Elasticsearch running in a container
}

@Test
void shouldGiveMostPublishedAuthorsInGivenYears() {
    var systemUnderTest = new BookSearcher(client);
    var list = systemUnderTest.mostPublishedAuthorsInYears(1800, 2010);
    Assertions.assertEquals("Beatrix Potter", list.get(12).author(), "Beatrix Potter was 13th most published author between 1800 and 2010");
}
<p>De cette façon, nous pouvons être assurés que lorsque nous envoyons nos données à Elasticsearch (dans cette version ou dans toute version future vers laquelle nous choisirons de migrer), notre requête nous donnera exactement ce que nous attendions : le format des données n'a pas changé, la requête est toujours valide, et tous les middleware (clients, pilotes, sécurité, etc.) continueront à fonctionner. Nous n'avons pas à nous préoccuper de maintenir les mocks à jour, le seul changement nécessaire pour assurer la compatibilité avec, par exemple, le système de gestion de l'information de l'entreprise. <code>8.15</code> changerait cela :</p>static final String ELASTICSEARCH_IMAGE = "docker.elastic.co/elasticsearch/elasticsearch:8.15.0";
<p>Il en va de même si vous décidez par exemple de utiliser le bon vieux QueryDSL au lieu de ES|QL : les résultats que vous recevez de la requête (quelle que soit la langue) devraient toujours être les mêmes.</p><h2>Utiliser les deux approches si nécessaire</h2><p>Le cas de la méthode <code>mostPublishedAuthorsInYears</code> illustre le fait qu'une seule méthode peut être testée à l'aide des deux méthodes. Et peut-être même qu'il devrait l'être.</p><ul><li><p>Le fait de n'utiliser que des maquettes signifie que nous devons maintenir la maquette et que nous n'avons aucune confiance dans la mise à jour de notre système.</p></li><li><p>Le fait de n'utiliser que des tests d'intégration signifierait que nous gaspillons beaucoup de ressources sans en avoir besoin.</p></li></ul><h2>Récapitulons</h2><ul><li><p>Il est possible d'utiliser des tests d'intégration et de simulation avec Elasticsearch.</p></li><li><p>Utiliser des tests de simulation comme filet de détection rapide et seulement s'ils passent avec succès, lancer des tests avec des dépendances (par exemple en utilisant <code>./mvnw test '-Dtest=!TestInt*' &amp;&amp; ./mvnw test '-Dtest=TestInt*'</code> ou les plugins <a href="https://maven.apache.org/surefire/maven-failsafe-plugin/">Failsafe</a> et <a href="https://maven.apache.org/surefire/maven-surefire-plugin/">Surefire</a> ).</p></li><li><p>Utilisez des mocks pour tester le comportement de votre système ("lignes de code") lorsque l'intégration avec des dépendances externes n'a pas vraiment d'importance (ou pourrait même être ignorée).</p></li><li><p>Utilisez des tests d'intégration pour vérifier vos hypothèses sur les systèmes externes et leur intégration.</p></li><li><p>N'hésitez pas à tester les deux approches - si cela se justifie - en fonction des points ci-dessus.</p></li></ul><p>On pourrait faire remarquer que le fait d'être aussi strict en ce qui concerne la version (dans notre cas <code>8.15.x</code>) est excessif. L'utilisation de la seule étiquette de la version pourrait l'être, mais sachez que dans ce billet, elle sert de représentation de toutes les autres caractéristiques susceptibles de changer entre les versions.</p><p>Dans le <a href="https://www.elastic.co/search-labs/blog/automated-integration-tests-faster-elasticsearch">prochain article de la série</a>, nous verrons comment initialiser Elasticsearch dans un conteneur de test, avec des ensembles de données de test. Faites-nous savoir si vous avez construit quelque chose à partir de ce blog ou si vous avez des questions sur nos <a href="https://discuss.elastic.co/">forums de discussion</a> et le <a href="https://communityinviter.com/apps/elasticstack/elastic-community">canal Slack de la communauté.</a></p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/tests-with-mocks-and-real-elasticsearch</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/tests-with-mocks-and-real-elasticsearch</guid>
    <category><![CDATA[Java]]></category>
    <dc:creator><![CDATA[Piotr Przybyl]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7e4d003f09dfbe50/6a1709aba929cf810aae0957/b6bb727815ebdb844aeb36d5c44cdf3657f0e4bc-720x420.jpg" length="0" type="image/jpeg"/>
    <pubDate>Thu, 03 Oct 2024 00:00:00 GMT</pubDate>
  </item>
  </channel>
</rss>