<% content_for :menu do %>
  • Introduction aux bonnes pratiques rspec
  • » Comment décrire vos méthodes
  • » Utiliser «context»
  • » Garder vos descriptions courtes
  • » Test d'attente unitaire
  • » Test de tous les cas possibles
  • » «Expect» contre «Should»
  • » Utiliser «subject»
  • » Utiliser «let» et «let!»
  • » «Mock or not to mock»
  • » Créer uniquement les données requises
  • » Utiliser les «factories» et ne pas utiliser les «fixtures»
  • » Des «matchers» facile à lire
  • » Exemples partagés
  • » Tester ce que vous voyez
  • » Ne pas utiliser «should»
  • » Exécuter automatiquement les tests avec Guard
  • » Des tests plus rapides en préchargeant Rails
  • » Émuler les requêtes HTTP
  • » Formater intelligemment
  • Livres
  • Présentations
  • Ressources en ligne
  • Screencasts
  • Documentation
  • Styleguide
  • Contribuer à Better Specs
  • Crédits
  • Aidez-nous
  • <% end %>

    RSpec est un excellent outil dans le processus de développement BDD utilisé pour écrire des tests lisibles, et vérifier le bon déroulement de la conception de votre application.

    Il existe de nombreuses ressources en ligne vous donnant un aperçu de ce que vous pouvez faire avec RSpec, mais peu d'entre elles vous disent comment améliorer votre suite de tests.

    Better Specs essaye de remplir ce rôle en collectant les bonnes pratiques que les autres développeurs ont acquis au cours de leurs années d'expérience.

    Comment décrire vos méthodes

    Soyez clair dans la description de vos méthodes. Par exemple, utiliser la convention issue de la documentation Ruby qui consiste à utiliser . (ou ::) lorsque vous faites référence à une méthode de classe, et # lorsque vous faites référence à une méthode d'instance.

    mauvais

    describe 'the authenticate method for User' do
    describe 'if the user is an admin' do
    

    bon

    describe '.authenticate' do
    describe '#admin?' do
    

    Discuter de ce conseil →

    <%= render "partials/share", text: "betterspecs.org | how to describe your methods", url: "http://betterspecs.org/#describe" %>

    Utiliser «context»

    «Context» est une méthode qui permet d'obtenir des tests clairs, et une bonne organisation. Sur le long terme, elle devrait vous permettre de garder des tests lisibles.

    mauvais

    it 'has 200 status code if logged in' do
      expect(response).to respond_with 200
    end
    it 'has 401 status code if not logged in' do
      expect(response).to respond_with 401
    end
    

    bon

    context 'when logged in' do
      it { is_expected.to respond_with 200 }
    end
    context 'when logged out' do
      it { is_expected.to respond_with 401 }
    end
    

    Quand vous utilisez «context», commencer votre description avec «when» ou avec «with».

    Discuter de ce conseil →

    <%= render "partials/share", text: "betterspecs.org | use contexts", url: "http://betterspecs.org/#contexts" %>

    Garder vos descriptions courtes

    Vos descriptions ne doivent jamais dépasser 40 caractères. Si c'est le cas, découpez les en utilisant «context».

    mauvais

    it 'has 422 status code if an unexpected params will be added' do
    

    bon

    context 'when not valid' do
      it { should respond_with 422 }
    end
    

    Dans cet exemple, nous avons supprimé la description par it { should respond_with 422 }. En lançant ce test, vous obtenez une réponse lisible.

    Sortie formatée

    when not valid
      it should respond with 422
    

    Discuter de ce conseil →

    <%= render "partials/share", text: "betterspecs.org | keep your description short", url: "http://betterspecs.org/#short" %>

    Test d'attente unitaire

    Une seule affirmation par test. Ce qui vous permet de détecter le test qui a échoué, et également de déceler les erreurs possibles. Ce conseil contribue à rendre votre code plus lisible.

    Dans une spécification unitaire isolée, spécifier un seul comportement. Des attentes multiples dans un même exemple sont le signe que vous devriez spécifier plusieurs comportements.

    Dans le cas de tests non isolés (intégration avec une base de donnée, un service web externe), vous noterez une baisse de performance en assignant un comportement différent pour chaque test. Dans ce cas uniquement spécifier plus d'un comportement par test.

    bon (isolé)

    it { should respond_with_content_type(:json) }
    it { should assign_to(:resource) }
    

    bon (non isolé)

    it 'creates a resource' do
      expect(response).to respond_with_content_type(:json)
      expect(response).to assign_to(:resource)
    end
    
    Discuter de ce conseil →

    <%= render "partials/share", text: "betterspecs.org | single expectation test", url: "http://betterspecs.org/#single" %>

    Test de tous les cas possibles

    Tester est une bonne pratique, mais est inutile si vous ne testez pas tous les cas possibles. Par exemple, dans l'exemple suivant.

    Destroy action

    before_action :find_owned_resources
    before_action :find_resource
    
    def destroy
      render 'show'
      @consumption.destroy
    end

    L'erreur que je vois souvent est que l'on teste seulement si la ressource a été supprimée. Mais il y a au moins deux autres cas : quand la ressource n'est pas trouvée et quand vous n'êtes pas le propriétaire de la ressource. En règle générale, penser à toutes les entrées possibles, et tester les.

    mauvais

    it 'shows the resource'
    

    bon

    describe '#destroy' do
    
      context 'when resource is found' do
        it 'responds with 200'
        it 'shows the resource'
      end
    
      context 'when resource is not found' do
        it 'responds with 404'
      end
    
      context 'when resource is not owned' do
        it 'responds with 404'
      end
    end
    

    Discuter de ce conseil →

    <%= render "partials/share", text: "betterspecs.org | test all possible cases", url: "http://betterspecs.org/#all" %>

    «Expect» contre «Should»

    Dans vos nouveaux projets, utiliser la syntaxe expect.

    mauvais

    it 'creates a resource' do
      response.should respond_with_content_type(:json)
    end

    bon

    it 'creates a resource' do
      expect(response).to respond_with_content_type(:json)
    end

    Configurer RSpec pour qu'il n'accepte que la nouvelle syntaxe dans vos nouveaux projets, et ainsi éviter d'avoir les deux syntaxes qui cohabitent.

    bon

    # spec_helper.rb
    RSpec.configure do |config|
      # ...
      config.expect_with :rspec do |c|
        c.syntax = :expect
      end
    end

    Dans les projets existants, utiliser transpec pour convertir vos tests vers la nouvelle syntaxe.

    Dans les attentes d'une seule ligne ou avec un sujet implicite continuer d'utiliser la syntaxe «should» plus d'informations

    Plus d'informations à propos de la nouvelle syntaxe peuvent être trouvées ici et ici.

    Discuter de ce conseil →

    <%= render "partials/share", text: "betterspecs.org | Expect vs Should syntax", url: "http://betterspecs.org/#expect" %>

    Utiliser «subject"

    Si vous avez plusieurs tests qui possèdent le même sujet, utiliser subject{} pour éviter la duplication de code.

    mauvais

    it { expect(assigns('message')).to match /it was born in Belville/ }
    

    bon

    subject { assigns('message') }
    it { should match /it was born in Billville/ }
    

    Vous pouvez également utiliser des sujets nommés avec RSpec.

    bon

    subject(:hero) { Hero.first }
    it "carries a sword" do
      expect(hero.equipment).to include "sword"
    end
    

    En apprendre plus sur «subject».

    Discuter de ce conseil →

    <%= render "partials/share", text: "betterspecs.org | use subject", url: "http://betterspecs.org/#subject" %>

    Utiliser «let» et «let!»

    Lors de l'assignation d'une variable, vous pouvez à la place de créer un bloc before utiliser let. L'utilisation de let rend la variable longue à charger la première fois, mais elle est ensuite mise en cache jusqu'à la fin du test. Une très bonne description de let peut être trouvée dans cette réponse stackoverflow.

    mauvais

    describe '#type_id' do
      before { @resource = FactoryBot.create :device }
      before { @type     = Type.find @resource.type_id }
    
      it 'sets the type_id field' do
        expect(@resource.type_id).to equal(@type.id)
      end
    end
    

    bon

    describe '#type_id' do
      let(:resource) { FactoryBot.create :device }
      let(:type)     { Type.find resource.type_id }
    
      it 'sets the type_id field' do
        expect(resource.type_id).to equal(type.id)
      end
    end
    

    Utiliser let pour initialiser des actions longues à charger pour tester vos spécifications.

    bon

    context 'when updates a not existing property value' do
      let(:properties) { { id: Settings.resource_id, value: 'on' } }
    
      def update
        resource.properties = properties
      end
    
      it 'raises a not found error' do
        expect { update }.to raise_error Mongoid::Errors::DocumentNotFound
      end
    end
    

    Utiliser let! si vous souhaitez définir une variable quand le bloc est défini. C'est utile lorsque vous peuplez votre base de données pour tester des requêtes ou des portées.

    Ici un exemple de «let».

    bon

    # this:
    let(:foo) { Foo.new }
    
    # is very nearly equivalent to this:
    def foo
      @foo ||= Foo.new
    end
    

    En apprendre plus sur «let».

    Discuter de ce conseil →

    <%= render "partials/share", text: "betterspecs.org | use let and let!", url: "http://betterspecs.org/#let" %>

    «Mock or not to mock»

    Ne pas (trop) utiliser les «mocks» et tester le comportement réel quand c'est possible. Tester les cas réels est utile lors de la mise à jour du flux de votre application.

    bon

    # simulate a not found resource
    context "when not found" do
      before { allow(Resource).to receive(:where).with(created_from: params[:id]).and_return(false) }
      it { should respond_with 404 }
    end
    

    Utiliser des «mocks» rend vos spécifications plus rapides mais elles sont plus difficiles à utiliser. Vous avez besoin de les maîtriser pour bien les utiliser. Lire plus à propos.

    Discuter de ce conseil →

    <%= render "partials/share", text: "betterspecs.org | mock or not to mock", url: "http://betterspecs.org/#mock" %>

    Créer seulement les données requises

    Si vous avez déjà travaillé sur des projets de taille moyenne (mais également sur des petits), les tests peuvent être long à exécuter. Pour résoudre ce problème, c'est important de ne pas charger plus de données que nécessaire. Si vous pensez que vous avez besoin de douzaines d'enregistrements, vous avez probablement tort.

    bon

    describe "User"
      describe ".top" do
        before { FactoryBot.create_list(:user, 3) }
        it { expect(User.top(2)).to have(2).item }
      end
    end
    

    Discuter de ce conseil →

    <%= render "partials/share", text: "betterspecs.org | create only tha data you need", url: "http://betterspecs.org/#data" %>

    Utiliser les «factories» et ne pas utiliser les «fixtures»

    Ne pas utiliser les «fixtures» car elles sont difficiles à maîtriser. Utiliser des "«factories" à la place qui permettent de réduire la verbosité dans la création de nouvelles données.

    mauvais

    user = User.create(
      name: 'Genoveffa',
      surname: 'Piccolina',
      city: 'Billyville',
      birth: '17 Agoust 1982',
      active: true
    )
    

    bon

    user = FactoryBot.create :user
    

    Quand nous parlons de tests unitaires la bonne pratique devrait de n'utiliser ni les «fixtures», ni les «factories». Mettez le maximum de logique dans des bibliothèques qui peuvent être testées sans complexité. Lire plus dans cet article

    En apprendre plus sur Factory Bot.

    Discuter de ce conseil →

    <%= render "partials/share", text: "betterspecs.org | use factories and not fixtures", url: "http://betterspecs.org/#factories" %>

    Des «matchers» facile à lire

    Utilisez des «matchers» lisibles et jetez un œil sur rspec matchers.

    mauvais

    lambda { model.save! }.to raise_error Mongoid::Errors::DocumentNotFound
    

    bon

    expect { model.save! }.to raise_error Mongoid::Errors::DocumentNotFound
    

    Discuter de ce conseil →

    <%= render "partials/share", text: "betterspecs.org | easy to read matcher", url: "http://betterspecs.org/#matchers" %>

    Exemples partagés

    Écrire des tests est bien, et vous devriez gagner en confiance chaque jour. Mais vous devriez commencer à voir de la duplication de code. Utiliser les exemples partagés pour réduire le nombre de répétitions.

    mauvais

    describe 'GET /devices' do
      let!(:resource) { FactoryBot.create :device, created_from: user.id }
      let(:uri) { '/devices' }
    
      context 'when shows all resources' do
        let!(:not_owned) { FactoryBot.create factory }
    
        it 'shows all owned resources' do
          page.driver.get uri
          expect(page.status_code).to be(200)
          contains_owned_resource resource
          does_not_contain_resource not_owned
        end
      end
    
      describe '?start=:uri' do
        it 'shows the next page' do
          page.driver.get uri, start: resource.uri
          expect(page.status_code).to be(200)
          contains_resource resources.first
          expect(page).to_not have_content resource.id.to_s
        end
      end
    end
    

    bon

    describe 'GET /devices' do
    
      let!(:resource) { FactoryBot.create :device, created_from: user.id }
      let(:uri)       { '/devices' }
    
      it_behaves_like 'a listable resource'
      it_behaves_like 'a paginable resource'
      it_behaves_like 'a searchable resource'
      it_behaves_like 'a filterable list'
    end
    

    De notre propre expérience, les exemples partagés sont utilisés principalement dans les contrôleurs. Les modèles sont très différents les un des autres, ils partagent peu de logique.

    En apprendre plus à propos desexemples partagés.

    Discuter de ce conseil →

    <%= render "partials/share", text: "betterspecs.org | shared examples", url: "http://betterspecs.org/#sharedexamples" %>

    Tester ce que vous voyez

    Tester profondément vos modèles, et le comportement de votre application (tests d'intégration). N'ajouter pas de tests inutiles pour vos contrôleurs.

    Quand j'ai commencé à tester mes applications je testais les contrôleurs, maintenant je ne le fais plus. Maintenant je crée seulement des tests d'intégration en utilisant RSpec et Capybara. Pourquoi ? Parce que je crois que vous devriez tester ce que vous voyez et parce que tester les contrôleurs est une étape supplémentaire qui n'est pas nécessaire. Vous devriez trouver que la plupart de vos tests concernant les modèles et les tests d'intégration peuvent être facilement groupés avec des exemples partagés, et ainsi construire une suite de tests claire et lisible.

    C'est un débat ouvert dans la communauté Ruby, avec des deux côtés de très bons arguments supportant leur idée. Les personnes qui supportent le besoin de tester les contrôleurs devraient dire que les tests d'intégration ne couvrent pas tous les cas et qu'ils sont lent.

    Les deux ont tort. Vous pouvez facilement couvrir tous les cas et vous pouvez lancer seulement un seul fichier spec en utilisant des outils automatisés comme Guard. Dans cette voie, vous devriez pouvoir lancer seulement les spécifications que vous avez besoin de tester rapidement sans ralentir votre flux de tâches.

    Discuter de ce conseil →

    <%= render "partials/share", text: "betterspecs.org | test what you see", url: "http://betterspecs.org/#integration" %>

    Ne pas utiliser «should»

    N'utiliser pas "should" quand vous décrivez vos tests. Utiliser la troisième personne au présent simple. Encore mieux commencer à utiliser la nouvelle syntaxe «should» syntax.

    mauvais

    it 'should not change timings' do
      consumption.occur_at.should == valid.occur_at
    end
    

    bon

    it 'does not change timings' do
      expect(consumption.occur_at).to equal(valid.occur_at)
    end
    

    Voir le «should_not» gem pour utiliser cette solution dans RSpec et le «should_clean» gem pour nettoyer les exemples RSpec qui commencent par «should».

    Discuter de ce conseil →

    <%= render "partials/share", text: "betterspecs.org | don't use should", url: "http://betterspecs.org/#should" %>

    Exécuter automatiquement les tests avec Guard

    Lancer la totalité des tests à chaque changement devrait être pesant. Cela prend du temps, et casse votre flux de tâches. Avec Guard, vous pouvez automatiser votre suite de tests en lançant seulement les tests liés à la spécification sur laquelle vous êtes en train de travailler.

    bon

    bundle exec guard
    
    Ici vous pouvez voir un exemple d'un fichier Guardfile avec des règles de rechargement basiques.

    bon

    guard 'rspec', cli: '--drb --format Fuubar --color', version: 2 do
      # run every updated spec file
      watch(%r{^spec/.+_spec\.rb$})
      # run the lib specs when a file in lib/ changes
      watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
      # run the model specs related to the changed model
      watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
      # run the view specs related to the changed view
      watch(%r{^app/(.*)(\.erb|\.haml)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" }
      # run the integration specs related to the changed controller
      watch(%r{^app/controllers/(.+)\.rb}) { |m| "spec/requests/#{m[1]}_spec.rb" }
      # run all integration tests when application controller change
      watch('app/controllers/application_controller.rb') { "spec/requests" }
    end
    

    Guard est un bon outil mais il ne couvre pas tous les besoins. Quelquefois votre flux TDD travaille mieux avec des raccourcis clavier qui rendent le lancement d'exemple unitaire aisé. Vous pouvez alors utiliser les tâches rake pour lancer la totalité des tests avant de pousser votre code. Voici quelques exemples des raccourcis vim.

    En apprendre plus sur guard-rspec.

    Discuter de ce conseil →

    <%= render "partials/share", text: "betterspecs.org | automatic tests with guard", url: "http://betterspecs.org/#guard" %>

    Des tests plus rapides en préchargeant Rails

    Quand vous lancez un test avec Rails l'application Rails entière est chargée. Ce qui peut prendre du temps et casser votre flux de développement. Pour résoudre ce problème utiliser des solutions comme Zeus, Spin ou Spork. Ces solutions devraient préchargées toutes les bibliothèques qui n'ont pas changées et recharger les contrôleurs, modèles, vues et «factories» ainsi que les fichiers qui changent régulièrement.

    Vous pouvez trouver un spec helper et un fichier de configuration Guardfile basé sur Spork. Avec cette configuration l'application ne sera entièrement rechargée que si un fichier préchargé (comme initializers) change et devrait lancer les tests uniques très rapidement.

    L'inconvénient d'utiliser Spork est que vous risquez de perdre quelques heures à essayer de comprendre pourquoi un fichier n'est pas rechargé. Si vous avec quelques exemples de code utilisant Spin or n'importe quelles autres solutions faites le nous savoir.

    Vous pouvez trouver ici un fichier de configurationGuardfile pour utiliser Zeus. Le spec_helper n'a pas besoin d'être modifié, mais vous devriez lancer `zeus start` dans une console pour démarrer le serveur zeus avant de lancer vos tests.

    Bien que Zeus a une approche moins agressive que Spork, l'inconvénient majeur est ses exigences strictes; Ruby 1.9.3+ (recommended using backported GC from Ruby 2.0) et un système d'exploitation qui supporte FSEVENTS ou inotify.

    Ces solutions subissent de nombreuses critiques. Ces bibliothèques sont un pansement à un problème qui devrait être résolu par une meilleure conception, et par le chargement intentionnel des dépendances nécessaires. En apprendre plus en lisant la conversation liée.

    Discuter de ce conseil →

    <%= render "partials/share", text: "betterspecs.org | faster tests with spork", url: "http://betterspecs.org/#spork" %>

    Émuler des requêtes HTTP

    Quelquefois vous avez besoin d’accéder à des services web externes. Dans ces cas vous ne pouvez pas relier au service réel mais vous pouvez le simuler avec des solutions comme webmock.

    bon

    context "with unauthorized access" do
      let(:uri) { 'http://api.lelylan.com/types' }
      before    { stub_request(:get, uri).to_return(status: 401, body: fixture('401.json')) }
      it "gets a not authorized notification" do
        page.driver.get uri
        expect(page).to have_content 'Access denied'
      end
    end
    

    En apprendre plus sur webmock and VCR. Ici une bonne présentation qui explique comment les utiliser ensemble.

    Discuter de ce conseil →

    <%= render "partials/share", text: "betterspecs.org | mocking http requests", url: "http://betterspecs.org/#http" %>

    Formater intelligemment

    Utiliser un format qui peut vous donner des informations utiles sur la suite de tests. Je trouve que fuubar est vraiment intéressant. Pour le faire fonctionner ajouter le gem et ajouter fuubar comme format par défaut dans votre fichier Guardfile.

    bon

    # Gemfile
    group :development, :test do
      gem 'fuubar'
    

    bon

    # Guardfile
    guard 'rspec' do
      # ...
    end
    

    bon

    # .rspec
    --drb
    --format Fuubar
    --color
    

    En apprendre plus sur fuubar.

    Discuter de ce conseil →

    <%= render "partials/share", text: "betterspecs.org | useful formatter", url: "http://betterspecs.org/#formatter" %>

    Livres

    <%= render "partials/books" %>

    Présentations

    RSpec 2 Best practices from Andrea Reginato

    Ressources en ligne

    1. Everyday Rails Spec
    2. Eggs on Bread Best Practices
    3. The Carbon Emitter Best Practices
    4. Andy Vanasse Best Practices
    5. Bitfluxx Best Practices
    6. Dmytro Shteflyuk Best Practices
    7. thoughtbot's RSpec Related Reading

    Screencasts

    1. RSpec on PeepCode
    2. Testing With RSpec Code School course
    3. Spork Railscast
    4. Using Zeus to speed up your tests
    5. Code TV Screencast on Guard and Spork
    6. Many of Destroy All Software screencasts

    Documentation

    1. RSpec Documentation
    2. Capybara Documentation
    3. Factory Bot Documentation
    4. Webmock Documentation
    5. Timecop Documentation
    6. Shoulda Matchers
    7. Fuubar Release

    Styleguide

    Nous sommes à la recherche des meilleures pratiques pour écrire des spécifications "facile à lire". Pour le moment, la Mongoid test suite est un bon début. Elle utilise un style clair, et des spécifications facile à lire, qui suivent les conseils décrit ici.

    Contribuer à Better Specs

    Il s'agit d'un projet open-source. Si quelque chose est manquant ou incorrect crée une issue pour discuter du sujet. Vérifier les problèmes suivis:

    Crédits

    Ce document a été préparé par Andrea Reginato. Un remerciement spéciale à l'équipe Lelylan Team. Traduit par Florent Ferry. Ce document est sous licence MIT.

    Aidez-nous

    Si ces conseils vous ont aidés à améliorer votre façon d'écrire des tests, vous pouvez faire un don de $9, et ainsi contribuer à l'amélioration de ce site.