Organisation des tests de votre projet Symfony

Publié le 22/12/2021 • Mis à jour le 22/12/2021

Dans cet article nous voyons comment organiser les tests d'un projet Symfony. Nous passons en revue les différents types de tests et créons des suites de tests cohérentes et robustes. C'est parti ! 😎


English language detected! 🇬🇧

  We noticed that your browser is using English. Do you want to read this post in this language?

Read the english version 🇬🇧 Close

» Publié dans "Une semaine Symfonique 782" (du 20 au 26 décembre 2021).

Prérequis

Je présumerai que vous avez au moins les connaissances fondamentales de Symfony et que vous savez ce que sont des tests automatisés.

Configuration

  • PHP 8.0
  • Symfony 5.4.9
  • PHPUnit 9.5.10

Introduction

Quand on commence à ajouter des tests, il peut être tentant d'ajouter des fichiers à la racine du répertoire tests. Mais, au fur et à mesure qu'on ajoute des fichiers, ça peut rapidement devenir le désordre. Voyons comment s'y prendre afin d'éviter ceci.

But

Nous allons passer en revue les différents types de tests avec à chaque fois un exemple. Je montrerai ce que j'appplique sur la plupart de mes projets et la structure de répertoires qui en résulte.

Cas de test (TestCase) ?

Tout d'abord, passons en revue les différents types de tests. Que dit Symfony à ce sujet ? On n'a même pas besoin de lire la documentation, on peut lancer la commande make:test :

bin/console make:test

Nous avons la sortie suivante :

 Which test type would you like?:
  [TestCase       ] basic PHPUnit tests
  [KernelTestCase ] basic tests that have access to Symfony services
  [WebTestCase    ] to run browser-like scenarios, but that don't execute JavaScript code
  [ApiTestCase    ] to run API-oriented scenarios
  [PantherTestCase] to run e2e scenarios, using a real-browser or HTTP client and a real web server

Super ! C'est un bon résumé. Voyons chacun de ces "cas de test".

TestCase > tests unitaires (tests PHPUnit)

“Un test unitaire est un morceau de code automatisé qui invoque une unité de travail dans le système et vérifie une unique supposition à propos du comportement de cette unité de travail. ”

J'ai récupéré cette définition de cet article de Royo Sherove, qui me parait être explicite. Voyons un exemple sur ce projet :

<?php

// tests/Unit/Helper/StringHelperTest.php

declare(strict_types=1);

namespace App\Tests\Unit\Helper;

use App\Helper\String\AppUnicodeString;
use App\Helper\String\StringHelper;
use PHPUnit\Framework\TestCase;

/**
 * @see StringHelper
 */
final class StringHelperTest extends TestCase
{
    /**
     * @return iterable<int, array{0:?string, 1:string}>
     */
    public function provideTestFirstCharForUnicode(): iterable
    {
        yield ['ABCD', 'A'];
        yield ['äBCD', 'ä'];
        yield [null, ''];
        yield ['', ''];
        yield ['123456789', '1'];
        yield ['नमस्ते', 'न'];
        yield ['さよなら', 'さ'];
        yield ['🙂👻🐱🌳🙃', '🙂'];
        yield ['🇫🇷🇬🇧🇺🇸🇧🇪', '🇫🇷'];
        yield ['ӚӜҾѾѮ', 'Ӛ'];
    }

    /**
     * @dataProvider provideTestFirstCharForUnicode
     *
     * @see StringHelper::s
     */
    public function testFirstCharFromS(?string $string, string $expected): void
    {
        $appString = new StringHelper();
        self::assertSame($expected, $appString->s($string)->firstChar());
    }

    /**
     * @dataProvider provideTestFirstCharForUnicode
     *
     * @see StringHelper::u
     * @see AppUnicodeString::firstChar
     */
    public function testFirstCharFromU(?string $string, string $expected): void
    {
        $stringHelper = new StringHelper();
        self::assertSame($expected, $stringHelper->u($string)->firstChar());
    }

