Introduction au test logiciel/Tests unitaires/PHPUnit

est utilisé dans un certain nombre de frameworks connus pour réaliser des . Sa documentation en anglais est disponible au format PDF.
Installation.
Via composer.
 composer require --dev phpunit/phpunit ^8
Via wget.
Une fois le .phar téléchargé depuis le site officiel, le copier dans le dossier où il sera toujours exécuté. Exemple :
Unix-like.
wget https://phar.phpunit.de/phpunit-8.phar
mv phpunit.phar /usr/local/bin/phpunit
chmod +x phpunit.phar
Windows.
Par ailleurs, le code source de cet exécutable est sur .
Test.
Test de l'installation :
phpunit --version
Utilisation.
Il faut indiquer au programme les dossiers contenant des tests dans le fichier phpunit.xml.dist. Exemple sur Symfony :
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/9.0/phpunit.xsd"
 backupGlobals="false"
 colors="true"
 bootstrap="vendor/autoload.php"
 <php>
 <ini name="error_reporting" value="-1" />
 <server name="KERNEL_CLASS" value="AppKernel" />
 <env name="SYMFONY_DEPRECATIONS_HELPER" value="weak" />
 </php>
 <testsuites>
 <testsuite name="Project Test Suite">
 <directory suffix=".php">./tests</directory>
 <exclude>tests/FunctionalTests/*</exclude>
 </testsuite>
 </testsuites>
 <listeners>
 <listener class="Symfony\Bridge\PhpUnit\SymfonyTestsListener" />
 </listeners>
</phpunit>
Si on a plusieurs dossiers à exclure, mieux vaut sélectionner plutôt ceux à traiter :
 <directory suffix=".php">tests/UnitTests</directory>
 <directory suffix=".php">tests/FunctionalTests/QuickTests</directory>
Ensuite, pour tester tous les fichiers du dossier /test et ignorer ceux de /src, il suffit de lancer :
./bin/phpunit
Options.
Ce .xml donne des options par défaut qui peuvent être modifiées dans les commandes. Par exemple codice_5 dans la balise codice_6 peut être par défaut, et codice_7 seulement pour ce lancement.
Choisir les tests à lancer.
Si les tests sont longs et qu'on ne travaille que sur un seul fichier, une seule classe ou une seule méthode, on peut demander à ne tester qu'elle en précisant son nom (ce qui évite d'afficher des dumps que l'on ne souhaite pas voir lors des autres tests) :
bin/phpunit tests/MaClasseTest.php
bin/phpunit --filter=MaClasseTest
bin/phpunit --filter=MaMethodeTest
Si une méthode dépend d'une autre, on ne n'appeler que ces deux-là (peu importe l'ordre) :
bin/phpunit --filter='test1|test2'
Détails de chaque test.
Pour afficher les noms des tests et le temps qu'ils prennent, utiliser : --testdox
Rapports.
Outre les résultats des tests, on peut avoir besoin de mesurer et suivre leur complétude, via le taux de . PhpUnit permet d'afficher ce taux en installant Xdebug et en activant son option codice_8.
Le calcul du taux de couverture peut ensuite être obtenu avec :
 bin/phpunit --coverage-text
Dans un fichier.
Le résultat des tests peut être sauvegardé dans un fichier de rapport XML avec l'option codice_9.
L'ajout de l'option codice_10 générera un rapport du taux de couverture des tests en HTML (mais d'autres formats sont disponibles tels que l'XML ou le PHP), dans le dossier "reports" (créé automatiquement).
Exemple récupérable par l'outil d'analyse de code :
 phpunit --coverage-clover phpunit.coverage.xml --log-junit phpunit.logfile.xml
Écriture des tests.
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
class SuiteDeTests1 extends TestCase
 /** @var MockObject */
 private $monMock1;
 protected function setUp(): void
 // Création des mocks et instanciation de la classe à tester...
 $this->monMock1 = $this->getMockBuilder(maMockInterface::class)->getMock();
 protected function tearDown(): void
 // Libération des ressources après les tests...
 public static function setUpBeforeClass(): void
 // Pour réinitialiser une connexion déclarée dans setUp()
 public static function tearDownAfterClass(): void
 // Pour fermer une connexion déclarée dans setUp()
 protected function test1()
 // Lancement du premier test...
 $this->assertTrue($condition);
