Si vous avez déjà essayé de tester votre code PHP, vous le savez, ce n'est pas forcément évident. En effet, une fois qu'une classe ou une fonction est définie il n'est plus possible de la modifier. Cette particularité devient très rapidement problématique lorsque l'on essaie de tester un code qui fait référence à une classe ou une fonction externe et on est souvent ammené à modifier notre code juste pour le rendre "testable" (en utilisant de l'injection de dépendance à la moindre occasion).
Un autre problème vient des librairies de tests qui sont parfois complexe à utiliser et à mettre en place (PHPUnit je pense à toi :)). En comparaison sur Ruby avec Rspec les tests sont beaucoup plus naturels à écrire :
RSpec.describe MaClass do
it "should understand additions" do
expect(1 + 1).to eq(2)
end
end
Kahlan s'inspire de cette vision et propose une alternative intéréssante en se concentrant avant tout sur la simplicité.
- La librairie est capable de modifier n'importe quel objet ou méthode (à la Ruby ou JavaScript) sans extensions PHP particulière. Ainsi, plus besoin d'avoir recours à l'injection de dépendance à tout bout de champs juste pour rendre le code "testable".
- La syntaxe
describe-it
permet d'avoir des tests qui sont plus naturels à écrire et qui reflètent les fonctionnalités que l'on teste.
Par exemple tester un code qui ressemble à ça devient un jeu d'enfant :
<?php
namespace App;
class Asset
{
public static $json = false;
public static function path($filename)
{
// On fait appel à des fonctions sans les "injecter" !
$json = json_decode(file_get_contents(public_path() . '/assets/assets.json'), true);
if (self::isLocal()) {
return 'http://localhost:3003/assets/' . $filename;
} else {
return $json[$parts[0]][$parts[1]];
}
}
public static function isLocal() {
return strpos($_SERVER['HTTP_HOST'], 'localhost') !== false;
}
}
Ici le code n'est pas séparé et si on souhaite tester la méthode path()
il faut être en mesure de controler le retour de la méthode file_get_contents()
ce qui serait impossible par défaut avec PHPUnit et nous obligerait à repenser notre classe.
<?php
use App\Asset;
use Kahlan\Plugin\Monkey;
use Kahlan\Plugin\Stub;
describe('Asset', function () {
// Permet de créer une propriété $json qui sera lazy loadé au besoin dans les tests
given('json', function () {
return '{"app":{"js":"/assets/app.920fc8a1.js","css":"/assets/app.f5555616.css"}}';
});
// Permet de définir une logique à répéter entre chaque test
beforeEach(function() {
Stub::on(Asset::class)->method('::isLocal')->andReturn(false);
Monkey::patch('public_path', function () { return ''; }); // On monkey patch la fonction public_path
Monkey::patch('file_get_contents', function () { return $this->json; }); // On monkey patch le file get contents pour renvoyer la chaine attendue
});
it('resolves the correct path', function () {
expect(Asset::path('app.js'))->toBe('/assets/app.920fc8a1.js');
});
it('resolves the correct path if we are on localhost', function () {
Stub::on(Asset::class)->method('::isLocal')->andReturn(true);
expect(Asset::path('app.js'))->toBe('http://localhost:3003/assets/app.js');
});
});
Le but ici est juste de vous présenter l'outil mais je vous invite vivement à l'essayer et à consulter la documentation pour en découvrir plus.