Tester votre code Ruby avec Guard, RSpec & Pry

Mon travail récent porte sur un projet Ruby basé sur le cloud pour BBC News et les prochaines élections de 2014. Cela nécessite une entrée / sortie rapide, une évolutivité et doit être bien testé. L'exigence "être bien testé", est ce que je veux mettre l'accent dans ce tutoriel.

introduction

Ce projet utilise quelques services Amazon différents tels que:

  • SQS (service de file d'attente simple)
  • DynamoDB (magasin de clés / valeurs)
  • S3 (service de stockage simple)

Nous devons pouvoir écrire des tests rapides et nous donner un retour immédiat sur les problèmes rencontrés avec notre code..

Bien que nous n'utilisions pas les services Amazon dans ce didacticiel, je les mentionne car, pour que les tests soient rapides, il nous faut simuler ces objets externes (par exemple, nous ne devrions pas avoir besoin d'une connexion réseau tests, car cette dépendance peut ralentir les tests).

Aux côtés de Robert Kenny, responsable technique, qui maîtrise très bien l'écriture d'applications Ruby basées sur TDD (développement piloté par les tests), nous avons utilisé différents outils qui ont facilité ce processus et notre travail de programmation..

Je souhaite partager avec vous des informations sur ces outils.

Les outils que je couvrirai sont:

  • RSpec (framework de test)
  • Garde (coureur de tâche)
  • Pry (REPL et débogage)

Que dois-je savoir dès le départ?

Je suppose que vous connaissez bien le code Ruby et son écosystème. Par exemple, je ne devrais pas avoir besoin de vous expliquer ce que sont les "joyaux" ou comment fonctionnent certains syntaxes / concepts de Ruby..

Si vous n'êtes pas sûr, alors avant de partir, je vous recommande de lire un de mes autres articles sur Ruby pour vous mettre à niveau..

Garde

Vous n'êtes peut-être pas familier avec Guard, mais il s'agit essentiellement d'un outil en ligne de commande qui utilise Ruby pour gérer différents événements..

Par exemple, Guard peut vous avertir lorsque des fichiers spécifiques ont été modifiés et vous pouvez effectuer certaines actions en fonction du type de fichier ou d'événement déclenché..

C'est ce qu'on appelle un «coureur de tâches», vous avez peut-être déjà entendu cette phrase, car ils sont de plus en plus utilisés dans le monde front-end / côté client (Grunt et Gulp sont deux exemples populaires)..

La raison pour laquelle nous allons utiliser Guard est parce que cela aide à resserrer davantage la boucle de rétroaction (lorsque vous utilisez TDD). Cela nous permet d'éditer nos fichiers de test, de voir un test qui a échoué, de mettre à jour et d'enregistrer notre code et de voir immédiatement s'il réussit ou échoue (selon ce que nous avons écrit).

Vous pouvez utiliser quelque chose comme Grunt ou Gulp à la place, mais nous préférons utiliser ces types de gestionnaires de tâches pour gérer les éléments front-end / côté client. Pour le code back-end / côté serveur, nous utilisons Rake et Guard.

RSpec

RSpec, si vous n'étiez pas déjà au courant, est un outil de test du langage de programmation Ruby..

