Comme dans tout projet logiciel, il est primordial de supporter le développement de fonctionnalités par une suite de tests solide. Le logiciel devra évoluer et le code devra être modifié (réusinage), pour ces deux raisons, un filet de sécurité est essentiel : les tests.
Il est possible d’écrire des tests unitaires et fonctionnels. Dans cet article, des tests fonctionnels seront mis en place. Il sont plus lents, mais permettront de tester l’application de point à point. Dans ces tests, un crawler sera initialisé et lancera des requêtes de types GET, POST, PUT et DELETE sur notre API REST. Par la suite, il sera possible de vérifier que l’API REST réagisse correctement aux requêtes lancées avec des vérifications (assert) fournies par PHPUnit.
Tests fonctionnels pour un API REST
La première méthode rencontrée est setUp(), elle permet de réinitialiser chaque test dans un état connu. Dans le cas qui nous concerne, les fixtures seront injectées dans la base de données.
/src/Acme/Bundle/ApiBundle/Tests/Controller/Rest/CommentController.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
<?php namespace Acme\Bundle\ApiBundle\Tests\Controller\Rest; use Acme\Bundle\ApiBundle\Tests\WebTestCase; /** * CommentControllerTest * @author G. Simard */ class CommentControllerTest extends WebTestCase { public function setUp() { parent::setup(); $fixtures = array( 'AppBundle\DataFixtures\ORM\LoadUserData', 'Acme\Bundle\ApiBundle\DataFixtures\ORM\LoadUserCommentData', ); $this->loadFixtures($fixtures); } } |
Tester la sécurité
La méthode testGetCommentsWithoutToken() lance une requête GET afin de vérifier qu’une ressource est bien protégée, le serveur devrait retourner un code 401 (Unauthorized).
/src/Acme/Bundle/ApiBundle/Tests/Controller/Rest/CommentController.php
1 2 3 4 5 6 7 8 9 10 11 |
<?php ... public function testGetCommentsWithoutToken() { $client = static::createClient(); $this->execQuery($client, 'GET', null, '/api/comments'); $response = $client->getResponse(); $this->assertJsonResponse($response, 401); } ... } |
Tester la réponse GET de l’API REST
La méthode testGetComments() lance une requête GET afin de vérifier que la réponse du serveur est celle attendue, le serveur devrait retourner un objet Comment sérialisé (JSON) ainsi que le code 200.
Avant de faire cette requête, un client authentifié est créé avec la méthode createAuthenticatesClient(), ce client aura en sa possession un JWT valide.
/src/Acme/Bundle/ApiBundle/Tests/Controller/Rest/CommentController.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<?php ... public function testGetComments() { $client = $this->createAuthenticatedClient('api@api.com', 'api'); $this->execQuery($client, 'GET', null, '/api/comments'); $response = $client->getResponse(); $this->assertJsonResponse($response, 200); $content = json_decode($response->getContent(), true); $this->assertInternalType('array', $content); $this->assertCount(3, $content); $comment = $content[0]; $this->assertArrayHasKey('body', $comment); $this->assertArrayHasKey('status', $comment); $this->assertArrayNotHasKey('content', $comment); $this->assertArrayNotHasKey('password', $comment['user']); } ... |
Tester le bon fonctionnement d’une requête POST
Cette fois-ci, une requête POST est envoyée. On vérifier que le résultat est celui attendu et accompagné d’un code 201 (Created)
/src/Acme/Bundle/ApiBundle/Tests/Controller/Rest/CommentController.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
<?php ... public function testPostNewComment() { $data = array ( "body" => "This is a comment", "status" => "1", "user_id" => "1", "movie_id" => "4", ); $client = $this->createAuthenticatedClient('api@api.com', 'api'); $this->postData($client, $data, '/api/comments'); $response = $client->getResponse(); $this->assertStatusCodeResponse($response, 201); $comment = json_decode($response->getContent(), true); $this->assertInternalType('array', $comment); $this->assertArrayHasKey('id', $comment); $this->assertArrayHasKey('movie_id', $comment); $this->assertArrayHasKey('body', $comment); $this->assertArrayNotHasKey('password', $comment['user']); } ... |
Tester la robustesse et les validations
Il est aussi important de vérifier que nos règles de validations sont bonnes. Dans le cas suivant, on s’assure que le code 410 est retourné. Il serait aussi bon de vérifier que le message retourné est celui attendu.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<?php ... public function testPostNewCommentWithoutMovieId() { $data = array ( "body" => 'My new simple comment', ); $client = $this->createAuthenticatedClient('api@api.com', 'api'); $this->postData($client, $data, '/api/comments'); $response = $client->getResponse(); $this->assertStatusCodeResponse($response, 410); } ... |
Exécuter les tests pour votre API REST
Vous pouvez lancer l’exécution de vos tests avec la commande suivante :
1 |
$ phpunit |
Avant Symfony 2.8, vous devrez utiliser la variante suivante :
1 |
$ phpunit -c app |
PHPUnit en ligne de commande
Arrêter les tests lorsque ça va mal
Aussitôt qu’une erreur est rencontrée, PHPUnit affichera le message d’erreur.
1 2 3 |
$ phpunit --stop-on-failure $ phpunit --stop-on-error $ phpunit --stop-on-erro |
Exécuter quelques tests seulement
Il est possible de filtrer les tests à exécuter de la manière suivante :
1 2 3 |
$ phpunit --filter Comment $ phpunit --filter "Comment|Fav|Auth" $ phpunit --filter 'TestNamespace\\TestCaseClass::testMethod' |
https://phpunit.de/manual/current/en/textui.html
Afin de filtrer les tests à exécuter, il est aussi possible de modifier le fichier phpunit.xml et de créer des suites :
1 2 3 4 5 6 7 |
<testsuites> <testsuite name="Project Test Suite"> <directory>../src/*/*Bundle/Tests</directory> <directory>../src/*/Bundle/*Bundle/Tests</directory> <directory>../src/*Bundle/Tests</directory> </testsuite> </testsuites> |
Modifier le format de sortie des tests
De base, PHPUnit n’affiche pas le titre des tests, mais le caractère point. Afin d’afficher les titres des tests, il est possible d’utiliser –testdox :
1 2 3 4 5 6 7 8 9 10 11 |
$ phpunit --testdox PHPUnit 5.0.10 by Sebastian Bergmann and contributors. Acme\Bundle\ApiBundle\Tests\Controller\Rest\CommentController [x] Get comments without token [x] Get comments [x] Get comments with movie id param [x] Get comments with empty movie id param [ ] Get comment [x] Get comment with invalid role [x] Get invalid comment |
Plutôt que :
1 2 3 4 5 |
$ phpunit PHPUnit 5.0.10 by Sebastian Bergmann and contributors. ................................................ .............. |
Couverture du code
PHPUnit offre aussi le possibilité de générer un rapport sur la couverture de code par vos tests.
1 |
--coverage-html |
Vider la cache
1 2 3 4 5 |
# Vide la cache pour l'environnement test $ bin/console cache:clear --env=test # Vide la cache pour l'environnement dev $ bin/console cache:clear |
Pre-commit hook
Il est possible de configurer git afin d’exécuter un script lors d’un commit avec un pre-commit hook. Il suffit de créer le script et de l’activer en le déposant dans le dossier .git/hook.
Exemple de hook shell
Lance les tests pour un projet Symfony >= 2.8 :
1 2 |
#!/bin/sh phpunit |
Lance les tests pour un projet Symfony < 2.8 :
1 2 |
#!/bin/sh phpunit -c /CHEMIN_VERS_SYMFONY/app |
Exemple de hook php
Il est aussi possible de créer un script php.
1 2 3 4 5 6 7 8 9 10 11 12 |
#!/usr/bin/env php <?php $projectName = basename(getcwd()); exec('vendor/bin/phpunit', $output, $returnCode); if ($returnCode !== 0) { $minimalTestSummary = array_pop($output); printf("Test suite for %s failed: ", $projectName); printf("( %s ) %s%2\$s", $minimalTestSummary, PHP_EOL); printf("ABORTING COMMIT!\n"); exit(1); } exit(0); |
Activer un hook
Afin d’activer un hook, il suffit de créer un lien symbolique dans le dossier caché .git/hooks.
1 |
$ ln -s ../vendor/hgtan/symfony-pre-commit/hooks .git/hooks |
Hook pour un projet Symfony
Un petit extra pour les projets Symfony.
https://github.com/hoangthienan/symfony-pre-commit
Laisser un commentaire
Participez-vous à la discussion?N'hésitez pas à contribuer!