La classe de test PHPUnit propose des dizaines d'assertions différentes.
$this->fail().
PhpUnit distingue pour chaque test, les erreurs (ex : division par zéro) des échecs (assertion fausse).
Dans le cas où on on souhaiterait transformer les erreurs en échecs, on peut utiliser codice_11 :
 try {
 $response = $this->MonTestEnErreur();
 } catch (\Throwable $e) {
 $this->fail($e->getMessage());
MockObject.
Les s permettent de simuler des résultats de classes existantes.
willReturn().
Par exemple, pour simuler le résultat de deux classes imbriquées (en appelant la méthode d'une méthode), on leur crée une méthode de test chacune :
 public function mainTest()
 $this->monMock1
 ->expects($this->once())
 ->method('MaMéthode1')
 ->willReturn($this->mockProvider())
 $this->assertEquals(null, $this->monMock1->MaMéthode1()->MaMéthode2());
 private function mockProvider()
 $monMock = $this
 ->getMockBuilder('MaClasse1')
 ->getMock()
 $monMock->method('MaMéthode2')
 ->willReturn('MonRésultat1')
 return $monMock;
willReturnArgument().
Renvoie l'argument dont le numéro est en paramètre.
willThrowException().
Pour qu'un mock simule une erreur. Ex :
 $monMock->method('MaMéthode3')->willThrowException(new Exception());
expects().
Dans l'exemple précédent, codice_12 est un qui compte le nombre de passage dans la méthode, et le test échoue si ce résultat n'est pas 1. Ses valeurs possibles sont :
De plus, on trouve codice_17 pour définir un comportement dépendant du passage.
onConsecutiveCalls.
Si la valeur retournée par le mock doit changer à chaque appel, il faut remplacer codice_18 par codice_19.
Exemple :
 $this->enumProvider->method('getEnumFromVariable')
 ->will($this->onConsecutiveCalls(
 ProductStatusEnum::ON_LINE,
 OrderStatusEnum::VALIDATED
 ;
with().
Cette méthode permet de définir les paramètres avec lesquels doit être lancé une méthode mock. Ex :
 $this->enumProvider->method('getEnumFromVariable')
 ->with($this->equalTo('variable 1'))
disableOriginalConstructor().
Cette méthode s'emploie quand il est inutile de passer par le constructeur du mock.
expectException().
S'utilise quand le test unitaire doit provoquer une exception dans le code testé (ex : s'il contient un "throw").
 $this->expectException(Exception::class);
 $monObjetTesté->method('MaMéthodeQuiPète');
Si au contraire on veut vérifier que le code testé ne renvoie pas d'exception, on peut le lancer suivi d'une incrémentation des assertions :
 $monObjetTesté->method('MaMéthodeSansErreur');
 $this->addToAssertionCount(1);
Annotations.
PHPUnit offre plusieurs annotations pour influencer les tests. Exemples :
JavaScript.
En PHP, Selenium peut s'interfacer avec PHPUnit pour tester du JavaScript.
Avec Symfony, il existe aussi Panther.
Symfony.
Pour récupérer une variable d'environnement ou un service dans un test unitaire Symfony, il faut passer par codice_21 pour booter le kernel du framework :
 /** @var string */
 private static $maVariableYaml;
 /** @var Translator */
 private static $translator;
 public static function setUpBeforeClass(): void
 $kernel = static::createKernel();
 $kernel->boot();
 self::$maVariableYaml = $kernel->getContainer()->getParameter('ma_variable');
 self::$translator = $kernel->getContainer()->get('translator');
Test fonctionnel :
 public function testPost(): void
 $route = '/api/test';
 $body = [
 'data' =>
 ['post_parameter_1' => 'value 1'],
 static::$client->request('POST', $route, [], [], [], json_encode($body));
 $response = static::$client->getResponse();
 $this->assertInstanceOf(JsonResponse::class, $response);
 $this->assertTrue($response->isSuccessful(), $response);
 $content = json_decode($response->getContent(), true);
 $this->assertNotEmpty($content['message'], json_encode($content));