Vous exécutez vos tests (à l'aide de RSpec) via la ligne de commande et je vais vous montrer comment rendre ce processus plus facile via le programme de compilation de Ruby, Rake.

Pry

Enfin, nous utiliserons un autre joyau Ruby appelé Pry, un outil de débogage Ruby extrêmement puissant qui s'injecte dans votre application, pendant son exécution, pour vous permettre d'inspecter votre code et de comprendre pourquoi quelque chose ne fonctionne pas..

TDD (développement piloté par les tests)

Bien que cela ne soit pas nécessaire pour démontrer l'utilisation de RSpec et de Guard, il est important de noter que j'approuve totalement l'utilisation de TDD comme moyen de garantir que chaque ligne de code que vous écrivez a un but et a été conçue de manière testable et fiable..

Je détaillerai la manière dont nous ferions le TDD avec une application simple, afin qu'au moins vous ayez une idée du fonctionnement du processus..

Créer un exemple de projet

J'ai créé un exemple de base sur GitHub pour vous éviter d'avoir à tout taper vous-même. N'hésitez pas à télécharger le code. 

Passons maintenant à l'examen de ce projet, étape par étape..

Fichiers primaires

Pour que notre exemple d'application fonctionne, trois fichiers principaux sont nécessaires:

  1. Gemfile
  2. Guardfile
  3. Rakefile

Nous examinerons le contenu de chaque fichier sous peu, mais la première chose à faire est de mettre en place notre structure de répertoires..

Structure du répertoire

Pour notre exemple de projet, nous aurons besoin de deux dossiers créés:

  • lib (cela contiendra notre code d'application)
  • spec (cela tiendra notre code de test)

Ce n'est pas une exigence pour votre application, vous pouvez facilement modifier le code dans nos autres fichiers pour qu'il fonctionne avec la structure qui vous convient.

Installation

Ouvrez votre terminal et exécutez la commande suivante:

Gem Installer Bundler

Bundler est un outil qui facilite l'installation d'autres joyaux.

Une fois que vous avez exécuté cette commande, créez les trois fichiers ci-dessus (Gemfile, Guardfile et Rakefile).

Gemfile

le Gemfile est responsable de la définition d'une liste de dépendances pour notre application.

Voici à quoi cela ressemble:

source "https://rubygems.org" groupe de gem 'rspec': développement ne gem 'garde' gem 'garde-rspec' gem 'pry' end 

Une fois ce fichier sauvegardé, lancez la commande installation groupée.

Ceci installera toutes nos gemmes pour nous (y compris les gemmes spécifiées dans le développement groupe).

Le but de la développement group (qui est une fonctionnalité spécifique à un bundle) signifie que lorsque vous déployez votre application, vous pouvez indiquer à votre environnement de production d'installer uniquement les gems nécessaires au bon fonctionnement de votre application..

Ainsi, par exemple, tous les joyaux à l’intérieur du développement groupe, ne sont pas nécessaires au bon fonctionnement de l'application. Ils sont utilisés uniquement pour nous aider pendant que nous développons et testons notre code.

Pour installer les gems appropriés sur votre serveur de production, vous devez exécuter quelque chose comme:

bundle install --without development

Rakefile

le Rakefile nous permettra d’exécuter nos tests RSpec à partir de la ligne de commande.

Voici à quoi cela ressemble:

besoin de 'rspec / core / rake_task' RSpec :: Core :: RakeTask.new do | task | task.rspec_opts = ['--color', '--format', 'doc'] end 

Remarque: vous n’avez pas besoin de Guard pour pouvoir exécuter vos tests RSpec. Nous utilisons Guard pour faciliter le TDD.

Lorsque vous installez RSpec, cela vous donne accès à une tâche intégrée dans Rake et c'est ce que nous utilisons ici..

Nous créons une nouvelle instance de RakeTask qui crée par défaut une tâche appelée spec qui va chercher un dossier appelé spec et exécutera tous les fichiers de test dans ce dossier, en utilisant les options de configuration que nous avons définies.

Dans ce cas, nous voulons que notre sortie shell ait une couleur et nous voulons formater la sortie au format doc style (vous pouvez changer le format pour imbriqué par exemple).

Vous pouvez configurer la tâche Rake pour qu'elle fonctionne à votre guise et examiner différents répertoires, si c'est ce que vous avez. Mais les paramètres par défaut fonctionnent très bien pour notre application et c'est ce que nous allons utiliser.

Maintenant, si je veux exécuter les tests dans mon exemple de référentiel GitHub, je dois ouvrir mon terminal et exécuter la commande:

spécification de râteau

Cela nous donne la sortie suivante:

Spéc. rake / bin / ruby ​​-S rspec ./spec/example_spec.rb --color --format doc RSpecGreeter RSpecGreeter # greet () Terminé en 0.0006 secondes 1 exemple, 0 échec 

Comme vous pouvez le constater, il n'y a aucun échec. En effet, bien que nous n'ayons pas de code d'application écrit, nous n'avons pas encore de code de test écrit non plus..

Guardfile

Le contenu de ce fichier indique à Guard quoi faire lorsque nous exécutons le garde commander:

garde 'rspec' do # regarder / lib / files regarder (% r ^ lib /(.+). rb $) do | m | "spec / # m [1] _ spec.rb" fin # watch / spec / fichiers watch (% r ^ spec /(.+). rb $) do | m | "spec / # m [1]. rb" fin fin 

Vous aurez remarqué dans notre Gemfile nous avons spécifié la gemme: garde-rspec. Nous avons besoin de ce bijou pour permettre à Guard de comprendre comment gérer les modifications apportées aux fichiers liés à RSpec.

Si nous regardons à nouveau le contenu, nous pouvons voir que si nous courions garde rspec alors Guard regardera les fichiers spécifiés et exécutera les commandes spécifiées une fois que tous les changements auront été apportés à ces fichiers.

Remarque: parce que nous n'avons qu'une tâche de garde, rspec, alors c'est exécuté par défaut si nous avons exécuté la commande garde.

Vous pouvez voir que la garde nous fournit un regarder fonction que nous passons une expression régulière pour nous permettre de définir quels fichiers nous intéressons à la surveillance.

Dans ce cas, nous disons à la Garde de regarder tous les fichiers de notre lib et spec dossiers et si des modifications sont apportées à l’un de ces fichiers, puis exécuter les fichiers de test dans notre spec dossier pour s’assurer que nous n’avons pas apporté de modifications à nos tests (et par la suite n’a pas cassé notre code).

Si vous avez tous les fichiers téléchargés à partir du dépôt GitHub, vous pouvez essayer la commande pour vous-même..

Courir garde puis enregistrez l'un des fichiers pour le voir exécuter les tests.

Code de test

Avant de commencer à examiner un code de test et d’application, laissez-moi vous expliquer ce que notre application va faire. Notre application est une classe unique qui renvoie un message de bienvenue à celui qui exécute le code..

Nos exigences ont été volontairement simplifiées, car elles faciliteront la compréhension du processus que nous allons entreprendre..

Examinons maintenant un exemple de spécification (notre fichier de test, par exemple) qui décrira nos besoins. Après cela, nous allons commencer à parcourir le code défini dans la spécification et voir comment nous pouvons utiliser TDD pour nous aider à écrire notre application..

Notre premier test

Nous allons créer un fichier intitulé example_spec.rb. Le but de ce fichier est de devenir notre fichier de spécification (en d’autres termes, il s’agira de notre code de test et représentera la fonctionnalité attendue)..

Si nous écrivons notre code de test avant d’écrire notre code d’application, c’est parce que cela signifie en définitive que tout code d’application que nous produirons existera, car il a été utilisé.

C’est un point important que je soulève, alors laissez-moi prendre un moment pour le préciser plus en détail..

Écrire le code de test avant le code d'application

En règle générale, si vous écrivez d'abord le code de votre application (pour ne pas utiliser TDD), vous vous retrouverez alors en train d'écrire un code qui, à l'avenir, serait trop élaboré et potentiellement obsolète. Tout au long du processus de refactorisation ou de modification des exigences, vous constaterez peut-être que certaines fonctions ne seront jamais appelées..

C'est pourquoi TDD est considéré comme la meilleure pratique et la méthode de développement préférée à utiliser, car chaque ligne de code que vous produisez a été produite pour une raison: obtenir la réussite d'une spécification défaillante (vos exigences métier réelles). C'est une chose très puissante à garder à l'esprit.

Voici notre code de test:

require 'spec_helper' décrire 'RSpecGreeter' do it 'RSpecGreeter # greet ()' greeter = RSpecGreeter.new # Salut donné = greeter.greet # Lorsque saluer.should eq ('Bonjour RSpec!') # Puis fin à la fin 

Vous remarquerez peut-être les commentaires de code à la fin de chaque ligne:

  • Donné
  • Quand
  • ensuite

Ce sont une forme de terminologie BDD (Behavior-Driven Development). Je les ai incluses pour les lecteurs qui sont plus familiers avec BDD (Behavior-Driven Development) et qui étaient intéressés par la façon dont ils peuvent assimiler ces déclarations à TDD..

La première chose que nous faisons dans ce fichier est de charger spec_helper.rb (qui se trouve dans le même répertoire que notre fichier de spécifications). Nous reviendrons et regarder le contenu de ce fichier dans un instant.

Nous avons ensuite deux blocs de code spécifiques à RSpec:

  • décrire 'x' do
  • c'est toi

La première décrire block devrait décrire de manière adéquate la classe / le module spécifique sur lequel nous travaillons et pour lequel nous fournissons des tests. Vous pourriez très bien avoir plusieurs décrire blocs dans un seul fichier de spécification.

Il y a beaucoup de théories différentes sur la façon d'utiliser décrire et il blocs de description. Personnellement, je préfère la simplicité et je vais donc utiliser les identifiants pour la classe / les modules / les méthodes que nous allons tester. Mais vous rencontrez souvent des personnes qui préfèrent utiliser des phrases complètes pour leurs descriptions. Ni est bon ou faux, juste préférence personnelle.

le il le bloc est différent et doit toujours être placé à l'intérieur d'un décrire bloc. Ça devrait expliquer Comment nous voulons que notre application fonctionne.

Encore une fois, vous pourriez utiliser une phrase normale pour décrire les exigences, mais j’ai constaté que cela pouvait parfois rendre les descriptions trop explicites, alors qu’elles devraient vraiment être plus claires. implicite. En étant moins explicite, vous réduisez les risques de modification de vos fonctionnalités, ce qui rend votre description obsolète (le fait de devoir mettre à jour votre description chaque fois que des modifications mineures sont apportées aux fonctionnalités est plus un fardeau qu'une aide). En utilisant l'identifiant de la méthode que nous testons (par exemple, le nom de la méthode que nous exécutons), nous pouvons éviter ce problème..

Le contenu de la il le bloc est le code que nous allons tester.

Dans l'exemple ci-dessus, nous créons une nouvelle instance de la classe RSpecGreeter (qui n'existe pas encore). Nous envoyons le message saluer (qui n'existe pas encore) à l'objet instancié créé (Remarque: ces deux lignes sont du code Ruby standard à ce stade).

Enfin, nous indiquons au cadre de test que nous attendons le résultat de l'appel du saluer méthode pour être le texte "Bonjour RSpec!", en utilisant la syntaxe RSpec: eq (quelque chose).

Notez que la syntaxe permet de la lire facilement (même par une personne non technique). Ceux-ci sont connus comme des affirmations.

Il y a beaucoup d'assertions RSpec différentes et nous n'entrerons pas dans les détails, mais n'hésitez pas à consulter la documentation pour voir toutes les fonctionnalités fournies par RSpec.

Créer un assistant pour notre test

Une certaine quantité de passe-partout est nécessaire au bon déroulement de nos tests. Dans ce projet, nous n’avons qu’un seul fichier de spécification, mais dans un vrai projet, il est probable que vous en avez des dizaines (selon la taille de votre application)..

Pour nous aider à réduire le code général, nous le placerons dans un fichier d'aide spécial que nous chargerons à partir de nos fichiers de spécifications. Ce fichier sera intitulé spec_helper.rb.

Ce fichier fera quelques choses:

  • dire à Ruby où se trouve notre code d'application principal
  • charger notre code d'application (pour que les tests soient exécutés)
  • charger le faire levier gem (nous aide à déboguer notre code; s'il le faut).

Voici le code:

$ << File.join(File.dirname(FILE), '… ', 'lib') require 'pry' require 'example' 

Remarque: la première ligne peut sembler un peu cryptique, alors laissez-moi vous expliquer comment cela fonctionne. Ici, nous disons que nous voulons ajouter le / lib / dossier à Ruby $ LOAD_PATH variable système. Chaque fois que Ruby évalue nécessite 'un_fichier' il a une liste de répertoires qu'il va essayer de localiser. Dans ce cas, nous nous assurons que si nous avons le code exiger 'exemple' que Ruby sera en mesure de le localiser car il va vérifier notre / lib / répertoire et là, il va trouver le fichier spécifié. C'est une astuce astucieuse que vous verrez utilisée dans beaucoup de gemmes Ruby, mais cela peut être assez déroutant si vous ne l'avez jamais vu auparavant.

Code d'application

Notre code d'application va se trouver dans un fichier intitulé exemple.rb.

Avant de commencer à écrire un code d'application, rappelez-vous que nous réalisons ce projet TDD. Nous allons donc laisser les tests de notre fichier de spécifications nous guider sur la première chose à faire..

Commençons par supposer que vous utilisez garde pour exécuter vos tests (chaque fois que nous apportons un changement à exemple.rb, La garde remarquera le changement et continuera à courir example_spec.rb pour nous assurer que nos tests réussissent).

Pour que nous puissions faire correctement le TDD, notre exemple.rb le fichier sera vide et donc si nous ouvrons le fichier et le "sauvegardons" dans son état actuel, alors Guard s'exécutera et nous découvrirons (sans surprise) que notre test échouera:

Échecs: 1) RSpecGreeter RSpecGreeter # greet () Échec / Erreur: greeter = RSpecGreeter.new # NameError donné: constante non initialisée RSpecGreeter # ./spec/example_spec.rb:5:inblock (2 levels) in 'Terminé dans 0.00059 secondes 1 exemple, 1 échec Exemples ayant échoué: rspec ./spec/example_spec.rb:4 # RSpecGreeter RSpecGreeter # greet ()

Avant d’aller plus loin, permettez-moi de préciser à nouveau que TDD est basé sur le principe que chaque ligne de code a une raison d’exister, donc ne pas commencez à courir et à écrire plus de code que nécessaire. N'écrivez que la quantité minimale de code nécessaire à la réussite du test. Même si le code est laid ou ne remplit pas toutes les fonctionnalités.

Le but du TDD est d’avoir un boucle de rétroaction, également connu sous le nom de «rouge, vert, refactor»). En pratique, cela signifie:

  • écrire un test qui échoue
  • écrire le moins de code possible pour le faire passer
  • refactoriser le code

Vous verrez tout de suite que, du fait de la simplicité de nos exigences, nous n’avons pas besoin de refactoriser. Mais dans un projet réel avec des exigences bien plus complexes, vous devrez probablement passer à la troisième étape et refactoriser le code que vous avez entré pour réussir le test..


Pour revenir à notre test d’échec, comme vous pouvez le constater dans l’erreur, il n’ya pas RSpecGreeter classe définie. Corrigeons cela, ajoutons le code suivant et sauvegardons le fichier pour que nos tests fonctionnent:

code classe RSpecGreeter # finira par aller ici 

Cela entraînera l'erreur suivante:

Échecs: 1) RSpecGreeter RSpecGreeter # greet () Échec / Erreur: greeter = greeter.greet # Si NoMethodError: méthode non définie 'pour # #. .Spsp/exemple_spec.rb:6:in' bloc (2 niveaux) dans 'Terminé en 0.00036 secondes 1 exemple, 1 échec 

Maintenant, nous pouvons voir que cette erreur nous dit la méthode saluer n'existe pas, ajoutons-le et sauvegardons encore notre fichier pour lancer nos tests:

classe RSpecGreeter def saler # code finira par aller ici fin fin 

OK, nous y sommes presque. L'erreur que nous obtenons maintenant est:

Échecs: 1) RSpecGreeter RSpecGreeter # greet () Échec / Erreur: greeter = greeting.should eq ('Hello RSpec!') # Alors attendu: "Hello RSpec!" got: nil (comparé avec ==) # ./spec/example_spec.rb:7:in 'block (2 niveaux) dans' Terminé dans 0.00067 secondes 1 exemple, 1 échec 

RSpec nous dit qu'il s'attendait à voir Bonjour RSpec! mais au lieu de cela néant (parce que nous avons défini le saluer méthode mais n'a pas réellement défini quoi que ce soit à l'intérieur de la méthode et donc il retourne néant).

Nous ajouterons le code restant pour que notre test réussisse:

class RSpecGreeter def salue "Bonjour RSpec!" fin fin 

Voilà, un test de réussite:

Terminé dans 0.00061 secondes 1 exemple, 0 échec 

Nous avons fini ici. Notre test est écrit et le code passe.

Conclusion

Jusqu'à présent, nous avons appliqué un processus de développement piloté par les tests à la construction de notre application, tout en utilisant le populaire framework de tests RSpec..

Nous allons le laisser ici pour le moment. Revenez et rejoignez-nous pour la deuxième partie. Nous verrons plus de fonctionnalités spécifiques à RSpec ainsi que l'utilisation de Pry gem pour vous aider à déboguer et à écrire votre code..