Comment je teste

Dans une récente discussion sur Google+, un de mes amis a commenté "Le développement piloté par les tests (TDD) et le développement piloté par le comportement (BDD) est Ivory Tower BS.."C’est ce qui m’a amené à réfléchir à mon premier projet, à la façon dont j’avais ressenti la même chose à l’époque et à ce que je ressens maintenant. Depuis ce premier projet, j’ai développé un rythme de TDD / BDD qui fonctionne non seulement pour moi, mais aussi pour moi. pour le client aussi.

Ruby on Rails est livré avec une suite de tests, appelée Test Unit, mais de nombreux développeurs préfèrent utiliser RSpec, Cucumber ou une combinaison des deux. Personnellement, je préfère ce dernier, en utilisant une combinaison des deux.


RSpec

Sur le site de RSpec:

RSpec est un outil de test du langage de programmation Ruby. Né sous la bannière du développement basé sur le comportement, il est conçu pour faire du développement basé sur les tests une expérience productive et agréable..

RSpec fournit un puissant DSL utile pour les tests unitaires et d'intégration. Bien que j'utilise RSpec pour écrire des tests d'intégration, je préfère ne l'utiliser que dans une capacité de test unitaire. Par conséquent, je vais expliquer comment j'utilise RSpec exclusivement pour les tests unitaires. Je recommande de lire The RSpec Book de David Chelimsky et d’autres pour une couverture complète et approfondie de RSpec..


Concombre

J'ai trouvé que les avantages du TDD / BDD l'emportaient largement sur les inconvénients.

Cucumber est un framework de tests d'intégration et d'acceptation prenant en charge Ruby, Java, .NET, Flex et une multitude d'autres langages et frameworks Web. Son vrai pouvoir vient de son DSL; non seulement il est disponible en anglais courant, mais il a été traduit dans plus de quarante langues parlées..

Avec un test d'acceptation lisible par l'homme, vous pouvez demander au client de signer une fonctionnalité avant d'écrire une seule ligne de code. Comme avec RSpec, je ne couvrirai Cucumber que dans la mesure où je l’utilise. Pour le récapitulatif complet sur Cucumber, consultez The Cucumber Book.


La mise en place

Commençons d’abord par un nouveau projet, demandant à Rails de sauter Test Unit. Tapez ce qui suit dans un terminal:

 rails nouveaux how_i_test -T

Dans le Gemfile, ajouter:

 source 'https: //rubygems.org'… groupe: test de gem' capybara 'gem' concombre-rails ', nécessite: fausse gem' nettoyeur de base de données 'gem' factory_girl_rails 'gem' devrait '' groupe terminal: développement,: test do gem 'rspec-rails' fin

J'utilise principalement RSpec pour veiller à ce que mes modèles et leurs méthodes restent en échec.

Ici, nous avons mis Cucumber et des amis à l'intérieur du groupe tester bloc. Cela garantit qu'ils sont correctement chargés uniquement dans l'environnement de test Rails. Notez comment nous chargeons également RSpec à l’intérieur du développement et tester blocs, le rendant disponible dans les deux environnements. Il y a quelques autres joyaux. que je détaillerai brièvement ci-dessous. N'oubliez pas de courir installation groupée les installer.

  • Capybara: simule les interactions du navigateur.
  • Nettoyeur de base de données: nettoie la base de données entre les tests.
  • Rails de fille d'usine: remplacement du luminaire.
  • Shoulda: méthodes d'assistance et correspondants pour RSpec.

Nous devons utiliser les générateurs de ces pierres précieuses pour les configurer. Vous pouvez le faire avec les commandes de terminal suivantes:

 rails g rspec: installer créer .rspec créer spec créer spec / spec_helper.rb rails g concombre: installer créer config / cucumber.yml créer script / cucumber script chmod / cucumber créer des fonctionnalités / step_definitions créer des fonctionnalités / soutenir créer des fonctionnalités / support / env. rb existe lib / tasks crée lib / tasks / cucumber.rake config gsub / database.yml confs gsub / database.yml force config / database.yml

À ce stade, nous pourrions commencer à rédiger des spécifications et des cukes pour tester notre application, mais nous pouvons configurer quelques éléments pour faciliter les tests. Commençons dans le application.rb fichier.

 module HowITest classe Application < Rails::Application config.generators do |g| g.view_specs false g.helper_specs false g.test_framework :rspec, :fixture => true g.fixture_replacement: factory_girl,: dir => 'spec / factories' end… end end

Dans la classe Application, nous substituons quelques générateurs par défaut de Rails. Pour les deux premiers, nous ignorons les spécifications de génération des vues et des aides.

Ces tests ne sont pas nécessaires car nous utilisons uniquement RSpec pour les tests unitaires.

La troisième ligne indique à Rails que nous avons l’intention d’utiliser RSpec comme cadre de test de choix. Elle devrait également générer des fixtures lors de la génération de modèles. La dernière ligne garantit que nous utilisons factory_girl pour nos appareils, qui sont créés dans le spec / usines annuaire.


Notre premier long métrage

