Débuter avec Phpspec

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.

SpecBDD & Phpspec

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é.

Qu'allons nous faire

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.

Installation

Pour ce tutoriel, je suppose que les éléments suivants sont opérationnels:

  • Une configuration PHP fonctionnelle (min. 5.3)
  • Compositeur

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

Configuration

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.

Notre première spec

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.

Quel est $ 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é.

Construire notre collection de tâches

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.

Ajout d'une tâche

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 Interface

Nous 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 un compter() méthode.

Plus tôt, nous avons utilisé le matcher shouldHaveType (), qui est un type matcher. Il utilise le comparateur PHP exemple 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 est shouldImplement (), 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 notre TaskCollection classe:

classe TaskCollection implements \ Countable

Nos 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 pas 0. 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 notre TaskCollection 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 ces Collaborateur 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? y

Ajout 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 le ajouter() 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 le ajouter() 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 le Liste de choses à faire. Enfin, nous essayons d’appeler le Ajouter 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, la ajouter() 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éelle compter() méthode. Nous devons juste nous assurer que notre code en appelle compter() 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 le compter() 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 retourne faux.

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 retourne nul, ne pas faux.

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 pas vrai, Nous devons donc améliorer notre code. Utilisons ça compter() 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 de ObjectBehavior. 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) 16ms

Nous 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!