Dans ce didacticiel court mais complet, nous examinerons le développement dirigé par le comportement (BDD) avec phpspec. Généralement, ce sera une introduction à l'outil phpspec, mais nous allons maintenant aborder différents concepts BDD. BDD est un sujet d'actualité et phpspec a beaucoup attiré l'attention récemment dans la communauté PHP.
BDD s’attache à décrire le comportement d’un logiciel afin d’obtenir une conception correcte. Il est souvent associé au TDD, mais alors que le TDD se concentre sur essai votre application, BDD est plus sur décrivant son comportement. L'utilisation d'une approche BDD vous obligera à tenir constamment compte des exigences réelles et du comportement souhaité du logiciel que vous construisez..
Deux outils BDD ont récemment attiré beaucoup d'attention dans la communauté PHP, Behat et phpspec. Behat vous aide à décrire le comportement externe de votre application, en utilisant le langage Gherkin lisible. phpspec, en revanche, vous aide à décrire le comportement interne de votre application, en écrivant de petites "specs" en langage PHP - d'où SpecBDD. Ces spécifications testent que votre code a le comportement souhaité.
Dans ce tutoriel, nous couvrirons tout ce qui concerne la prise en main de phpspec. Sur notre chemin, nous construirons les bases d’une application de liste de tâches, étape par étape, en utilisant une approche SpecBDD. Au fur et à mesure, phpspec ouvrira la voie!
Note: Ceci est un article intermédiaire sur PHP. Je suppose que vous maîtrisez parfaitement PHP orienté objet.
Pour ce tutoriel, je suppose que les éléments suivants sont opérationnels:
L'installation de phpspec via Composer est la méthode la plus simple. Tout ce que vous avez à faire est d’exécuter la commande suivante dans un terminal:
$ composer composer phpspec / phpspec Veuillez fournir une contrainte de version pour l'exigence phpspec / phpspec: 2.0.*@dev
Cela fera un composer.json
déposer pour vous et installer phpspec dans un vendeur/
annuaire.
Pour vous assurer que tout fonctionne, exécutez phpspec
et voyez que vous obtenez le résultat suivant:
$ vendor / bin / phpspec run 0 specs 0 examples 0ms
Avant de commencer, nous devons faire un peu de configuration. Quand phpspec est lancé, il cherche un fichier YAML nommé phpspec.yml
. Puisque nous allons placer notre code dans un espace de noms, nous devons nous assurer que phpspec en sa connaissance. De plus, pendant que nous y sommes, assurons-nous que nos spécifications sont belles et jolies lorsque nous les exécutons..
Allez-y et créez le fichier avec le contenu suivant:
formatter.name: pretty suites: todo_suite: espace de noms: Petersuhm \ Todo
Il existe de nombreuses autres options de configuration disponibles, que vous pouvez lire dans la documentation..
Une autre chose que nous devons faire est de dire à Composer comment charger automatiquement notre code. phpspec utilisera l'autoloader de Composer, il est donc nécessaire pour que nos spécifications soient exécutées..
Ajouter un élément autoload au composer.json
fichier que Composer a créé pour vous:
"require": "phpspec / phpspec": "2.0.*@dev", "autoload": "psr-0": "Petersuhm \\ Todo": "src"
Fonctionnement composer dump-autoload
mettra à jour l'autoloader après ce changement.
Nous sommes maintenant prêts à rédiger notre première spécification. Nous allons commencer par décrire une classe appelée TaskCollection
. Nous aurons phpspec générer une classe de spécification pour nous en utilisant le décrire
commande (ou alternativement la version courte desc
) .
$ vendor / bin / phpspec décrivent "Petersuhm \ Todo \ TaskCollection" $ vendor / bin / phpspec à exécuter Voulez-vous que je crée "Petersuhm \ Todo \ TaskCollection" pour vous? y
Alors que s'est-il passé ici? Premièrement, nous avons demandé à phpspec de créer une spécification pour un TaskCollection
. Deuxièmement, nous avons lancé notre suite de spécifications, puis phpspec automagiquement offert pour créer le réel TaskCollection
classe pour nous. Cool, n'est ce pas?
Continuez et exécutez à nouveau la suite, et vous verrez que nous avons déjà un exemple dans notre spécification (nous verrons dans un instant quel est cet exemple):
$ vendor / bin / phpspec lance Petersuhm \ Todo \ TaskCollection 10 ✔ est initialisable 1 spécification 1 exemples (1 transmis) 7ms
De cette sortie, nous pouvons voir que le TaskCollection
est initialisable. Ca parle de quoi? Jetez un coup d'œil au fichier de spécification généré par phpspec, il devrait être plus clair:
shouldHaveType ('Petersuhm \ Todo \ TaskCollection');
La phrase "est initialisable" est dérivée d'une fonction nommée it_is_initializable ()
qui phpspec a ajouté à une classe appelée TaskCollectionSpec
. Cette fonction est ce que nous appelons une Exemple. Dans cet exemple particulier, nous avons ce que nous appelons une matcher appelé shouldHaveType ()
qui vérifie le type de notre TaskCollection
. Si vous modifiez le paramètre transmis à cette fonction par quelque chose d'autre et exécutez à nouveau la spécification, vous verrez qu'il échouera. Avant de comprendre complètement cela, je pense que nous devons étudier en quoi la variable $ this
se réfère à notre spec.
$ this
?Bien sûr, $ this
fait référence à l'instance de la classe TaskCollectionSpec
, puisque c'est juste du code PHP normal. Mais avec phpspec, vous devez traiter $ this
différent de ce que vous faites normalement, car sous le capot, il s’agit en fait de l’objet testé, qui est en fait le TaskCollection
classe. Ce comportement est hérité de la classe ObjectBehavior
, qui s'assure que les appels de fonction sont envoyés par proxy à la classe spécifiée. Cela signifie que SomeClassSpec
sera des appels de méthode proxy à une instance de Une classe
. phpspec encapsulera ces appels de méthode afin d'exécuter leurs valeurs de retour contre des correspondants tels que celui que vous venez de voir.
Vous n'avez pas besoin d'une compréhension profonde de ceci pour utiliser phpspec, rappelez-vous simplement qu'en ce qui vous concerne, $ this
fait en réalité référence à l'objet testé.
Jusqu'ici, nous n'avons rien fait nous-mêmes. Mais phpspec a fait un vide TaskCollection
classe pour nous d'utiliser. Il est maintenant temps de renseigner du code et de rendre cette classe utile. Nous allons ajouter deux méthodes: un ajouter()
méthode, pour ajouter des tâches, et un compter()
méthode, pour compter le nombre de tâches de la collection.
Avant d'écrire un code réel, nous devrions écrire un exemple dans nos spécifications. Dans notre exemple, nous voulons essayer d'ajouter une tâche à la collection, puis nous assurer ensuite que la tâche a bien été ajoutée. Pour ce faire, nous avons besoin d’une instance de la (jusqu’à présent inexistante) Tâche
classe. Si nous ajoutons cette dépendance en tant que paramètre à notre fonction spec, phpspec nous donnera automatiquement une instance que nous pouvons utiliser. En fait, l’instance n’est pas une instance réelle, mais ce que phpspec appelle un Collaborateur
. Cet objet agira comme l’objet réel, mais phpspec nous permet d’en faire plus, ce que nous verrons bientôt. Même si le Tâche
la classe n’existe pas encore, pour l’instant, prétendez-en. Ouvrez le TaskCollectionSpec
et ajouter un utilisation
déclaration pour le Tâche
classe et ensuite ajouter l'exemple it_adds_a_task_to_the_collection ()
:
utilisez Petersuhm \ Todo \ Task;… function it_adds_a_task_to_the_collection (Task $ task) $ this-> add ($ task); $ this-> tasks [0] -> shouldBe ($ task);
Dans notre exemple, nous écrivons le code "nous souhaiterions avoir". Nous appelons le ajouter()
méthode et ensuite essayer de lui donner un $ tâche
. Ensuite, nous vérifions que la tâche a bien été ajoutée à la variable d'instance $ tâches
. Le matcher devrait être()
est un identité Matcher similaire au PHP ===
comparateur. Vous pouvez utiliser soit devrait être()
, shouldBeEqualTo ()
, shouldEqual ()
ou devraitRetour ()
- ils font tous la même chose.
L'exécution de phpspec générera des erreurs, car nous n'avons pas de classe nommée Tâche
encore.
Que phpspec répare cela pour nous:
$ vendor / bin / phpspec décrit "Petersuhm \ Todo \ Task" $ vendor / bin / phpspec run Voulez-vous que je crée "Petersuhm \ Todo \ Task" pour vous? y
En exécutant à nouveau phpspec, il se passe quelque chose d'intéressant:
$ vendor / bin / phpspec run Voulez-vous que je crée 'Petersuhm \ Todo \ TaskCollection :: add ()' pour vous? y
Parfait! Si vous regardez le TaskCollection.php
fichier, vous verrez que phpspec a fait un ajouter()
fonction pour nous de remplir:
phpspec se plaint toujours, cependant. Nous n'avons pas de
$ tâches
tableau, alors faisons-en un et ajoutons la tâche:tâches [] = $ tâche;Maintenant, nos spécifications sont toutes belles et vertes. Notez que je me suis assuré de dactylographier le
$ tâche
paramètre.Juste pour nous assurer que nous avons bien compris, ajoutons une autre tâche:
fonction it_adds_a_task_to_the_collection (tâche $ tâche, tâche $ une autre tâche) $ this-> add ($ tâche); $ this-> tasks [0] -> shouldBe ($ task); $ this-> add ($ anotherTask); $ this-> tasks [1] -> shouldBe ($ anotherTask);En cours d'exécution phpspec, on dirait que nous sommes tous bons.
Mise en œuvre du
Dénombrable
InterfaceNous voulons savoir combien de tâches se trouvent dans une collection, ce qui constitue une excellente raison d'utiliser l'une des interfaces de la bibliothèque PHP standard (SPL), à savoir:
Dénombrable
interface. Cette interface dicte qu'une classe l'implémentant doit avoir uncompter()
méthode.Plus tôt, nous avons utilisé le matcher
shouldHaveType ()
, qui est un type matcher. Il utilise le comparateur PHPexemple de
pour valider qu'un objet est en fait une instance d'une classe donnée. Il y a 4 types de matchers, qui font tous la même chose. L'un d'eux estshouldImplement ()
, ce qui est parfait pour notre but, alors allons-y et utilisons cela dans un exemple:fonction it_is_countable () $ this-> shouldImplement ('Countable');Vous voyez comme c'est beau? Examinons l'exemple et laissez phpspec nous guider:
$ vendor / bin / phpspec lance Petersuhm / Todo / TaskCollection 25 count est dénombrable, attend une instance de Countable, mais a [obj: Petersuhm \ Todo \ TaskCollection].Bon, alors notre classe n'est pas une instance de
Dénombrable
puisque nous ne l'avons pas encore implémenté. Mettons à jour le code de notreTaskCollection
classe:classe TaskCollection implements \ CountableNos tests ne fonctionneront pas, car le
Dénombrable
l'interface a une méthode abstraite,compter()
, que nous devons mettre en œuvre. Une méthode vide fera l'affaire pour l'instant:fonction publique count () //…Et nous sommes de retour au vert. Au moment notre
compter()
méthode ne fait pas beaucoup, et il est en fait assez inutile. Écrivons une spécification pour le comportement que nous souhaitons. Premièrement, en l'absence de tâches, notre fonction de comptage devrait renvoyer zéro:fonction it_counts_elements_of_the_collection () $ this-> count () -> shouldReturn (0);Il retourne
nul
, ne pas0
. Pour obtenir un test vert, corrigeons ceci de la manière TDD / BDD:fonction publique count () return 0;Nous sommes verts et tout va bien, mais ce n'est probablement pas le comportement que nous souhaitons. Au lieu de cela, développons nos spécifications et ajoutons quelque chose à la
$ tâches
tableau:fonction it_counts_elements_of_the_collection () $ this-> count () -> shouldReturn (0); $ this-> tasks = ['foo']; $ this-> count () -> shouldReturn (1);Bien sûr, notre code revient toujours
0
, et nous avons un pas rouge. Le réparer n’est pas trop difficile et notreTaskCollection
La classe devrait maintenant ressembler à ceci:tâches [] = $ tâche; public function count () return count ($ this-> tâches);Nous avons un test vert et notre
compter()
méthode fonctionne. Quelle journée!Attentes et promesses
N'oubliez pas que je vous ai dit que phpspec vous permet de faire des trucs sympas avec des instances du
Collaborateur
classe, AKA les instances qui sont automatiquement injectées par phpspec? Si vous avez déjà écrit des tests unitaires, vous savez ce que sont les imitations et les moignons. Si vous ne le faites pas, ne vous inquiétez pas trop à ce sujet. Ce n'est que du jargon. Ces choses font référence à de "faux" objets qui agiront comme vos objets réels, mais vous permettront de tester de manière isolée. phpspec va automatiquement transformer cesCollaborateur
des instances dans des moqueries et des talons si vous en avez besoin dans vos spécifications.C'est vraiment génial. Sous le capot, phpspec utilise la bibliothèque Prophecy, un framework hautement moqueur qui joue bien avec phpspec (et qui est construit par les mêmes personnes géniales). Vous pouvez définir une attente sur un collaborateur (moqueur), comme "cette méthode devrait être appelé", et vous pouvez ajouter des promesses (stubbing), comme" cette méthode reviendra cette valeur ". Avec phpspec cela est vraiment facile et nous ferons les deux choses suivantes.
Faisons un cours, nous l'appellerons
Liste de choses à faire
, qui peut utiliser notre classe de collection.$ vendor / bin / phpspec desc "Petersuhm \ Todo \ TodoList" $ vendor / bin / phpspec run Voulez-vous que je crée "Petersuhm \ Todo \ TodoList" pour vous? yAjout de tâches
Le premier exemple que nous allons ajouter est celui pour l’ajout de tâches. Nous ferons un
Ajouter une tâche()
méthode, cela ne fait rien de plus que d'ajouter une tâche à notre collection. Il dirige simplement l'appel vers leajouter()
méthode sur la collection, donc c'est un endroit parfait pour faire usage d'une attente. Nous ne voulons pas que la méthode appelle réellement leajouter()
méthode, nous voulons juste nous assurer qu'il essaie de le faire. De plus, nous voulons nous assurer qu’il l’appelle une seule fois. Jetez un coup d'œil à la façon dont on peut s'y prendre avec phpspec:shouldHaveType ('Petersuhm \ Todo \ TodoList'); function it_adds_a_task_to_the_list (tâches TaskCollection $, tâches Task $) $ tasks-> add ($ task) -> shouldBeCalledTimes (1); $ this-> tasks = $ tâches; $ this-> addTask ($ task);Phpspec nous fournit d’abord les deux collaborateurs dont nous avons besoin: une collection de tâches et une tâche. Ensuite, nous définissons une attente sur le collaborateur de la collection de tâches qui dit en gros: "le
ajouter()
la méthode doit être appelée exactement 1 fois avec la variable$ tâche
en tant que paramètre ". C’est ainsi que nous préparons notre collaborateur, qui est maintenant une maquette, avant de l’assigner au$ tâches
propriété sur leListe de choses à faire
. Enfin, nous essayons d’appeler leAjouter une tâche()
méthode.Ok, qu'est-ce que phpspec a à dire à ce sujet:
$ vendor / bin / phpspec lance Petersuhm / Todo / TodoList 17! ajoute une tâche à la liste des tâches de propriétés non trouvées.le
$ tâches
la propriété est inexistante - facile:Réessayez et demandez à phpspec de vous guider:
$ vendor / bin / phpspec run Voulez-vous que je crée 'Petersuhm \ Todo \ TodoList :: addTask ()' pour vous? y $ vendor / bin / phpspec lance Petersuhm / Todo / TodoList 17 ✘ ajoute une tâche à la liste. Certaines prédictions ont échoué: Double \ Petersuhm \ Todo \ TaskCollection \ P4: Attend exactement 1 appels correspondants: Double \ Petersuhm \ Todo \ TaskCollection \ P4-> add (exact (Double \ Petersuhm \ Todo \ Task \ P3: 000000002544d76d0000000059fcae53)) mais aucune n'a été faite.Ok, maintenant quelque chose d'intéressant est arrivé. Voir le message "Attendu exactement 1 appels correspondant à: ..."? Ceci est notre attente qui échoue. Cela se produit car après avoir appelé le
Ajouter une tâche()
méthode, laajouter()
méthode sur la collection était ne pas appelé, que nous nous attendions à ce qu'il soit.Pour revenir au vert, remplissez le code suivant dans le champ vide
Ajouter une tâche()
méthode:tâches-> ajouter ($ tâche);Retour au vert! Ça fait du bien?
Vérification des tâches
Voyons aussi les promesses. Nous voulons une méthode qui puisse nous dire s'il y a des tâches dans la collection. Pour cela, nous allons simplement vérifier la valeur de retour du
compter()
méthode sur la collection. Encore une fois, nous n’avons pas besoin d’une instance réelle avec une réellecompter()
méthode. Nous devons juste nous assurer que notre code en appellecompter()
méthode et faire des trucs en fonction de la valeur de retour.Regardez l'exemple suivant:
fonction it_checks_whether_it_has_any_tasks (TaskCollection $ tasks) $ tasks-> count () -> willReturn (0); $ this-> tasks = $ tâches; $ this-> hasTasks () -> shouldReturn (false);Nous avons un collaborateur de la collection de tâches qui a un
compter()
méthode qui reviendra zéro. Ceci est notre promesse. Cela signifie que chaque fois que quelqu'un appelle lecompter()
méthode, il retournera zéro. Nous assignons ensuite le collaborateur préparé à la$ tâches
propriété sur notre objet. Enfin, nous essayons d'appeler une méthode,hasTasks ()
, et assurez-vous qu'il retournefaux
.Qu'est-ce que phspec a à dire à ce sujet?
$ vendor / bin / phpspec run Voulez-vous que je crée 'Petersuhm \ Todo \ TodoList :: hasTasks ()' pour vous? y $ vendor / bin / phpspec lance Petersuhm / Todo / TodoList 25 ✘ vérifie si des tâches sont attendues fausses, mais ont la valeur null.Cool. phpspec nous a fait
hasTasks ()
méthode et sans surprise, il retournenul
, ne pasfaux
.Encore une fois, c’est un problème facile à résoudre:
fonction publique hasTasks () return false;Nous sommes revenus au vert, mais ce n'est pas tout à fait ce que nous voulons. Vérifions les tâches quand il y en a 20. Cela devrait retourner
vrai
:fonction it_checks_whether_it_has_any_tasks (TaskCollection $ tasks) $ tasks-> count () -> willReturn (0); $ this-> tasks = $ tâches; $ this-> hasTasks () -> shouldReturn (false); $ tasks-> count () -> willReturn (20); $ this-> tasks = $ tâches; $ this-> hasTasks () -> shouldReturn (true);Exécutez phspec et nous aurons:
$ vendor / bin / phpspec lance Petersuhm / Todo / TodoList 25 ✘ vérifie si des tâches sont attendues vraies, mais devenues fausses.d'accord,
faux
n'est pasvrai
, Nous devons donc améliorer notre code. Utilisons çacompter()
méthode pour voir s'il y a des tâches ou pas:fonction publique hasTasks () if ($ this-> tasks-> count ()> 0) renvoie la valeur true; retourne faux;Tah dah! Retour au vert!
Construire des correspondants personnalisés
Une partie de la rédaction de bonnes spécifications consiste à les rendre aussi lisibles que possible. Notre dernier exemple peut en réalité être légèrement amélioré, grâce aux adaptateurs personnalisés de phpspec. Il est facile d’implémenter des adaptateurs personnalisés. Tout ce que nous avons à faire est d’écraser
getMatchers ()
méthode héritée deObjectBehavior
. En mettant en œuvre deux adaptateurs personnalisés, nos spécifications peuvent être modifiées pour ressembler à ceci:fonction it_checks_whether_it_has_any_tasks (TaskCollection $ tasks) $ tasks-> count () -> willReturn (0); $ this-> tasks = $ tâches; $ this-> hasTasks () -> shouldBeFalse (); $ tasks-> count () -> willReturn (20); $ this-> tasks = $ tâches; $ this-> hasTasks () -> shouldBeTrue (); function getMatchers () return ['beTrue' => fonction ($ subject) return $ subject === true; , 'beFalse' => function ($ subject) return $ subject === false; ,];Je pense que ça a l'air bien. Rappelez-vous que la refactorisation de vos spécifications est importante pour les maintenir à jour. L'implémentation de vos propres adaptateurs personnalisés peut nettoyer vos spécifications et les rendre plus lisibles..
En fait, on peut aussi utiliser la négation des matchers:
fonction it_checks_whether_it_has_any_tasks (TaskCollection $ tasks) $ tasks-> count () -> willReturn (0); $ this-> tasks = $ tâches; $ this-> hasTasks () -> shouldNotBeTrue (); $ tasks-> count () -> willReturn (20); $ this-> tasks = $ tâches; $ this-> hasTasks () -> shouldNotBeFalse ();Ouais. Plutôt cool!
Conclusion
Toutes nos spécifications sont vertes et regardent comment elles documentent bien notre code!
Petersuhm \ Todo \ TaskCollection 10 ✔ est initialisable 15 ✔ ajoute une tâche à la collection 24 ✔ est comptable 29 ✔ compte des éléments de la collection Petersuhm \ Todo \ Task 10 ✔ est initialisable Petersuhm \ Todo \ TodoList 11 ✔ est initialisable 16 ✔ ajoute une tâche à la liste 24 ✔ vérifie si elle a des tâches 3 spécifications 8 exemples (8 passées) 16msNous avons efficacement décrit et atteint le comportement souhaité de notre code. Sans oublier que notre code est couvert à 100% par nos spécifications, ce qui signifie que le refactoring ne sera pas une expérience apeurante..
En suivant, j'espère que vous avez eu l'inspiration d'essayer phpspec. C'est plus qu'un outil de test, c'est un outil de conception. Une fois que vous serez habitué à utiliser phpspec (et ses formidables outils de génération de code), vous aurez du mal à le lâcher! Les gens se plaignent souvent que le TDD ou le BDD les ralentissent. Après avoir incorporé phpspec à mon flux de travail, je sens vraiment le contraire: ma productivité est considérablement améliorée. Et mon code est plus solide!