Créé le
Au sommaire :
Dans le but de travailler avec une démarche orientée craftmanship, nous allons dans de prochains articles utiliser la méthode Test Driven Development (TDD)
En d'autres termes et pour faire simple nous allons écrire les tests avant nos méthodes.
Dans cet articles nous allons donc définir les principaux tests à réaliser lors du développement d'une application web.
Nous verrons également quels bundles installer avec Symfony 7.
Voici la table des matières de ce qui nous attend :
N'hésitez pas à consulter les prérequis de ce projet si vous ne l'avez pas déjà fait fait :)
1. Pourquoi écrire des tests ?
Dans un premier temps, pourquoi écrire des tests ?
En effet, écrire des tests ne se fait pas tout seul et par conséquent cela prendra du temps et donc coûtera de l'argent.
De plus, si le projet ne comprend que quelques page on peut-être tenté de se dire que l'on vérifiera le bon fonctionnement de chacune d'elle à chaque fois que l'on modifiera le code source du projet.
Ce raisonnement pourrait fonctionner pour, par exemple, un site vitrine réalisé en une fois et qui ne souhaite pas évoluer dans le temps.
Mais dans le réalité la majorité des sites évoluent, les clients ont de nouvelles idées, de nouveaux besoins et il en emmerge de nouvelles fonctionnalités à créer.
Il faudra donc modifier le code source et vérifier à chaque fois que tout fonctionne bien.
Un autre point à prendre en compte est celui de la sécurité.
En effet, si vous mettez à jour les dépendances de votre application (si vous travaillez avec un framework), les interpréteurs (par exemple PHP), etc ... il faudra encore une fois tester toutes les pages de votre projet.
Vérifier à chaque modification que votre projet fonctionne toujours correctement prends du temps.
Et plus vous ajouterez de fonctionnalités plus cela vous prendra de temps.
Il faut bien comprendre que tester une page, ce n'est pas juste l'afficher.
Il faut aussi vérifier que chaque fonctionnalité présente le comportement attendu.
Par exemple, si l'on souhaite tester un formulaire de création de compte, il faut vérifier que :
- la page renvoie bien un code http 200
- les informations fournies par l'utilisateur sont correctes (adresse email, nombre de caractéres minimums pour le mot de passe, etc ...)
- il est possible d'enregistrer un nouveau compte
- une erreur s'affiche si le compte existe déjà
- un email est bien envoyé à l'utilisateur
- il n'est pas possible de réaliser une injection SQL
- etc ...
Et il faut bien l'avouer, toujours répéter ces mêmes tests n'est pas très enrichissant.
On peut également citer la notoriété de l'application vis-a-vis de ses utilisateur.
Une application qui présente de nouveaux problèmes à chaque mise à jour donnera une mauvaise image.
C'est pourquoi il est utile d'écrire des tests pour automatiser tout cela afin de gagner du temps et de minimiser la possibilité d'envoyer des bugs en production.
Chaque cas de figure pourra être couvert par un test et vous pourrez voir en quelques secondes / minutes si les modifications que vous avez réalisé dans le code entraine une régression sur une ou plusieurs fonctionnalités de votre application.
Il sera également possible de faire jouer automatiquement ces tests via une pipeline de votre dépôt de code mais nous verrons cela dans un prochain article sur l'intégration continue (CI).
2. Les différents types de tests
Une application peut subir des tests très variés.
Dans cet article nous allons nous limiter à tester le code et son bon fonctionnement.
Donc, pour faire au plus simple, nous utiliserons les 3 types de tests suivants :
- Les tests unitaires
-
Les tests unitaire vont nous permettre de tester indépendemment chacune de nos méthodes.
Par exemple, imaginons une méthode add() qui prend en paramètre les nombres $x et $y et qui en retourne la somme.
Nous pouvons tester sont bon fonctionnement avec add(1, 2) qui doit être égal à 3.
De même add(2, 3) sera égal à 5.
etc ... - Les tests d'intégrations
-
Les tests d'intégration nous permettrons de vérifier le comportement d'un enchainement de méthodes.
Bien souvent ces tests utiliseront d'autres programmes comme par exemple une base de données.
Nous pourrions ainsi tester qu'il est possible d'enregistrer un nouvel utilisateur en base de données sans déclancher d'anomalies. - Les tests fonctionnels
-
Les tests fonctionnels, aussi appelés tests end-to-end, permettent de vérifier le bon fonctionnement de l'application par le biais d'un navigateur web.
Cela consiste à utiliser le moteur d'un navigateur web afin d'afficher les pages d'un site et d'effectuer automatiquement des intéractions afin de tester les différents comportements.
Il est ainsi possible d'afficher un formulaire de contact et de vérifier son comportement selon les informations qui lui sont renseignées.
3. La pyramide des tests
La pyramide des tests est un modèle qui prends en compte les tests sur 3 niveaux.
Chaque niveau correspond aux 3 types de tests que nous avons vu précédemment.
Ce modèle met en avant le temps pris par ces différents types de tests.
Les tests unitaires représentent la base de la pyramide des tests.
Ce sont les plus rapides à éxécuter car ils ne testent que la logique des méthodes.
Dans la majorité des cas, ces tests seront les plus nombreux et permettrons aux développeurs de vérifier rapidement le bon fonctionnement de leur code.
Viennent ensuite les tests d'intégrations.
Ces tests seront plus lents à réaliser que les tests unitaires car ils mettront en oeuvres des applications tierces.
Ils permettrons de vérifier le bon fonctionnement d'un enchainement de méthodes.
Les tests fonctionnels représentent le haut de la pyramide des tests.
Ces test emploient un navigateur headless qui vérifiera le comportement d'une fonctionnalité de bout en bout.
Ce sont donc les moins nombreux mais les plus lents à exécuter.
4. Trouver les bundles Symfony sur Packagist
Il est possible que dans les prochains chapitres vous vous demandiez comment trouver les bundles que nous allons installer.
C'est très simple, il existe un site nommé "Packagist" qui regroupe toutes sortes d'extensions pour des frameworks PHP, dont les bundles Symfony.
N'hésitez pas à consulter le site "Packagist" pour trouver les bundles dont vous avez besoin.
5. Installer le bundle PHPUnit
Commençons par installer le bundle PHPUnit
Il nous faudra pour cela nous rendre dans le container "blog-server" grace à la commande suivante :
docker exec -ti blog-server /bin/bash
Une fois dans le container, cette commande nous permettra d'installer PHPUnit :
composer require --dev phpunit/phpunit
Et vous devriez avoir un résultat semblable à celui-ci :
root@9afcc3f699a3:/var/www/html# composer require --dev phpunit/phpunit
./composer.json has been updated
Running composer update phpunit/phpunit
Loading composer repositories with package information
Updating dependencies
Lock file operations: 26 installs, 0 updates, 0 removals
- Locking myclabs/deep-copy (1.12.0)
- Locking nikic/php-parser (v5.1.0)
- Locking phar-io/manifest (2.0.4)
- Locking phar-io/version (3.2.1)
- Locking phpunit/php-code-coverage (11.0.5)
- Locking phpunit/php-file-iterator (5.0.1)
- Locking phpunit/php-invoker (5.0.1)
- Locking phpunit/php-text-template (4.0.1)
- Locking phpunit/php-timer (7.0.1)
- Locking phpunit/phpunit (11.2.7)
- Locking sebastian/cli-parser (3.0.2)
- Locking sebastian/code-unit (3.0.1)
- Locking sebastian/code-unit-reverse-lookup (4.0.1)
- Locking sebastian/comparator (6.0.1)
- Locking sebastian/complexity (4.0.1)
- Locking sebastian/diff (6.0.2)
- Locking sebastian/environment (7.2.0)
- Locking sebastian/exporter (6.1.3)
- Locking sebastian/global-state (7.0.2)
- Locking sebastian/lines-of-code (3.0.1)
- Locking sebastian/object-enumerator (6.0.1)
- Locking sebastian/object-reflector (4.0.1)
- Locking sebastian/recursion-context (6.0.2)
- Locking sebastian/type (5.0.1)
- Locking sebastian/version (5.0.1)
- Locking theseer/tokenizer (1.2.3)
Writing lock file
Installing dependencies from lock file (including require-dev)
Package operations: 26 installs, 0 updates, 0 removals
- Downloading sebastian/version (5.0.1)
- Downloading sebastian/type (5.0.1)
- Downloading sebastian/recursion-context (6.0.2)
- Downloading sebastian/object-reflector (4.0.1)
- Downloading sebastian/object-enumerator (6.0.1)
- Downloading sebastian/global-state (7.0.2)
- Downloading sebastian/exporter (6.1.3)
- Downloading sebastian/environment (7.2.0)
- Downloading sebastian/diff (6.0.2)
- Downloading sebastian/comparator (6.0.1)
- Downloading sebastian/code-unit (3.0.1)
- Downloading sebastian/cli-parser (3.0.2)
- Downloading phpunit/php-timer (7.0.1)
- Downloading phpunit/php-text-template (4.0.1)
- Downloading phpunit/php-invoker (5.0.1)
- Downloading phpunit/php-file-iterator (5.0.1)
- Downloading theseer/tokenizer (1.2.3)
- Downloading nikic/php-parser (v5.1.0)
- Downloading sebastian/lines-of-code (3.0.1)
- Downloading sebastian/complexity (4.0.1)
- Downloading sebastian/code-unit-reverse-lookup (4.0.1)
- Downloading phpunit/php-code-coverage (11.0.5)
- Downloading phar-io/version (3.2.1)
- Downloading phar-io/manifest (2.0.4)
- Downloading myclabs/deep-copy (1.12.0)
- Downloading phpunit/phpunit (11.2.7)
- Installing sebastian/version (5.0.1): Extracting archive
- Installing sebastian/type (5.0.1): Extracting archive
- Installing sebastian/recursion-context (6.0.2): Extracting archive
- Installing sebastian/object-reflector (4.0.1): Extracting archive
- Installing sebastian/object-enumerator (6.0.1): Extracting archive
- Installing sebastian/global-state (7.0.2): Extracting archive
- Installing sebastian/exporter (6.1.3): Extracting archive
- Installing sebastian/environment (7.2.0): Extracting archive
- Installing sebastian/diff (6.0.2): Extracting archive
- Installing sebastian/comparator (6.0.1): Extracting archive
- Installing sebastian/code-unit (3.0.1): Extracting archive
- Installing sebastian/cli-parser (3.0.2): Extracting archive
- Installing phpunit/php-timer (7.0.1): Extracting archive
- Installing phpunit/php-text-template (4.0.1): Extracting archive
- Installing phpunit/php-invoker (5.0.1): Extracting archive
- Installing phpunit/php-file-iterator (5.0.1): Extracting archive
- Installing theseer/tokenizer (1.2.3): Extracting archive
- Installing nikic/php-parser (v5.1.0): Extracting archive
- Installing sebastian/lines-of-code (3.0.1): Extracting archive
- Installing sebastian/complexity (4.0.1): Extracting archive
- Installing sebastian/code-unit-reverse-lookup (4.0.1): Extracting archive
- Installing phpunit/php-code-coverage (11.0.5): Extracting archive
- Installing phar-io/version (3.2.1): Extracting archive
- Installing phar-io/manifest (2.0.4): Extracting archive
- Installing myclabs/deep-copy (1.12.0): Extracting archive
- Installing phpunit/phpunit (11.2.7): Extracting archive
Generating autoload files
52 packages you are using are looking for funding.
Use the `composer fund` command to find out more!
Symfony operations: 1 recipe (a5ce6203c7486d3724fe7b1085282a14)
- Configuring phpunit/phpunit (>=9.6): From github.com/symfony/recipes:main
Executing script cache:clear [OK]
Executing script assets:install public [OK]
What's next?
Some files have been created and/or updated to configure your new packages.
Please review, edit and commit them: these files are yours.
No security vulnerability advisories found.
Using version ^11.2 for phpunit/phpunit
6. Le fichier de configuration de PHPUnit
Suite à l'installation de PHPUnit, nous disposons maintenant d'un fichier "phpunit.xml.dist" qui a été créé à la racine du projet.
Ce fichier permettra de configurer divers comportements des tests lancés par PHPUnit.
Par défaut, voici à quoi il ressemble :
<?xml version="1.0" encoding="UTF-8"?>
<!-- https://phpunit.readthedocs.io/en/latest/configuration.html -->
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
backupGlobals="false"
colors="true"
bootstrap="tests/bootstrap.php"
convertDeprecationsToExceptions="false"
>
<php>
<ini name="display_errors" value="1" />
<ini name="error_reporting" value="-1" />
<server name="APP_ENV" value="test" force="true" />
<server name="SHELL_VERBOSITY" value="-1" />
<server name="SYMFONY_PHPUNIT_REMOVE" value="" />
<server name="SYMFONY_PHPUNIT_VERSION" value="9.5" />
</php>
<testsuites>
<testsuite name="Project Test Suite">
<directory>tests</directory>
</testsuite>
</testsuites>
<coverage processUncoveredFiles="true">
<include>
<directory suffix=".php">src</directory>
</include>
</coverage>
<listeners>
<listener class="Symfony\Bridge\PhpUnit\SymfonyTestsListener" />
</listeners>
<extensions>
</extensions>
</phpunit>
7. Installer le bundle Panther
Le bundle Panther permet de réaliser des tests end-to-end en utilisant le moteur de rendu de différents navigatuers internet.
Il permettra donc de réaliser les tests fonctionnels que nous souhaiterons réaliser sur notre application.
composer require --dev symfony/panther
Mais le résultat n'est pas vraiment celui que nous attendions :
dev@DESKTOP-2291OCF:~/workstation/contribution-blog-symfony-7-docker$ docker exec -ti blog-server /bin/bash
root@9afcc3f699a3:/var/www/html# composer require --dev symfony/panther
./composer.json has been updated
Running composer update symfony/panther
Loading composer repositories with package information
Restricting packages listed in "symfony/symfony" to "7.0.*"
Updating dependencies
Your requirements could not be resolved to an installable set of packages.
Problem 1
- symfony/panther v2.1.1 requires php-webdriver/webdriver ^1.8.2 -> satisfiable by php-webdriver/webdriver[1.8.2, ..., 1.15.1].
- symfony/panther[v0.1.0, ..., v0.4.1] require symfony/browser-kit ^4.0 -> found symfony/browser-kit[v4.0.0, ..., v4.4.44] but these were not loaded, likely because it conflicts with another require.
- symfony/panther[v0.5.0, ..., v0.5.2] require symfony/browser-kit ^4.3 -> found symfony/browser-kit[v4.3.0, ..., v4.4.44] but these were not loaded, likely because it conflicts with another require.
- symfony/panther[v0.6.0, ..., v0.7.1] require symfony/browser-kit ^4.3 || ^5.0 -> found symfony/browser-kit[v4.3.0, ..., v4.4.44, v5.0.0, ..., v5.4.40] but these were not loaded, likely because it conflicts with another require.
- symfony/panther[v0.8.0, ..., v0.9.0, v1.0.0, ..., v1.1.2] require symfony/browser-kit ^4.4 || ^5.0 -> found symfony/browser-kit[v4.4.0, ..., v4.4.44, v5.0.0, ..., v5.4.40] but these were not loaded, likely because it conflicts with another require.
- symfony/panther[v2.0.0, ..., v2.1.0] require symfony/browser-kit ^5.3 || ^6.0 -> found symfony/browser-kit[v5.3.0, ..., v5.4.40, v6.0.0, ..., v6.4.8] but these were not loaded, likely because it conflicts with another require.
- php-webdriver/webdriver[1.8.0, ..., 1.8.3] require php ^5.6 || ~7.0 -> your php version (8.3.7) does not satisfy that requirement.
- php-webdriver/webdriver[1.9.0, ..., 1.15.1] require ext-zip * -> it is missing from your system. Install or enable PHP's zip extension.
- Root composer.json requires symfony/panther * -> satisfiable by symfony/panther[v0.1.0, ..., v0.9.0, v1.0.0, ..., v1.1.2, v2.0.0, v2.0.1, v2.1.0, v2.1.1].
To enable extensions, verify that they are enabled in your .ini files:
- /usr/local/etc/php/php.ini
- /usr/local/etc/php/conf.d/apcu.ini
- /usr/local/etc/php/conf.d/docker-php-ext-intl.ini
- /usr/local/etc/php/conf.d/docker-php-ext-opcache.ini
- /usr/local/etc/php/conf.d/docker-php-ext-pdo_mysql.ini
- /usr/local/etc/php/conf.d/docker-php-ext-sodium.ini
- /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini
You can also run `php --ini` in a terminal to see which files are used by PHP in CLI mode.
Alternatively, you can run Composer with `--ignore-platform-req=ext-zip` to temporarily ignore these required extensions.
You can also try re-running composer require with an explicit version constraint, e.g. "composer require symfony/panther:*" to figure out if any version is installable, or "composer require symfony/panther:^2.1" if you know which you need.
Installation failed, reverting ./composer.json and ./composer.lock to their original content.
Comme nous pouvons le constater, nous avons besoin de l'extension zip pour PHP.
Nous allons donc commencer par ajouter la librairie libzip-dev dans notre conteneur Docker.
Dans un premier temps, il nous faut stopper les conteneurs :
docker compose stop
Nous pouvons maintenant éditer le fichier Dockerfile afin d'y ajouter la librairie libzip-dev :
# ./automation/docker/Dockerfile
[...]
RUN apt-get update && apt-get install -y \
# Used by entrypoint
acl \
# Used by Symfony CLI
git \
# intl dependancy
libicu-dev \
# usefull to edit files
nano \
# Used to install composer
wget \
# Used with Symfony bundle
libzip-dev \
# Used by composer
zip
[...]
Reconstruisons notre image Docker :
docker compose build
Et relançons les conteneurs :
docker compose up -d
Nous pouvons maintenant installer ext-zip via composer.
Entrons dans le conteneur blog-server :
docker exec -ti blog-server /bin/bash
Et lançons l'installation de ext-zip :
composer require ext-zip
Nous allons profiter de ce petit debug pour mettre à jour l'ensemble des bundles :
composer update
Le bundle Panther peut désormais être installé sans problème :
root@b122c4ca4a77:/var/www/html# composer require --dev symfony/panther
./composer.json has been updated
Running composer update symfony/panther
Loading composer repositories with package information
Restricting packages listed in "symfony/symfony" to "7.0.*"
Updating dependencies
Lock file operations: 8 installs, 0 updates, 0 removals
- Locking masterminds/html5 (2.9.0)
- Locking php-webdriver/webdriver (1.15.1)
- Locking symfony/browser-kit (v7.0.8)
- Locking symfony/dom-crawler (v7.0.8)
- Locking symfony/http-client (v7.0.10)
- Locking symfony/http-client-contracts (v3.5.0)
- Locking symfony/panther (v2.1.1)
- Locking symfony/process (v7.0.8)
Writing lock file
Installing dependencies from lock file (including require-dev)
Package operations: 8 installs, 0 updates, 0 removals
- Downloading masterminds/html5 (2.9.0)
- Downloading symfony/http-client-contracts (v3.5.0)
- Downloading symfony/process (v7.0.8)
- Downloading symfony/http-client (v7.0.10)
- Downloading symfony/dom-crawler (v7.0.8)
- Downloading symfony/browser-kit (v7.0.8)
- Downloading php-webdriver/webdriver (1.15.1)
- Downloading symfony/panther (v2.1.1)
- Installing masterminds/html5 (2.9.0): Extracting archive
- Installing symfony/http-client-contracts (v3.5.0): Extracting archive
- Installing symfony/process (v7.0.8): Extracting archive
- Installing symfony/http-client (v7.0.10): Extracting archive
- Installing symfony/dom-crawler (v7.0.8): Extracting archive
- Installing symfony/browser-kit (v7.0.8): Extracting archive
- Installing php-webdriver/webdriver (1.15.1): Extracting archive
- Installing symfony/panther (v2.1.1): Extracting archive
Generating autoload files
57 packages you are using are looking for funding.
Use the `composer fund` command to find out more!
Symfony operations: 1 recipe (a9049ecf98426b01d32843512566cd34)
- Configuring symfony/panther (>=1.0): From github.com/symfony/recipes:main
- WARNING symfony/panther (>=1.0): From github.com/symfony/recipes:main
The recipe for this package contains some Docker configuration.
This may create/update compose.yaml or update Dockerfile (if it exists).
Do you want to include Docker configuration from recipes?
[y] Yes
[n] No
[p] Yes permanently, never ask again for this project
[x] No permanently, never ask again for this project
(defaults to y): n
Executing script cache:clear [OK]
Executing script assets:install public [OK]
What's next?
Some files have been created and/or updated to configure your new packages.
Please review, edit and commit them: these files are yours.
symfony/panther instructions:
* Install ChromeDriver or geckodriver with
the package manager of your Operating System.
Alternatively, use Browser Driver Installer:
composer require --dev dbrekelmans/bdi
vendor/bin/bdi detect drivers
This step is not necessary when using Docker.
No security vulnerability advisories found.
Using version ^2.1 for symfony/panther
8. Conclusion
Nous disposons maintenant de tous les éléments nécessaires à la rédaction de tests pour notre application.
Nous nous attarderons sur la configuration de ces bundles dans de prochains articles.
a. Code source du projet
Si vous souhaitez utiliser ce projet dans l'état où il se trouve à la fin de cet article, vous pouvez le récupérer depuis mon repo Github.
Vous devrez pour cela lancer la commande "git clone" dans votre terminal afin de récupérer le tag v2.0.
N'oubliez pas de remplacer "mon_projet" par le nom que vous souhaitez utiliser pour votre répertoire d'installation :
git clone --depth 1 --branch v2.0 https://github.com/cpesin/contribution-blog-symfony-7-docker.git mon_projet
b. Sources
- Bash : Manuel de référence de Bash
- Docker build : Documentation de Docker build
- Docker compose : Documentation de Docker compose
- Site PHPUnit : Site PHPUnit
- Bundle Symfony Panther : Dépôt de code du bundle Symfony Panther
- Librairie libzip-dev : Documentation de la librairie libzip-dev