linkedin-ico github-ico

Les Tests Fonctionnels avec Meilisearch et Symfony

meilisearch

C'est quoi Meilisearch

Meilisearch est un moteur de recherche ultra-rapide qui permet d'intégrer des fonctionnalités de recherche à vos applications. Son installation et sa configuration sont simples, offrant une expérience utilisateur exceptionnelle dès les premières requêtes. Grâce à sa capacité à fournir des résultats en quelques millisecondes, Meilisearch se distingue comme une solution idéale pour les développeurs cherchant à améliorer l'accessibilité et l'efficacité de leurs recherches.

Pourquoi choisir Meilisearch

La décision d'adopter Meilisearch pour les projets Symfony repose principalement sur la qualité exceptionnelle de son bundle et sa performance inégalée. Le bundle Meilisearch pour Symfony facilite grandement l'intégration, rendant le processus d'indexation des données à la fois fluide et efficace. Cette combinaison de facilité d'intégration et de performance de recherche de haut niveau fait de Meilisearch le choix privilégié pour les développeurs soucieux de fournir une expérience de recherche rapide et précise à leurs utilisateurs. La capacité de Meilisearch à offrir des réponses instantanées, même avec de grands volumes de données, assure que votre application reste réactive et conviviale, un avantage décisif dans le développement d'applications modernes.

Test Fonctionnel, oui, mais pas trop

Les tests fonctionnels sont un pilier essentiel dans le développement d'applications robustes et fiables. Ils permettent de vérifier que l'application fonctionne comme prévu du point de vue de l'utilisateur final, en simulant des scénarios d'utilisation réels. L'objectif principal est de s'assurer que toutes les fonctionnalités répondent aux exigences spécifiées, avant que le produit ne soit livré au client.

Pourquoi les tests sont-ils indispensables ?

Les tests, qu'ils soient fonctionnels ou unitaires, jouent un rôle critique dans la prévention des bugs, la garantie de la qualité et la réduction des coûts de maintenance à long terme. En effectuant des tests fonctionnels, les développeurs peuvent :

  • Détecter les erreurs tôt : Identifier et corriger les bugs dès les premières étapes du développement évite des complications futures et réduit les coûts de débogage et de correction.
  • Améliorer la qualité du produit : Les tests garantissent que le produit fini fonctionne selon les attentes, offrant ainsi une meilleure expérience utilisateur et augmentant la satisfaction client.
  • Faciliter les mises à jour et les évolutions : Avec une suite de tests fonctionnels solide, les développeurs peuvent effectuer des modifications et ajouter des fonctionnalités en toute confiance, sachant que les tests existants aideront à identifier immédiatement tout impact négatif sur les fonctionnalités existantes.

L'équilibre entre Tests Unitaires et Tests Fonctionnels

L'établissement d'un bon équilibre entre tests unitaires (TU) et tests fonctionnels (TF) est crucial dans le développement logiciel. Les tests unitaires, ciblant des composants spécifiques du code, sont essentiels pour une couverture de test complète et sont appréciés pour leur rapidité d'exécution.

L'importance d'une bonne couverture de TU

Une solide couverture de tests unitaires est primordiale pour plusieurs raisons :

  • Rapidité : Les TU s'exécutent rapidement, permettant aux développeurs de vérifier fréquemment leur code pour des cycles de feedback courts. Cette rapidité est vitale pour le développement agile, facilitant les intégrations continues et le déploiement continu (CI/CD).
  • Précision : En se concentrant sur des portions spécifiques du code, les TU permettent d'identifier précisément où les erreurs se produisent, simplifiant ainsi le débogage.
  • Efficacité : Les tests unitaires peuvent être exécutés fréquemment sans impacter significativement le temps de développement, ce qui les rend particulièrement efficaces pour prévenir les régressions lors des mises à jour du code.

Toutefois, cela ne signifie pas que les tests fonctionnels doivent être négligés. Ces derniers, en testant l'application de bout en bout, assurent que l'ensemble des composants fonctionne harmonieusement selon les attentes des utilisateurs. Le ratio optimal TU/TF varie selon le projet, mais une règle générale consiste à maximiser la couverture par les TU pour leurs avantages en termes de rapidité et d'efficacité, tout en complétant avec des TF pour couvrir les aspects intégratifs et les scénarios utilisateur complexes.

Un équilibre judicieux entre TU et TF contribue à un cycle de développement plus fluide et à une assurance qualité renforcée, garantissant ainsi que l'application est non seulement fonctionnelle mais aussi robuste face aux changements et aux évolutions futures..

Comment faire le test

La réalisation de tests fonctionnels efficaces pour vérifier l'intégration avec Meilisearch dans un projet Symfony nécessite une approche méthodique. Voici un exemple de code pour tester l'écriture de données :

<?php

namespace App\Tests\Functional\Handler;

