Quand on développe des gros projets, il peut être très utile de tester ses principales fonctionnalités régulièrement, notamment tout au long de la phase de développement où l’ajout de code peut entrainer une modification involontaire du comportement d’un script. Le développement collaboratif implique également cette méthode de travail afin de s’assurer que son code n’influence pas le comportement du code des autres développeurs de façon néfaste.

Les tests unitaires

Les tests unitaires (ou tests du programmeur, comme on les appelle pour être fashion quand on fait de « l’extrême programming ») permettent de s’assurer du bon fonctionnement d’un algorithme durant toute la phase de développement d’un projet.

On y comprend très vite l’intérêt. On peut, par exemple, détecter l’influence qu’a eu l’ajout d’un nouveau module sur l’ensemble d’un projet, vérifier que la gestion des utilisateurs que l?on vient d?ajouter n?empêche pas le script de checks quotidien de fonctionner.

Tout bon développeur inclut beaucoup de tests dans son code, de façon a vérifier que l’exécution d’une fonction ou d’un script se déroule comme prévue par son auteur. Mais beaucoup de ces tests ? surement useful en dév- ne seront pas forcement très utile en production et ne feront que ralentir votre application.

La base des tests unitaires en php : les assertions

Une assertion est une expression que l’on peut qualifier de vraie ou fausse, par exemple :

  • « je m’appelle Hugues », cette assertion est vraie.
  • « 2+2 = 5 », cette assertion est fausse.

Il est très aisé de faire des tests unitaires en php, des fonctions natives existent pour vérifier des assertions :

  • assert() permet de vérifier une assertion.
  • assert_options() permet de configurer les options relatives aux assertions.

Notes :

  • Comme indiqué dans la doc de php, les assertions sont déconseillées en environnement de production, ce qui semble logique et qui n’a de toute façon aucun sens.
  • L’utilisation des assertions ne doit pas remplacer la gestion des erreurs (aucun rapport), il faut savoir utiliser trigger_error( ) et assert() de façon complémentaire.
  • Le grand avantage d’utiliser les assertions est que l’on peut les désactiver très facilement et sans toucher au code lorsque l’on passe en prod ; ca ne ralentit quasiment plus le code.

Utilisation des assertions

L’utilisation des assertions est extrêmement simple et rapide. Comme pour les erreurs, il suffit de définir un gestionnaire d’évènements de façon a déterminer les actions a effectuer en cas d’échec d’une assertion.

[ASSERT ERROR]\r\nfile $file:$line";
	}
}

// On assigne la méthode assert_error() en cas d'assertion fausse
// Ceci doit être fait après avoir déclaré cette fonction
assert_options(ASSERT_CALLBACK, array('debug', 'assert_error'));

class user
{
	private $user_registered;

	public function __construct() {
		$this->user_registered = false;
	}

	public function login($user) {
		if(false === $this->checkUser($user)) {
			return false;
		}

		$this->user_registered = true;

		return true;
	}

	private function checkUser($user) {
		return false;
	}

	public function userIsRegistered() {
		return $this->user_registered;
	}
}

$user = new user;

if(false === $user->login('toto')) {
	trigger_error('Invalid user.');
	//exit;
	// On simule un comportement anormal en poursuivant le traitement
}

// Si on arrive ici c'est que le user s'est logué correctement
// donc userIsRegistered doit être à true, on vérifie le
// comportement souhaité

assert('$user->userIsRegistered()');
// Ce test n'a aucun sens en production mais pourrait en avoir un en dév.

echo 'Welcome!';
?>

Ceci va afficher le message suivant :

[ASSERT ERROR]
file /home/plumbr/www/foo/foo.php:58

On détecte donc un comportement anormal très facilement.
Cet exemple est particulièrement pourri mais on imagine bien l’utilité que cela peut avoir.

Quand le développement est terminé et que le comportement souhaité est bien respecté, on peut désactiver les assertions et passer en prod :

//assert_options(ASSERT_ACTIVE, 1);
assert_options(ASSERT_ACTIVE, 0);

Notes :
Bien que les tests unitaires n’ont plus de sens en production, il peut être judicieux de tester le bon fonctionnement de ses applications régulièrement.
On peut par exemple lancer des tests quotidiens via un cron, en activant temporairement les assertions sans que ca n’ait de conséquence sur le fonctionnement ou sur les performances pour l’utilisateur.

Aller plus loin

Ceci n’est qu’un exemple, les tests unitaires peuvent être plus poussés, en simulant, par exemple, le post d’un formulaire pour vérifier si les users peuvent se connecter ou pour checker la création d’un nouvel utilisateur ; pour vérifier le temps d’exécution d’un script et envoyer une alerte par email si c’est trop lent…

Plus d’infos sur Wikipedia :

Ressources :

Deux principaux frameworks ont été developpés pour faciliter l’utilisation des tests unitaires et en étendre les possibilités :

Tutorial en français

Tutorial en anglais

PhpUnit est un package pear, no comment :)

Note importante :
Il faut bien évaluer des châines de caratères dans les assertions :

assert('$user->userIsRegistered()'); // est correct
assert($user->userIsRegistered()); // est maladroit

Le deuxième exemple retourne apriori le resultat souhaité.
Le problème est que, quand on désactive les assertions, même si les messages indiquant de fausses assertions sont masqués, le code est évalué de façon identique.
Ca ralentit le code innutilement et n’a donc plus beaucoup de sens.
Merci à Ganf pour cette petite précision :)