PHP
Test d'unité
Recherche…
Syntaxe
- Liste complète des assertions . Exemples:
-
assertTrue(bool $condition[, string $messageIfFalse = '']);
-
assertEquals(mixed $expected, mixed $actual[, string $messageIfNotEqual = '']);
Remarques
Unit
tests Unit
sont utilisés pour tester le code source pour voir s'il contient des offres avec des entrées, comme prévu. Unit
tests Unit
sont supportés par la majorité des frameworks. Il existe plusieurs tests PHPUnit différents et leur syntaxe peut être différente. Dans cet exemple, nous utilisons PHPUnit
.
Test des règles de classe
Disons que nous avons une classe LoginForm
simple avec la méthode rules () (utilisée dans la page de connexion comme modèle d' LoginForm
):
class LoginForm {
public $email;
public $rememberMe;
public $password;
/* rules() method returns an array with what each field has as a requirement.
* Login form uses email and password to authenticate user.
*/
public function rules() {
return [
// Email and Password are both required
[['email', 'password'], 'required'],
// Email must be in email format
['email', 'email'],
// rememberMe must be a boolean value
['rememberMe', 'boolean'],
// Password must match this pattern (must contain only letters and numbers)
['password', 'match', 'pattern' => '/^[a-z0-9]+$/i'],
];
}
/** the validate function checks for correctness of the passed rules */
public function validate($rule) {
$success = true;
list($var, $type) = $rule;
foreach ((array) $var as $var) {
switch ($type) {
case "required":
$success = $success && $this->$var != "";
break;
case "email":
$success = $success && filter_var($this->$var, FILTER_VALIDATE_EMAIL);
break;
case "boolean":
$success = $success && filter_var($this->$var, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE) !== null;
break;
case "match":
$success = $success && preg_match($rule["pattern"], $this->$var);
break;
default:
throw new \InvalidArgumentException("Invalid filter type passed")
}
}
return $success;
}
}
Pour effectuer des tests sur cette classe, nous utilisons les tests unitaires (vérification du code source pour voir si cela correspond à nos attentes):
class LoginFormTest extends TestCase {
protected $loginForm;
// Executing code on the start of the test
public function setUp() {
$this->loginForm = new LoginForm;
}
// To validate our rules, we should use the validate() method
/**
* This method belongs to Unit test class LoginFormTest and
* it's testing rules that are described above.
*/
public function testRuleValidation() {
$rules = $this->loginForm->rules();
// Initialize to valid and test this
$this->loginForm->email = "[email protected]";
$this->loginForm->password = "password";
$this->loginForm->rememberMe = true;
$this->assertTrue($this->loginForm->validate($rules), "Should be valid as nothing is invalid");
// Test email validation
// Since we made email to be in email format, it cannot be empty
$this->loginForm->email = '';
$this->assertFalse($this->loginForm->validate($rules), "Email should not be valid (empty)");
// It does not contain "@" in string so it's invalid
$this->loginForm->email = 'invalid.email.com';
$this->assertFalse($this->loginForm->validate($rules), "Email should not be valid (invalid format)");
// Revert email to valid for next test
$this->loginForm->email = '[email protected]';
// Test password validation
// Password cannot be empty (since it's required)
$this->loginForm->password = '';
$this->assertFalse($this->loginForm->validate($rules), "Password should not be valid (empty)");
// Revert password to valid for next test
$this->loginForm->password = 'ThisIsMyPassword';
// Test rememberMe validation
$this->loginForm->rememberMe = 999;
$this->assertFalse($this->loginForm->validate($rules), "RememberMe should not be valid (integer type)");
// Revert remeberMe to valid for next test
$this->loginForm->rememberMe = true;
}
}
Comment exactement les tests Unit
peuvent-ils aider (en excluant les exemples généraux) ici? Par exemple, cela convient très bien lorsque nous obtenons des résultats inattendus. Par exemple, prenons cette règle plus tôt:
['password', 'match', 'pattern' => '/^[a-z0-9]+$/i'],
Au lieu de cela, si nous avons raté une chose importante et écrit ceci:
['password', 'match', 'pattern' => '/^[a-z0-9]$/i'],
Avec des douzaines de règles différentes (en supposant que nous utilisons non seulement le courrier électronique et le mot de passe), il est difficile de détecter les erreurs. Ce test unitaire:
// Initialize to valid and test this
$this->loginForm->email = "[email protected]";
$this->loginForm->password = "password";
$this->loginForm->rememberMe = true;
$this->assertTrue($this->loginForm->validate($rules), "Should be valid as nothing is invalid");
Va passer notre premier exemple mais pas le second . Pourquoi? Parce que dans le deuxième exemple, nous avons écrit un motif avec une faute de frappe (signe +
raté), ce qui signifie qu’il n’accepte qu’une lettre / un chiffre.
Les tests unitaires peuvent être exécutés dans la console avec la commande: phpunit [path_to_file]
. Si tout va bien, nous devrions être en mesure de voir que tous les tests sont en OK
état, sinon nous verrons soit Error
(erreurs de syntaxe) ou Fail
(au moins une ligne de cette méthode n’est pas passée).
Avec des paramètres supplémentaires tels que --coverage
nous pouvons également voir visuellement combien de lignes du code backend ont été testées et celles qui ont réussi / échoué. Cela s'applique à tout framework ayant installé PHPUnit .
Exemple de l'apparence du test PHPUnit
dans la console (aspect général, pas selon cet exemple):
Fournisseurs de données PHPUnit
Les méthodes de test nécessitent souvent de tester des données. Pour tester complètement certaines méthodes, vous devez fournir différents jeux de données pour chaque condition de test possible. Bien sûr, vous pouvez le faire manuellement en utilisant des boucles, comme ceci:
...
public function testSomething()
{
$data = [...];
foreach($data as $dataSet) {
$this->assertSomething($dataSet);
}
}
...
Et quelqu'un peut le trouver commode. Mais cette approche présente certains inconvénients. Tout d'abord, vous devrez effectuer des actions supplémentaires pour extraire des données si votre fonction de test accepte plusieurs paramètres. Deuxièmement, en cas d'échec, il serait difficile de distinguer l'ensemble de données défaillant sans messages ni débogage supplémentaires. Troisièmement, PHPUnit fournit un moyen automatique de traiter les ensembles de données de test à l'aide de fournisseurs de données .
Le fournisseur de données est une fonction qui doit renvoyer des données pour votre scénario de test particulier.
Une méthode de fournisseur de données doit être publique et renvoyer un tableau de tableaux ou un objet qui implémente l'interface Iterator et génère un tableau pour chaque étape d'itération. Pour chaque tableau faisant partie de la collection, la méthode de test sera appelée avec le contenu du tableau comme arguments.
Pour utiliser un fournisseur de données avec votre test, utilisez l'annotation @dataProvider
avec le nom de la fonction de fournisseur de données spécifiée:
/**
* @dataProvider dataProviderForTest
*/
public function testEquals($a, $b)
{
$this->assertEquals($a, $b);
}
public function dataProviderForTest()
{
return [
[1,1],
[2,2],
[3,2] //this will fail
];
}
Tableau de tableaux
Notez que
dataProviderForTest()
renvoie un tableau de tableaux. Chaque tableau imbriqué a deux éléments et remplira les paramètres nécessaires pourtestEquals()
un par un. Une erreur comme celle-ci seraMissing argument 2 for Test::testEquals()
s'il n'y a pas assez d'éléments. PHPUnit va automatiquement parcourir les données et exécuter les tests:
public function dataProviderForTest()
{
return [
[1,1], // [0] testEquals($a = 1, $b = 1)
[2,2], // [1] testEquals($a = 2, $b = 2)
[3,2] // [2] There was 1 failure: 1) Test::testEquals with data set #2 (3, 4)
];
}
Chaque ensemble de données peut être nommé pour plus de commodité. Il sera plus facile de détecter les données défaillantes:
public function dataProviderForTest()
{
return [
'Test 1' => [1,1], // [0] testEquals($a = 1, $b = 1)
'Test 2' => [2,2], // [1] testEquals($a = 2, $b = 2)
'Test 3' => [3,2] // [2] There was 1 failure:
// 1) Test::testEquals with data set "Test 3" (3, 4)
];
}
Les itérateurs
class MyIterator implements Iterator {
protected $array = [];
public function __construct($array) {
$this->array = $array;
}
function rewind() {
return reset($this->array);
}
function current() {
return current($this->array);
}
function key() {
return key($this->array);
}
function next() {
return next($this->array);
}
function valid() {
return key($this->array) !== null;
}
}
...
class Test extends TestCase
{
/**
* @dataProvider dataProviderForTest
*/
public function testEquals($a)
{
$toCompare = 0;
$this->assertEquals($a, $toCompare);
}
public function dataProviderForTest()
{
return new MyIterator([
'Test 1' => [0],
'Test 2' => [false],
'Test 3' => [null]
]);
}
}
Comme vous pouvez le voir, un itérateur simple fonctionne également.
Notez que même pour un seul paramètre, le fournisseur de données doit retourner un tableau
[$parameter]
Parce que si nous changeons notre méthode current()
(qui retourne des données à chaque itération) à ceci:
function current() {
return current($this->array)[0];
}
Ou changer les données réelles:
return new MyIterator([
'Test 1' => 0,
'Test 2' => false,
'Test 3' => null
]);
Nous aurons une erreur:
There was 1 warning:
1) Warning
The data provider specified for Test::testEquals is invalid.
Bien sûr, il n'est pas utile d'utiliser un objet
Iterator
sur un tableau simple. Il devrait implémenter une logique spécifique pour votre cas.
Générateurs
Il n'est pas explicitement noté et affiché dans le manuel, mais vous pouvez également utiliser un générateur en tant que fournisseur de données. Notez que la classe Generator
implémente l'interface Iterator
.
Voici donc un exemple d'utilisation de DirectoryIterator
combiné à un generator
:
/**
* @param string $file
*
* @dataProvider fileDataProvider
*/
public function testSomethingWithFiles($fileName)
{
//$fileName is available here
//do test here
}
public function fileDataProvider()
{
$directory = new DirectoryIterator('path-to-the-directory');
foreach ($directory as $file) {
if ($file->isFile() && $file->isReadable()) {
yield [$file->getPathname()]; // invoke generator here.
}
}
}
Notez que le fournisseur
yield
un tableau. Vous recevrez plutôt un avertissement de fournisseur de données non valide.
Test des exceptions
Disons que vous voulez tester la méthode qui lance une exception
class Car
{
/**
* @throws \Exception
*/
public function drive()
{
throw new \Exception('Useful message', 1);
}
}
Vous pouvez le faire en plaçant l'appel de méthode dans un bloc try / catch et en effectuant des assertions sur les propriétés de l'objet d'exécution, mais plus facilement, vous pouvez utiliser des méthodes d'assertion d'exception. A partir de PHPUnit 5.2, vous disposez des méthodes expectX () pour affirmer le type d’exception, le message et le code.
class DriveTest extends PHPUnit_Framework_TestCase
{
public function testDrive()
{
// prepare
$car = new \Car();
$expectedClass = \Exception::class;
$expectedMessage = 'Useful message';
$expectedCode = 1;
// test
$this->expectException($expectedClass);
$this->expectMessage($expectedMessage);
$this->expectCode($expectedCode);
// invoke
$car->drive();
}
}
Si vous utilisez une version antérieure de PHPUnit, la méthode setExpectedException peut être utilisée à la place des méthodes expectX (), mais gardez à l'esprit qu'elle est obsolète et sera supprimée dans la version 6.
class DriveTest extends PHPUnit_Framework_TestCase
{
public function testDrive()
{
// prepare
$car = new \Car();
$expectedClass = \Exception::class;
$expectedMessage = 'Useful message';
$expectedCode = 1;
// test
$this->setExpectedException($expectedClass, $expectedMessage, $expectedCode);
// invoke
$car->drive();
}
}