use App\Entity\Fqdn;
use App\Handler\FqdnCreateHandler;
use App\Message\FqdnsCreate;
use App\Repository\DomainRepository;
use Doctrine\ORM\EntityManagerInterface;
use Meilisearch\{Bundle\SearchService, Client, Contracts\TasksQuery};
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;

class FqdnCreateHandlerTest extends KernelTestCase
{
    private EntityManagerInterface $entityManager;
    private DomainRepository $domainRepository;
    private SearchService $searchService;
    private Client $client;
    private string $indexName;
    private int $numberOfDocuments;

    protected function setUp(): void
    {
        self::bootKernel();

        $this->entityManager = $this->getContainer()->get(EntityManagerInterface::class);
        $this->domainRepository = $this->getContainer()->get(DomainRepository::class);

        $this->searchService = $this->getContainer()->get(SearchService::class);
        $this->client = $this->getContainer()->get(Client::class);

        $this->indexName = $this->searchService->searchableAs(Fqdn::class);
        $stats = $this->client->stats()['indexes'];
        $this->numberOfDocuments = $stats[$this->indexName]['numberOfDocuments'];
    }

    public function testMultipleFqdnsCreate(): void
    {
        $fqdnsCreateMessage = new FqdnsCreate();
        $fqdnsCreateMessage->fqdns = ['test1.example.com', 'test2.example.com'];

        $handler = $this->getContainer()->get(FqdnCreateHandler::class);
        $handler->multipleMessages($fqdnsCreateMessage);

        $queryOption = new TasksQuery();
        $queryOption->setStatuses(['enqueued', 'processing']);

        // wait for all tasks to be processed
        do {
            usleep(5000);
        } while ($this->client->getTasks($queryOption)->count() > 0);

        $stats = $this->client->stats()['indexes'];
        $this->assertEquals($this->numberOfDocuments + count($fqdnsCreateMessage->fqdns), $stats[$this->indexName]['numberOfDocuments']);
    }

    /**
     * remove all example.com fqdns.
     */
    protected function tearDown(): void
    {
        $domain = $this->domainRepository->findOneBy(['domain' => 'example']);
        $taskUids = [];

        foreach ($domain->getFqdns() as $fqdn) {
            $taskUids[] = $this->searchService->remove(
                $this->entityManager,
                $fqdn
            )[0][$this->indexName]['taskUid'];
        }

        $this->client->waitForTasks($taskUids);
    }
}

Gérer l'Asynchronisme avec Meilisearch dans les Tests Fonctionnels

Lorsqu'on intègre Meilisearch dans un projet Symfony, il est crucial de comprendre que l'écriture de données se fait de manière asynchrone. Cette particularité implique que les données envoyées à l'index ne sont pas immédiatement disponibles pour recherche ou pour toute autre opération. Cette asynchronicité, tout en optimisant les performances et la scalabilité de Meilisearch, introduit un défi spécifique lors de l'élaboration des tests fonctionnels.

Vérification des Opérations Asynchrones : Deux Approches

Pour s'assurer que les données ont été correctement indexées, deux méthodes principales peuvent être utilisées dans le cadre des tests fonctionnels :

Attente Active des Tâches : Cette approche consiste à interroger périodiquement l'état des tâches jusqu'à ce que toutes soient traitées. Cela peut être réalisé en implémentant une boucle d'attente qui vérifie régulièrement l'état des tâches en cours. Si toutes les tâches, y compris celles d'indexation, ont atteint un état final (comme succeeded), on peut alors procéder à la vérification des données dans l'index.

Attente de Tâches Spécifiques : Lorsque l'identifiant unique (UID) de la tâche est connu, une alternative plus directe consiste à attendre la fin de cette tâche spécifique. Cette méthode est particulièrement efficace pour les tests fonctionnels ciblés, où l'on souhaite vérifier l'issue d'une opération d'indexation précise. En utilisant l'UID de la tâche, on peut interroger directement son statut et, une fois la tâche terminée, effectuer les assertions nécessaires pour valider le test.

Le code fourni illustre parfaitement l'application de la première méthode, où une boucle d'attente est mise en place jusqu'à ce que toutes les tâches soient traitées. Cette approche garantit que les tests fonctionnels ne procèdent à la vérification des conditions attendues que lorsque l'état de l'index est stable et reflète les dernières opérations d'écriture.

Conclusion

La compréhension et la gestion correcte de l'asynchronisme sont essentielles lors de l'intégration de Meilisearch dans vos projets Symfony, notamment pour l'écriture de tests fonctionnels fiables et précis. Que ce soit par une attente active des tâches ou par l'attente de tâches spécifiques, ces méthodes permettent de s'assurer que l'état de l'index est conforme aux attentes avant de procéder aux assertions des tests, offrant ainsi une garantie supplémentaire sur la qualité et la fiabilité de l'intégration des données.