    /**
     * @return iterable<int, array{0:?string, 1:string}>
     */
    public function provideTestFirstCharForByte(): iterable
    {
        yield ['ABCD', 'A'];
        yield ['abcd', 'a'];
        yield [null, ''];
        yield ['', ''];
        yield ['123456789', '1'];
    }

    /**
     * @dataProvider provideTestFirstCharForByte
     *
     * @see StringHelper::b
     * @see AppByteString::firstChar
     */
    public function testFirstCharFromB(?string $string, string $expected): void
    {
        $stringHelper = new StringHelper();
        self::assertSame($expected, $stringHelper->b($string)->firstChar());
    }
}

Ce test vérifie la fonction firstChar() de la classe StringHelper. On instancie la classe puis on lance des assertions avec différentes données grâce à un fournisseur de donnée PHPUnit (dataProvider). Je mets ces tests dans le répertoire tests/Unit. Veuillez noter qu'il étend la classe PHPUnit\Framework\TestCase. Cela signifie que nous ne sommes pas dans un contexte Symfony ; on utilise des classes PHP standards.

Lançons-le :

./vendor/bin/phpunit --filter=StringHelperTest
PHPUnit 9.5.10 by Sebastian Bergmann and contributors.

Testing
.........................                                         25 / 25 (100%)

Time: 00:00.035, Memory: 20.00 MB

OK (25 tests, 25 assertions)

Comme nous n'utilisons pas Symfony, ces tests sont très rapides. On peut utiliser les outils de substitution fournit par PHPUnit (getMockBuilder()) ou utiliser notre framework préféré comme Mockery ou Prophecy.

KernelTestCase > tests d'intégration

“Les tests d'intégration sont les phases durant le test d'une application ou les modules sont combinés et testés en tant que groupe. ”

Cette définition provient de Wikipédia. Cette fois nous sommes dans un contexte Symfony. Le nom du cas de test est explicite : nous avons accès au noyau (Kernel), cela signifie que nous avons accès à tous les services. Afin d'éviter de la configuration inutile, tous les services sont marqués comme publics dans l'environnement de test afin de pouvoir les récupérer avec la fonction ContainerInterface::get(). Voyons un exemple :

<?php

declare(strict_types=1);

namespace App\Tests\Integration\Twig\Extension;

use App\Twig\Extension\EnvExtension;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;

/**
 * @see EnvExtension
 */
final class EnvExtensionTest extends KernelTestCase
{
    private EnvExtension $envExtension;

    protected function setUp(): void
    {
        $this->envExtension = self::getContainer()->get(EnvExtension::class);
    }

    /**
     * @return iterable<int, array{0: string}>
     */
    public function provideGetGlobals(): iterable
    {
        yield ['php_minor_version'];
        yield ['php_version'];
        yield ['sf_major_version'];
        yield ['sf_minor_version'];
        yield ['sf_version'];
    }

    /**
     * @dataProvider provideGetGlobals
     *
     * @see EnvExtension::getGlobals
     */
    public function testGetGlobals(string $global): void
    {
        $globals = $this->envExtension->getGlobals();
        self::assertArrayHasKey($global, $globals);
        self::assertNotEmpty($globals[$global]);
    }
}

La fonction setup() récupère le service que nous voulons tester et le stocke en tant que propriété. Alors, nous pouvons l'utiliser. Celui-ci teste une extension personnalisés Twig qui ajoute dynamiquement quelques variables globales de l'environnement courant (version PHP, version Symfony...).

Lançons-le :

./vendor/bin/phpunit --filter=EnvExtensionTest
PHPUnit 9.5.10 by Sebastian Bergmann and contributors.

Testing
.                                                                   1 / 1 (100%)

Time: 00:00.267, Memory: 44.50 MB

OK (1 test, 10 assertions)

