Aujourd’hui, nous allons parler des tests unitaires, mocks, client HTTP et PHP ! 😮

Tests unitaires

Les tests unitaires automatisés sont un outil essentiel pour garantir la stabilité d’un développement et accélérer les phases de correction, d’évolution ou refactoring. Cependant, pour qu’un test unitaire soit efficace, il doit s’assurer de respecter quelques recommandations (liste non-exhaustive) :

  • être très rapide à exécuter : pour le lancer le plus souvent possible et avoir un feedback presque instantané
  • être rejouable indéfiniment : pour le lancer le plus souvent possible
  • ne tester que le composant ou la fonction à tester : car c’est sa raison d’être

Mocks

Un test unitaire ne doit donc pas tester les dépendances du composant à tester. Les deux principales raisons sont :

  • Les dépendances ont leurs propres tests unitaires. Il n’est donc pas nécessaire de les tester une seconde fois.
  • Si une dépendance contient un bug, est-ce que le composant testé a un bug ? Non !

Afin de répondre à cette problématique, il est possible d’utiliser des techniques de mocking (ou stubing). Elles permettent de simuler le comportement d’un composant et donc d’une dépendance. Ainsi, un contrat d’utilisation du sous-composant est défini. Le test valide le comportement du composant principal en le mettant en condition normale (ie. comportement du sous-composant attendu). L’utilisation de mocks assure la stabilité des dépendances et augmente donc celle du test unitaire.

Architecture REST

Ce qui est intéressant avec les architectures REST actuelles, c’est qu’elles reposent souvent sur un unique et même protocole de communication : HTTP. C’est donc le protocole qui est utilisé pour matérialiser les dépendances entre les composants dans ce type d’architecture. L’enjeu des tests unitaires ici est donc d’être capable de créer des mocks HTTP.

Et en PHP ?

Venons en maintenant à ce qui nous interesse : le PHP !

PHP propose des fonctions en standard pour faire des appels HTTP. Néanmoins, il existe des composants plus avancés tels que Guzzle. Ce client HTTP pour PHP propose lui-même de créer un mock HTTP.

Imaginons le composant suivant qui permet de récupérer le détail de ressources exposées via l’API “datas” :

class ApiBackup {
public $client;
public function __construct() { $this->client = new GuzzleHttp\Client();}
public function exportAll() {
  $response = $this->client->get('https://apis.company.com/datas');
  foreach($res->json()->results as $row) {
    $results[] = $this->client>get('https://apis.company/datas/'.$row->id);
  }
  return results;
}
}

Créer un test unitaire automatisé de ce composant nécessite donc d’avoir une API “datas” toujours disponible, très rapide et toujours avec les mêmes données. Autant dire : mission impossible ! C’est pourquoi nous allons “mocker” le client HTTP et créer le test uniaire suivant :

class ApiBackupTest extends PHPUnit_Framework_TestCase {
public function testExportAll() {
  // GIVEN
  $apiBackup = new ApiBackup();
  $mock = new GuzzleHttp\Subscriber\Mock();
  $mock->addResponse('./ressources/datas_all.txt'); //contient la réponse HTTP attendue (avec 3 résultats) pour l'appel à https://apis.company.com/datas
  $mock->addResponse('./ressources/datas_1.txt'); // contient la réponse HTTP attendue (avec le détail de la ressource) pour l'appel à 'https://apis.company/datas/'.$row->id
  $mock->addResponse('./ressources/datas_2.txt'); // idem
  $mock->addResponse('./ressources/datas_3.txt'); // idem
  $apiBackup->client->getEmitter()->attach($mock);

  // WHEN
  $results = $apiBackup->exportAll();

  // THEN
  $this->assertEquals(3,count($results));
}
}

Le test unitaire ci-dessus créé un mock qui retournera toujours 3 ressources avec toujours le même détail. L’API “datas” ne sera pas appelée lors de l’exécution du test.

Pour explication, un fichier de réponse ressemble à ceci :

HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Content-Type: application/json
Date: Fri, 14 Mar 2014 07:53:27 GMT
Content-Length: 79
Connection: keep-alive

{
  "results": [
    {"id":"1571"},
    {"id":"2349"},
    {"id":"3727"},
  ]
}

Il est possible d’obtenir ces fichiers de réponse HTTP avec les commandes suivantes (si l’API “datas” est dans l’état souhaité) :

$ curl -iv --raw -o ressources/datas_all.txt 'https://apis.company.com/datas'
$ curl -iv --raw -o ressources/datas_1.txt 'https://apis.company.com/datas/1571'
$ curl -iv --raw -o ressources/datas_2.txt 'https://apis.company.com/datas/2349'
$ curl -iv --raw -o ressources/datas_3.txt 'https://apis.company.com/datas/3727'

Vous êtes maintenant armé pour industrialiser les tests unitaires de vos applications PHP qui utiliseraient le client HTTP Guzzle.