Pour simplifier les choses, nous allons écrire une fonctionnalité simple pour vous connecter à notre application. Par souci de brièveté, je vais ignorer la mise en œuvre réelle et rester fidèle à la suite de tests. Voici le contenu de caractéristiques / signature_en.feature:

 Fonctionnalité: Connexion Pour utiliser l'application En tant qu'utilisateur enregistré, je souhaite me connecter via un formulaire Scénario: Connexion via le formulaire Etant donné qu'un utilisateur est enregistré avec le courrier électronique "[email protected]" Et je suis sur le panneau à la page Lorsque je saisis les informations d'identification correctes et que j'appuie sur le bouton de connexion, le message flash doit être "Connexion réussie".

Quand nous courons cela dans le terminal avec caractéristiques de concombre / signature_en.feature, nous voyons beaucoup de sorties se terminant par nos étapes non définies:

 Etant donné / ^ il y a un utilisateur enregistré avec l'email "(. *?)" $ / Do | arg1 | en attente # exprime l'expression rationnelle ci-dessus avec le code que vous souhaitez avoir terminé Étant donné / ^ Je suis sur la page de connexion $ / do en attente # exprimez l'expression rationnelle ci-dessus avec le code que vous souhaitez utiliser fin Quand / ^ J'ai entré les informations d'identification correctes $ / faire en attente # exprime l'expression rationnelle ci-dessus avec le code que vous souhaitez terminer Quand / ^ J'appuie sur le bouton d'ouverture de session $ / do en attente # exprimer l'expression rationnelle ci-dessus avec le code que vous souhaitez terminer  

La prochaine étape consiste à définir ce que nous attendons de chacune de ces étapes. Nous exprimons cela en features / stepdefinitions / signin_steps.rb, Utilisation de Ruby avec les sélecteurs Capybara et CSS.

 Étant donné / ^ il y a un utilisateur enregistré avec l'email "(. *?)" $ / Do | email | @user = FactoryGirl.create (: utilisateur, email: email) end Étant donné / ^ Je suis sur la page de connexion $ / do visiter sign_in_path end Quand / ^ j'entre les informations d'identification correctes $ / do remplit "Email", avec: @user .mail remplir "mot de passe", avec: @ user.password fin Quand / ^ j'appuie sur le bouton de connexion $ / do click_button "Sign in" end Alors / ^ le message flash doit être "(. *?)" $ / do | texte | dans (". flash") do page.should have_content text end end

Dans chacun des Donné, Quand, et ensuite blocs, nous utilisons le Capybara DSL pour définir ce que nous attendons de chaque bloc (sauf dans le premier). Dans le premier bloc donné, nous demandons à factory_girl de créer un utilisateur stocké dans le utilisateur variable d'instance pour une utilisation ultérieure. Si vous courez caractéristiques de concombre / signature_en.feature encore une fois, vous devriez voir quelque chose de similaire au suivant:

 Scénario: Connexion via le formulaire # features / signature_in.feature: 6 S'il existe un utilisateur enregistré avec le courrier électronique "[email protected]" # features / step_definitions / signature \ _in \ _steps.rb: 1 Usine non enregistrée: utilisateur ( ArgumentError) ./features/step_definitions/signing\_in\_steps.rb:2:in '/ ^ il y a un utilisateur enregistré avec l'email "(. *?)" $ /' Features / signature_in.feature: 7: in il y a un utilisateur enregistré avec le courrier électronique "[email protected]" '

Le message d'erreur indique que notre exemple échoue sur la ligne 1 avec un ArgumentError de la fabrique utilisateur non enregistrée. Nous pourrions créer cette usine nous-mêmes, mais une partie de la magie que nous avons configurée précédemment fera que Rails le fasse pour nous. Lorsque nous générons notre modèle d'utilisateur, nous obtenons gratuitement la fabrique d'utilisateurs.

 rails g modèle utilisateur email: chaîne mot de passe: chaîne invoquer active_record create db / migrate / 20121218044026 \ _create \ _users.rb créer une application / models / user.rb invoquer rspec créer spec / models / utilisateur_spec.rb invoke factory_girl créer spéc / factories / utilisateurs .rb

Comme vous pouvez le constater, le générateur de modèle appelle factory_girl et crée le fichier suivant:

ruby spec / fabories / users.rb FactoryGirl.define do factory: l'utilisateur envoie un courriel "MyString" mot de passe "MyString" end end

Je n'entrerai pas dans les détails de factory_girl ici, mais vous pouvez en lire plus dans leur guide de mise en route. N'oubliez pas de courir rake db: migrer et rake db: test: prepare charger le nouveau schéma. Cela devrait faire passer la première étape de notre fonctionnalité et vous permettre d’utiliser Cucumber pour vos tests d’intégration. À chaque passage de vos traits, Cucumber vous guidera vers les morceaux qu'il manque pour le faire passer.


Test de modèle avec RSpec et Shoulda