On remarque que ce test est environ dix fois plus lent que le test unitaire. Pourquoi ? Souvenez-vous que nous utilisons le noyau Symfony, et il doit être initialisé. C'est automatiquement fait dès que l'on appelle self::getContainer(). Évidemment cette phase d'initialisation n'a besoin d'être faite qu'une seule fois ; les scénarios suivants sont bien plus rapides.

ApiTestCase > scénarios orientés API

Ces tests sont un type particulier de tests fonctionnels. Je les présente avant car ils sont généralement plus simples que des tests fonctionnels classiques et l'on traite généralement uniquement avec des requêtes et réponses JSON. Le fichier ApiTestCase vient d'API Platform : c'est une bonne raison de l'installer si vous ne l'utilisez pas encore. Voici un exemple :

<?php

declare(strict_types=1);

namespace App\Tests\Api\Security;

use ApiPlatform\Core\Bridge\Symfony\Bundle\Test\ApiTestCase;
use Symfony\Component\HttpFoundation\Response;
use function Symfony\Component\String\u;
use Symfony\Contracts\HttpClient\ResponseInterface;

final class JsonLoginTest extends ApiTestCase
{
    public function testLoginOK(): void
    {
        $response = $this->login('reader', 'test');
        self::assertResponseIsSuccessful();
        $arrayResponse = $response->toArray();
        self::assertArrayHasKey('token', $arrayResponse);
        self::assertNotTrue(u($response->toArray()['token'])->isEmpty());
    }

    public function testLoginNOK(): void
    {
        $this->login('reader', 'wrong password');
        self::assertResponseStatusCodeSame(Response::HTTP_UNAUTHORIZED);
        self::assertResponseHeaderSame('content-type', 'application/json');
        self::assertJsonEquals([
            'code' => Response::HTTP_UNAUTHORIZED,
            'message' => 'Invalid credentials.',
        ]);
        self::assertJsonContains([
            'message' => 'Invalid credentials.',
        ]);
    }

    /**
     * JSON Login try with a given email and password.
     */
    public function login(string $username, string $password): ResponseInterface
    {
        return self::createClient()->request('POST', '/api/login_check', [
            'json' => compact('username', 'password'),
        ]);
    }
}

Lançons-le :

./vendor/bin/phpunit --filter=JsonLoginTest
PHPUnit 9.5.10 by Sebastian Bergmann and contributors.

Testing
..                                                                  2 / 2 (100%)

Time: 00:01.184, Memory: 56.50 MB

OK (2 tests, 6 assertions)

Cette fois, nous sommes au-dessus de la seconde même si nous n'avons que deux tests dans ce scénario. Pourquoi est-ce si lent ? Parce que cette fois, nous utilisons de vraies requêtes et réponses Symfony, tout doit donc être correctement initialisé. Encore ici, une fois cette phase d'initialisation effectuée, les tests suivants sont bien plus rapides. Dans ce test, on utilise l'assertion assertJsonEquals qui est très pratique pour vérifier le contenu d'une réponse JSON. On a aussi assertJsonContains qui cette fois permet de vérifier un contenu partiel. Cela permet d'éviter des erreurs concernant un contenu dynamique comme une date de création, par exemple, qui changerait à chaque chargement des données initiales.

WebTestCase > tests fonctionnels ou d'application

“Wikipédia : un test fonctionnel est un processus d'assurance qualité (QA) et un type de test en boite noire qui se fonde sur les spécifications du logiciel ou composant à tester. ”

Ce type de tests permet ainsi de tester une fonctionnalité complète de votre application. Cette fois, on peut utiliser l'UI et des formulaires vont pouvoir être remplis et utilisés. Cela inclut des tests de "fumée", qui garantissent qu'une page "marche" et qu’elle retourne au moins un code de retour 200 et non une erreur 500. Voyons un exemple :

<?php

declare(strict_types=1);

namespace App\Tests\Functional\Controller;

