Test unitaires #
Règles #
-
A voir au moins autant de tests qu’autant de nombre de
returnet d’exceptionpour une fonction donnée -
Les tests doivent s’exécuter le plus rapidement possible
-
Les tests doivent être isolés les uns des autres
-
Les tests doivent être répétable à l’infini et toujours renvoyer OK
-
Les tests doivent se suffirent à eux-mêmes pour être compréhensible aux autres développeurs
-
Couvrir le maximum de cas pour permettre l’anticipation
En quoi consiste l’écriture d’un test avec phpunit? #
- Instancier une classe
- Appeler une méthode
- En vérifier la sortie
Couverture de code #
- La couverture de code est une mesure qui permet d’identifier la proportion du code testé
- La couverture de code est une mesure du nombre de lignes/blocs/arcs de votre code exécutés pendant l’exécution des tests automatisés.
phpunit --coverage-html web/test-coverage
Configurations #
Fichier: phpunit.xml.dist
<?xml version="1.0" encoding="UTF-8"?>
<phpunit
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd"
backupGlobals="false"
colors="true"
bootstrap="vendor/autoload.php"
>
<coverage processUncoveredFiles="true">
<include>
<directory suffix=".php">./src</directory>
</include>
</coverage>
<testsuites>
<testsuite name="Test Suite">
<directory>./tests</directory>
</testsuite>
</testsuites>
</phpunit>
Comment faire pour afficher le code coverage à chaque qu’on lance la commande phpunit?
<logging>
<log type=”coverage-text” target=”php://stdout” />
</logging>
Data providers #
class ProductTest extends TestCase
{
/**
* @dataProvider pricesForFoodProduct
*/
public function testcomputeTVAFoodProduct($price, $expectedTva)
{
$product = new Product('Un produit', Product::FOOD_PRODUCT, $price);
$this->assertSame($expectedTva, $product->computeTVA());
}
public function pricesForFoodProduct()
{
return [
[0, 0.0],
[20, 1.1],
[100, 5.5]
];
}
}
Doublure #
- Elément qu’on créé de toutes pièces pour maîtriser une dépendance externe
- Le principe d’un test unitaire est surtout de ne pas dépendre d’un système externe
MOCK #
-
Objet créé à partir d’un type de classe
-
Le but est d’effectuer tranquillement vos tests unitaires sur une méthode qui a besoin de ce type de classe
-
Dummy: Objet un peu particulier qui remplit un contrat
-
Stub: Dummy auquel on ajoute un comportement
-
Mock: Un Stub qui a des attentes
class ClassTest extends TestCase
{
public function testExemple()
{
$request = new Request();
// Ceci est un Dummy
$client = $this->getMock('GuzzleHttp\Client');
// Ceci est un Stub
$client->method('get')->willReturn($request);
// Ceci est un Mock
$client
->expects($this->once()) // Signifie que la méthode ne sera appelée qu'une seule fois
->method('get')
->willReturn($request);
// …
}
}
Use cases #
1. Un objet est difficile à instancier
class Serializer
{
public function __construct(
MetadataFactoryInterface $factory,
HandlerRegistryInterface $handlerRegistry,
ObjectConstructorInterface $objectConstructor,
MapInterface $serializationVisitors,
MapInterface $deserializationVisitors,
EventDispatcherInterface $dispatcher = null,
TypeParser $typeParser = null,
ExpressionEvaluatorInterface $expressionEvaluator = null
)
{}
}
class ExempleClassTest extends TestCase
{
public function testExemple()
{
$serializer = $this
->getMockBuilder('JMS\Serializer\Serializer')
->disableOriginalConstructor()
->getMock();
$classToTest = new ExempleClass($serializer);
// …
}
}
2. Maîtriser le retour d’une méthode appelée par le code original
class GithubUserProvider extends UserProviderInterface
{
private $client;
public function __construct(Client $client)
{
$this->client = $client;
}
public function loadUserByUsername($username)
{
$response = $this->client->get('https://api.github.com/user?access_token='.$username);
// …
}
}
class GithubUserProviderTest extends TestCase
{
public function testLoadUserByUsername()
{
$response = 'Guzzle retournera TOTO'; // Ce que l'on souhaite recevoir.
$client = $this->getMockBuilder('GuzzleHttp\Client')
->disableOriginalConstructor()
->setMethods(['get'])
->getMock();
$client
->method('get')
->willReturn($response);
$githubUserProvider = new GithubUserProvider($client);
$githubUserProvider->loadUserByUsername('xxxxx');
// Assertions du test
// …
}
}
SetUp #
On y met les instructions qu’on veut exécuter avant chaque test
public function setUp()
{
$this->client = $this->getMockBuilder('GuzzleHttp\Client')
->disableOriginalConstructor()
->setMethods(['get'])
->getMock();
$this->serializer = $this
->getMockBuilder('JMS\Serializer\Serializer')
->disableOriginalConstructor()
->getMock();
// ...
}
tearDown #
On y met les instructions qu’on veut exécuter à chaque fin de test
public function tearDown()
{
$this->client = null;
$this->serializer = null;
$this->streamedResponse = null;
$this->response = null;
}