J'utilise principalement RSpec pour m'assurer que mes modèles et leurs méthodes restent en échec. Je l'utilise souvent aussi pour certains tests de contrôleur de haut niveau, mais cela va plus en détail que ce que permet ce guide. Nous allons utiliser le même modèle d'utilisateur que celui que nous avons précédemment configuré avec notre fonctionnalité de connexion. En regardant les résultats de l'exécution du générateur de modèle, nous pouvons voir que nous avons également user_spec.rb gratuitement. Si nous courons rspec spec / models / user_spec.rb nous devrions voir la sortie suivante.

 En attente: l'utilisateur ajoute des exemples à (ou supprime) /Users/janders/workspace/how\_i\_test/spec/models/user_spec.rb

Et si nous ouvrons ce fichier, nous voyons:

 require 'spechelper' decrire que l'utilisateur doit attendre "ajouter quelques exemples à (ou supprimer) # FILE" fin

La ligne en attente nous donne la sortie que nous avons vue dans le terminal. Nous tirerons parti des analyseurs ActiveRecord et ActiveModel de Shoulda pour nous assurer que notre modèle d'utilisateur correspond à notre logique métier..

 require 'spechelper' décrivent le contexte utilisateur "#fields" do it devrait répondre à (email) il devrait répondre à (: mot de passe) il devrait répondre à (: prénom) il devrait répondre à (: nom) fin contexte "#validations" do it doit validate_presence_of (: email) it doit validate_presence_of (: mot de passe) it devrait valider_uniqueness_of (: email) end context "#associations" do it devrait avoir_many (: tâches) end decrire "#methods" do let! (: user) FactoryGirl.create (: user) it "nom devrait renvoyer le nom d'utilisateur" do user.name.should eql "Testy McTesterson" end end end

Nous installons quelques blocs de contexte dans notre premier décrire bloquer pour tester des choses comme les champs, les validations et les associations. Bien qu'il n'y ait pas de différences fonctionnelles entre un décrire et un le contexte bloc, il y a un contextuel. Nous utilisons décrire des blocs pour définir l'état de ce que nous testons, et le contexte des blocs pour regrouper ces tests. Cela rend nos tests plus lisibles et maintenables à long terme.

La première décrire nous permet de tester contre la Utilisateur modèle dans un état non modifié.

Nous utilisons cet état non modifié pour tester la base de données avec les correspondeurs de Shoulda groupant chacun par type. Le suivant décrire bloquer met en place un utilisateur de notre précédemment créé utilisateur usine. Configurer l’utilisateur avec le laisser La méthode à l'intérieur de ce bloc nous permet de tester une instance de notre modèle utilisateur par rapport à des attributs connus..

Maintenant, quand nous courons rspec spec / models / user_spec.rb, nous voyons que tous nos nouveaux tests échouent.

 Échecs: 1) Le nom des méthodes utilisateur # doit renvoyer le nom de l'utilisateur. # ./spec/models/user_spec.rb:26:inbloc (3 niveaux) dans '2) utilisateur # validations Échec / Erreur: il devrait valider_uniqueness_of (: email) Les erreurs attendues à inclure "a déjà été prise" lorsque l'email est défini sur "arbitraire"string ", n'a pas d'erreur # ./spec/models/userspec.rb: 15: in 'block (3 level) in '3) utilisateur # validations Échec / erreur: il devrait valider_présence_of (: mot_passe) Les erreurs attendues à inclure "ne peuvent pas être vides" lorsque le mot de passe est défini sur nil, aucune erreur # ./spec/models/user_spec.rb : 14: en bloc (3 niveaux) en '4) Validation de l'utilisateur # Échec / Erreur: doit être validate_presence_of (: email) Les erreurs attendues à inclure "ne peuvent pas être vides" lorsque l'email est défini sur nil, aucune erreur # ./spec/models/user_spec.rb : 13: en bloc (3 niveaux) en '5) User # associations Failure / Error: il devrait avoirmany (: tasks) Utilisateur prévu pour avoir un ade nombreuses associations appelées tâches (aucune association appelée tâches) # ./spec/models/user_spec.rb:19:in 'block (3 level) in '6) Champs utilisateur # Echec / Erreur: il devrait répondredurernom) attendu # répondre à: derniernom # ./spec/models/userspec.rb: 9: in 'block (3 level) in '7) Champs utilisateur # Echec / Erreur: il devrait répondreà (: premiernom) attendu # répondre à: premiernom # ./spec/models/userspec.rb: 8: dans bloc (3 niveaux) dans '

Chacun de ces tests échouant, nous avons le cadre nécessaire pour ajouter des migrations, des méthodes, des associations et des validations à nos modèles. Au fur et à mesure que notre application évolue, que nos modèles se développent et que notre schéma change, ce niveau de test nous fournit une protection pour l'introduction de modifications de dernière minute..


Conclusion

Bien que nous n'ayons pas abordé trop de sujets en profondeur, vous devez maintenant avoir une compréhension de base de l'intégration et des tests unitaires avec Cucumber et RSpec. TDD / BDD est l’une des choses que les développeurs semblent faire ou ne pas faire, mais j’ai constaté que les avantages de TDD / BDD dépassaient de loin les inconvénients à plus d’une occasion..