use App\Tests\WebTestCase;
use Symfony\Component\ErrorHandler\ErrorHandler;

final class AppControllerTest extends WebTestCase
{
    /**
     * @see AppController::root
     */
    public function testRootAction(): void
    {
        $client = self::createClient();
        $this->assertUrlIsRedirectedTo($client, '/', '/en');
    }

    /**
     * @see AppController::homepage
     */
    public function testHomepage(): void
    {
        $client = self::createClient();
        $this->assertResponseIsOk($client, '/en');
        $this->assertResponseIsOk($client, '/fr');
    }

    /**
     * @see ErrorHandler::handleException
     */
    public function test404(): void
    {
        $client = self::createClient();
        $client->request('GET', '/404');
        self::assertTrue($client->getResponse()->isNotFound());
    }
}

Lançons-le :

./vendor/bin/phpunit --filter=AppControllerTest
PHPUnit 9.5.10 by Sebastian Bergmann and contributors.

Testing
...                                                                 3 / 3 (100%)

Time: 00:00.354, Memory: 62.50 MB

OK (3 tests, 5 assertions)

Ici aussi, le noyau a besoin d'être initialisé. Ces tests sont assez explicites. On teste que l'adresse de la racine de site est redirigée vers la version localisée selon les préférences de langue de l'utilisateur. Ensuite, on teste des codes de status. Enfin, on teste une page 404. Pourquoi ? N'est-ce pas quelque chose géré par Symfony ? Oui, mais ces pages peuvent être personnalisées, et s'il y a une erreur dans le template Twig on peut avoir une erreur 500. Donc, autant être sûr. assertUrlIsRedirectedTo et assertResponseIsOk ne sont pas des assertions natives, mais des raccourcis pour simplifier les tests.

Tests externes

J'ai regroupé quelques tests dans une catégorie particulière, "externe". Ces tests sont fonctionnels, mais ils font des accès au réseau ; par exemple, un test vérifie que l'URL https://www.strangebuzz.com/index.php n'est pas accessible, mais redirigée. La spécificité de ces tests réside dans le fait qu'ils ont besoin du réseau pour qu'ils puissent fonctionner.

PantherTestCase > tests de bout en bout

On peut prendre la définition du bundle maker :

“Les tests bout en bout utilisent un navigateur ou un client HTTP et un vrai serveur web où l'on peut tester le JavaScript. ”

Cette fois, un vrai navigateur comme Firefox est utilisé et lancé. On le voit s'ouvrir et accéder aux pages à tester. J'ai déjà fait un article complet sur le sujet. Vous pouvez le trouver ici. Ces tests sont les plus lents, car le navigateur doit être lancé. C'est le seul type de test de ce guide à pouvoir tester complètement le JavaScript. Voici un exemple :

<?php

declare(strict_types=1);

namespace App\Tests\E2E;

use Symfony\Component\Panther\PantherTestCase;

final class BlogPost138Test extends PantherTestCase
{
    private const BUTTON_SELECTOR = '#subscribe_button_panther';
    private const ERROR_MESSAGE_SELECTOR = '#error_msg_panther';
    private const FORM_SELECTOR = '#account_create';

    /**
     * @debug make test filter=BlogPost138Test
     */
    public function testPost138(): void
    {
        $client = self::createPantherClient([
            'browser' => PantherTestCase::FIREFOX,
        ]);
        $crawler = $client->request('GET', '/en/blog/end-to-end-testing-with-symfony-and-panther');
        self::assertSelectorTextContains('h1', 'End-to-end testing with Symfony and Panther');

        // At first load, the error message is shown and the button isn't there
        self::assertSelectorExists(self::ERROR_MESSAGE_SELECTOR);
        self::assertSelectorNotExists(self::BUTTON_SELECTOR);

        // Fill the form so the subscribe button appears
        $crawler->filter(self::FORM_SELECTOR)->form([
            'account_create[login]' => 'Les',
            'account_create[password]' => 'Tilleuls',
        ]);
        $client->waitForVisibility(self::BUTTON_SELECTOR); // wait for the button to appear!

        // Ok, now the button is visble and the error message should be removed from the DOM!
        self::assertSelectorNotExists(self::ERROR_MESSAGE_SELECTOR);
        self::assertSelectorExists(self::BUTTON_SELECTOR);
    }
}

Lançons-le :

./vendor/bin/phpunit --filter=BlogPost138Test
[07:32:37] coil@mac-mini.home:/Users/coil/Sites/strangebuzz.com$ ./vendor/bin/phpunit --filter=BlogPost138Test
PHPUnit 9.5.10 by Sebastian Bergmann and contributors.

Testing
.                                                                   1 / 1 (100%)

Time: 00:09.270, Memory: 107.00 MB

OK (1 test, 5 assertions)

Comme vous pouvez le voir, ces tests sont, en effet, très lents : c'est presque dix secondes pour un seul scénario. Bien sûr, les tests suivants sont plus rapides, car le navigateur est déjà ouvert.

Voilà ; nous avons passé en revue des différents types de tests. Maintenant, voyons comme lancer tout cela. Nous allons utiliser des suites de tests.

Les suites de tests

Voici à quoi ressemble le répertoire tests désormais :

ll tests/
0 drwxr-xr-x   5 coil  staff   160B  4 déc 15:44 Api
0 drwxr-xr-x   4 coil  staff   128B 15 déc 08:25 E2E
0 drwxr-xr-x   9 coil  staff   288B  5 déc 07:44 External
0 drwxr-xr-x   4 coil  staff   128B  4 déc 17:44 Functional
0 drwxr-xr-x   7 coil  staff   224B  4 déc 17:43 Integration
0 drwxr-xr-x   6 coil  staff   192B  4 déc 17:35 Unit
8 -rw-r--r--   1 coil  staff   3,6K 10 mai  2021 WebTestCase.php

Tous les tests sont dans le répertoire correspondant à leur cas de test, facile. Tout est donc déjà stocké correctement. Cela nous permet d'exécuter chaque type de cas de test séparément des autres. Par exemple, si vous travaillez sur les tests unitaires et que vous ne voulez lancer que les tests de ce type.

Création des suites de tests

D'abord, nous allons créer les suites de tests ; dans le fichier phpunit.xml nous pouvons appliquer la configuration suivante au nœud testsuites :

    <testsuites>
        <testsuite name="all">
            <directory>tests/</directory>
        </testsuite>

        <testsuite name="unit">
            <directory>tests/Unit</directory>
        </testsuite>

        <testsuite name="integration">
            <directory>tests/Integration</directory>
        </testsuite>

        <testsuite name="api">
            <directory>tests/Api</directory>
        </testsuite>

        <testsuite name="functional">
            <directory>tests/Functional</directory>
        </testsuite>

        <testsuite name="main">
            <directory>tests/</directory>
            <exclude>tests/External</exclude>
            <exclude>tests/E2E</exclude>
        </testsuite>

        <testsuite name="external">
            <directory>tests/External/</directory>
        </testsuite>

        <testsuite name="e2e">
            <directory>tests/E2E</directory>
        </testsuite>
    </testsuites>

On doit spécifier le répertoire que nous voulons utiliser. Maintenant, pour exécuter les tests unitaires nous pouvons lancer :

./vendor/bin/phpunit --testsuite=unit

On peut aussi simplement préciser le dossier :

./vendor/bin/phpunit tests/Unit

La suite de tests "all" n'est pas obligatoire, car on peut tout lancer tous les tests en appelant PHPUnit sans argument. Cependant, je l'utilise dans mon makefile, ou j'ai une cible qui m'aide à filtrer ce que je veux lancer. Vous pouvez trouver mon makefile complet ici. Il a aussi une suite "main" qui exclue les tests externe et bout en bout, les plus lents et n'étant pas modifiés souvent.

## —— Tests ✅ —————————————————————————————————————————————————————————————————
test: phpunit.xml check ## Run tests with optionnal suite and filter
	@$(eval testsuite ?= 'all')
	@$(eval filter ?= '.')
	@$(PHPUNIT) --testsuite=$(testsuite) --filter=$(filter) --stop-on-failure

J'utilise cette cible la plupart du temps pour exécuter un fichier ou scénario particulier.

Résumé

On peut résumer les caractéristiques des différents tests avec la matrice suivante :

Nom Cas de test Répertoire Peut être lancé hors-ligne ? Teste le JavaScript ? Test rapide ?
Unit tests TestCase Unit ⚡⚡
Integration tests KernelTestCase Integration
Api tests ApiTestCase Api 🚶
Functional tests WebTestCase Functional 🚶
End-to-end tests PantherTestCase E2E 🐌
External tests WebTestCase External 🐌

Test rapide : ⚡⚡ = très rapide, ⚡ = rapide,🚶 = OK, 🐌 = lent

Maintenant que nos tests sont bien organisés, nous pouvons les exécuter en parallèle. Ce n'est pas le sujet de cet article, mais cliquez sur le bouton "plus sur le web" (à la fin de cet article) pour lire l'article "Améliorer les performances de vos tests Symfony" par Maks Rafalko ; vous trouvez de bons conseils et astuces afin d'optimiser au maximum tout ce qui peut l'être. Spoiler alert : selon votre application, vous pouvez accélérer vos tests jusqu'à un ratio de 20x !

Une chose importante à remarquer : à l'intérieur d'un dossier donné, disons Functional ; les sous-répertoires reprennent exactement la structure du dossier src/. Entre autres, le fichier de test AppContolerTest est localisé ici tests/Functional/Controller/AppControllerTest.php, et il teste src/Controller/AppController.php. De cette manière, il est aisé de voir ce qui est testé ou pas (ça ne remplace bien sûr pas un rapport complet de couverture de code).

On obtient finalement la structure de répertoire suivante :

tests/
├── Api
│   ├── Controller
│   │   └── ApiControllerTest.php
│   ├── Entity
│   │   ├── ArticleTest.php
│   │   └── BookTest.php
│   └── Security
│       └── JsonLoginTest.php
├── E2E
│   ├── BlogPost138Test.php
│   └── BlogPost138ZenstruckTest.php
├── External
│   ├── Controller
│   │   ├── FrontControllerTest.php
│   │   ├── Post
│   │   │   └── Post26TraitTest.php
│   │   ├── Snippet
│   │   │   ├── Snippet99Test.php
│   │   │   └── SnippetsControllerTest.php
│   │   └── ToolsControllerTest.php
│   ├── FeedburnerTest.php
│   └── SslTest.php
├── Functional
│   ├── Controller
│   │   ├── Admin
│   │   │   └── DashboardControllerTest.php
│   │   ├── AppControllerTest.php
│   │   ├── BlogControllerTest.php
│   │   ├── GameControllerTest.php
│   │   ├── LegacyControllerTest.php
│   │   ├── Post
│   │   │   ├── Post13TraitTest.php
│   │   │   └── Post59TraitTest.php
│   │   ├── RedirectControllerTest.php
│   │   ├── SearchControllerTest.php
│   │   ├── SearchPart1ControllerTest.php
│   │   ├── SearchPart2ControllerTest.php
│   │   ├── SitemapControllerTest.php
│   │   ├── SnippetsControllerTest.php
│   │   ├── StaticControllerTest.php
│   │   ├── SuggestControllerTest.php
│   │   ├── TagControllerTest.php
│   │   └── ToolsControllerTest.php
│   └── Entity
│       └── ArticleTypeTest.php
├── Integration
│   ├── Command
│   │   └── ShowVersionCommandTest.php
│   ├── Controller
│   │   └── Snippets
│   │       ├── Snippet100Test.php
│   │       ├── Snippet105Test.php
│   │       ├── Snippet107Test.php
│   │       ├── Snippet108Test.php
│   │       ├── Snippet114Test.php
│   │       ├── Snippet115Test.php
│   │       ├── Snippet116Test.php
│   │       ├── Snippet12Test.php
│   │       ├── Snippet131Test.php
│   │       ├── Snippet132Test.php
│   │       ├── Snippet142Test.php
│   │       ├── Snippet147Test.php
│   │       ├── Snippet14Test.php
│   │       ├── Snippet157Test.php
│   │       ├── Snippet158Test.php
│   │       ├── Snippet160Test.php
│   │       ├── Snippet168Test.php
│   │       ├── Snippet173Test.php
│   │       ├── Snippet176Test.php
│   │       ├── Snippet177Test.php
│   │       ├── Snippet20Test.php
│   │       ├── Snippet2Test.php
│   │       ├── Snippet30Test.php
│   │       ├── Snippet32Test.php
│   │       ├── Snippet33Test.php
│   │       ├── Snippet42Test.php
│   │       ├── Snippet49Test.php
│   │       ├── Snippet50Test.php
│   │       ├── Snippet58Test.php
│   │       ├── Snippet61Test.php
│   │       ├── Snippet6Test.php
│   │       ├── Snippet70Test.php
│   │       ├── Snippet71Test.php
│   │       ├── Snippet74Test.php
│   │       ├── Snippet76Test.php
│   │       ├── Snippet7Test.php
│   │       └── Snippet8Test.php
│   ├── Repository
│   │   └── BaseRepositoryTraitTest.php
│   ├── Twig
│   │   ├── Extension
│   │   │   ├── EnvExtensionTest.php
│   │   │   ├── SeoExtensionTest.php
│   │   │   ├── TypeExtensionTest.php
│   │   │   └── UrlExtensionTest.php
│   │   └── Snippet152Test.php
│   └── Type
│       └── Post59UnitTest.php
├── Unit
│   ├── Helper
│   │   └── StringHelperTest.php
│   ├── Log
│   │   └── Processor
│   │       └── EnvProcessorTest.php
│   ├── Tools
│   │   └── FilesystemTest.php
│   └── Utility
│       └── SpamCheckerTest.php
└── WebTestCase.php

28 directories, 81 files

Conclusion

Les tests sont essentiels. On peut toujours écrire du code "sale" et prendre des raccourcis, mais seulement si vous avez de bons tests, car cela permet d'améliorer et de refactoriser le code avec confiance sans avoir peur de tout casser. Donc, votre suite de tests est probablement la partie la plus critique de votre application. Faites la briller, optimisez-la, chouchoutez là , rendez-la stable (et ennuyeuse) ; c'est la garantie de pouvoir faire grossir votre application dans des conditions saines.

Et voilà ! J'espère que vous avez aimé. Découvrez d'autres informations en rapport à cet article avec les liens ci-dessous. Comme toujours, retours, likes et retweets sont les bienvenus. (voir la boîte ci-dessous) À tantôt ! COil. 😊

  Lire la doc  Plus sur le web


A vous de jouer !

Ces articles vous ont été utiles ? Vous pouvez m'aider à votre tour de plusieurs manières : (cf le tweet à droite pour me contacter )

  • Me remonter des erreurs ou typos.
  • Me remonter des choses qui pourraient être améliorées.
  • Aimez et retweetez !
  • Suivez moi sur Twitter
  • Inscrivez-vous au flux RSS.
  • Cliquez sur les boutons Plus sur Stackoverflow pour me faire gagner des badges "annonceur" 🏅.

Merci d'avoir tenu jusque ici et à très bientôt sur Strangebuzz ! 😉

COil