[RUBY] Réglez RSpec sur DRY

C'est un mémorandum.

Références Rails au quotidien - Introduction aux tests de rails avec RSpec https://leanpub.com/everydayrailsrspec-jp

Module d'assistance

C'est une méthode pour éliminer la duplication de code et standardiser le traitement (traitement de connexion, etc.). Par exemple, s'il y a cinq tests qui exécutent le processus de connexion à l'avance, lorsque le bouton de connexion est modifié, cinq changements sont nécessaires. Le séparer en un seul module améliore la lisibilité tout en minimisant les modifications.

Essayez de séparer le processus de connexion suivant en modules

#Obtenir l'écran de connexion
visit root_path
#Cliquez sur connexion
click_link "Sign in"
#Saisissez l'adresse e-mail de l'utilisateur dans le champ de texte Email
fill_in "Email", with: user.email 
#Entrez le mot de passe comme ci-dessus
fill_in "Password", with: user.password
#Cliquez sur le bouton de connexion
click_button "Log in"

Supposons que le code des méthodes décrites dans ce module soit dispersé dans tout le test.

Pour détacher ce code, créez spec / suprot / login_support.rb et modulez-le.

login_support.rb


module LoginSupport
 def sign_in_as(user)
    visit root_path
    click_link "Sign in"
    fill_in "Email", with: user.email
    fill_in "Password", with: user.password
    click_button "Log in"
 end
#Lire l'ensemble de RSpec
RSpec.configure do |config|
  config.include LoginSupport
end
↓
#Si vous voulez le lire explicitement pour chaque test, dans le fichier de spécification que vous voulez lire
include LoginSupport
#Si tel est le cas, il sera lu.

Vous pouvez maintenant appeler les méthodes (méthodes d'assistance) dans le module. Comparons avant et après réécriture. Everyday Rails - Empruntez le code pour vous familiariser avec les tests Rails avec RSpec.

Avant le remplacement

projects_spec.rb


require 'rails_helper'

RSpec.feature "Projects", type: :feature do
  scenario "user creates a new project" do
    user = FactoryBot.create(:user)

  #d'ici
    visit root_path
    click_link "Sign in"
    fill_in "Email", with: user.email
    fill_in "Password", with: user.password
    click_button "Log in"
  #Remplacez jusqu'à ici par la méthode d'assistance

    expect {
      click_link "New Project"
      fill_in "Name", with: "Test Project"
      fill_in "Description", with: "Trying out Capybara"
      click_button "Create Project"

      expect(page).to have_content "Project was successfully created"
      expect(page).to have_content "Test Project"
      expect(page).to have_content "Owner: #{user.name}"
    }.to change(user.projects, :count).by(1)
  end
end

Après remplacement

projects_spec.rb


require 'rails_helper'

RSpec.feature "Projects", type: :feature do
  scenario "user creates a new project" do
    user = FactoryBot.create(:user)

  #d'ici

    sign_in_as(user)
  #Ou
  sign_in_as user
   #Mais d'accord

  #J'ai remplacé ce qui précède par une méthode d'aide

    expect {
      click_link "New Project"
      fill_in "Name", with: "Test Project"
      fill_in "Description", with: "Trying out Capybara"
      click_button "Create Project"

      expect(page).to have_content "Project was successfully created"
      expect(page).to have_content "Test Project"
      expect(page).to have_content "Owner: #{user.name}"
    }.to change(user.projects, :count).by(1)
  end
end

L'important ici est de rendre le nom de la méthode facile à comprendre. Il est important de garder à l'esprit un nom que vous pouvez facilement comprendre, car vous ne pouvez pas comprendre l'opération par le nom de la méthode, et faire référence au fichier d'origine ne fera que rendre le test gênant.

Chargement retardé avec let

Dans certains cas, les données requises pour le test sont configurées avant le test (processus de création de données tel que la création d'utilisateur) avant d'exécuter la description ou le contexte. Comme before est exécuté à chaque fois que describe ou context est exécuté, les problèmes suivants peuvent survenir. -Parce qu'il est exécuté à chaque fois, cela peut avoir un effet inattendu sur le test. -Il existe un risque élevé que les performances du test soient réduites car il est exécuté à chaque fois. -A mesure que la quantité de test augmente, la quantité de code écrit augmente, ce qui nuit au coût et à la lisibilité.

C'est la lecture retardée par let qui efface cela. Puisque let n'est exécuté que lorsqu'il est appelé, les problèmes ci-dessus peuvent être résolus. Comparons avant et après réécriture. Everyday Rails - Empruntez le code pour vous familiariser avec les tests Rails avec RSpec.

laissez inutilisé

note_spec.rb


require 'rails_helper'

RSpec.describe Note, type: :model do
  before do
    #Utilisateur créé
    @user = User.create(
      first_name: "Joe",
      last_name:  "Tester",
      email:      "[email protected]",
      password:   "dottle-nouveau-pavilion-tights-furze",
    )
    #Création de projet
    @project = @user.projects.create(
      name: "Test Project",
    )
  end

  it "Doit être valide s'il y a des utilisateurs, des projets, des messages" do
    note = Note.new(
      message: "This is a sample note.",
      user: @user,
      project: @project,
    )
    expect(note).to be_valid
  end

  it "S'il n'y a pas de message, il est dans un état invalide" do
    note = Note.new(message: nil)
    note.valid?
    expect(note.errors[:message]).to include("can't be blank")
  end

  describe "Rechercher les messages qui correspondent à la chaîne" do
    before do
      @note1 = @project.notes.create(
        message: "This is the first note.",
        user: @user,
      )
      @note2 = @project.notes.create(
        message: "This is the second note.",
        user: @user,
      )
      @note3 = @project.notes.create(
        message: "First, preheat the oven.",
        user: @user,
      )
    end

    context "Lorsque des données correspondantes sont trouvées" do
      it "Renvoyer des notes qui correspondent à la chaîne de recherche" do
        expect(Note.search("first")).to include(@note1, @note3)
      end
    end

    context "Lorsqu'aucune donnée correspondante n'est trouvée" do
      it "Retourner une collection vide" do
        expect(Note.search("message")).to be_empty
      end
    end
  end
end

utiliser let

note_spec.rb


require 'rails_helper'

RSpec.describe Note, type: :model do
  #utilisez let au lieu d'avant
  let(:user) { FactoryBot.create(:user) }
  let(:project) { FactoryBot.create(:project, owner: user) }

  it "Doit être valide s'il y a des utilisateurs, des projets, des messages" do
    note = Note.new(
      message: "This is a sample note.",
      user: user,
      project: project,
    )
    expect(note).to be_valid
  end

  it "S'il n'y a pas de message, il est dans un état invalide" do
    note = Note.new(message: nil)
    note.valid?
    expect(note.errors[:message]).to include("can't be blank")
  end

  describe "Rechercher les messages qui correspondent à la chaîne" do
    #utilisez let au lieu d'avant
    let(:note1) {
      FactoryBot.create(:note,
        project: project,
        user: user,
        message: "This is the first note."
      )
    }

    let(:note2) {
      FactoryBot.create(:note,
        project: project,
        user: user,
        message: "This is the second note."
      )
    }

    let(:note3) {
      FactoryBot.create(:note,
        project: project,
        user: user,
        message: "First, preheat the oven."
      )
    }

    context "Lorsque des données correspondantes sont trouvées" do
      it "Renvoyer des notes qui correspondent à la chaîne de recherche" do
        expect(Note.search("first")).to include(note1, note3)
      end
    end

    context "Lorsqu'aucune donnée correspondante n'est trouvée" do
      it "Retourner une collection vide" do
        expect(Note.search("message")).to be_empty
      end
    end
  end
end

Lors de l'utilisation précédente, les variables d'instance étaient utilisées pour traverser la portée du test, mais dans let, elles sont décrites par des variables. Maintenant que le test réussit, un problème se pose ici. Ajoutez expect (Note.count) .à l'éq 3 au dernier "retour d'une collection vide".

note_spec.rb


    context "Lorsqu'aucune donnée correspondante n'est trouvée" do
      it "Retourner une collection vide" do
        expect(Note.search("message")).to be_empty
     #Ajouter cette phrase
     expect(Note.count).to eq 3
      end
    end
  end
end

La description ci-dessus ne passe pas le test. La raison est simple: let est une lecture paresseuse.

En bref, aucune donnée n'est créée à moins d'être appelée.

Avec expect (Note.count) .to eq 3, le test va à la base de données pour trouver les données. Naturellement, le DB est vide car let n'est pas appelé. Cependant, puisque la définition de l'attente est que le nombre de données dans la note est égal à 3, le nombre de données est égal à 0, le test échoue et une erreur se produit.

Une façon de résoudre ce problème est d'appeler simplement let, Un exemple utilisant let! Est présenté ci-dessous.

note_spec.rb


   #laisser au lieu de laisser!utilisation
    let!(:note1) {
      FactoryBot.create(:note,
        project: project,
        user: user,
        message: "This is the first note."
      )
    }

    let!(:note2) {
      FactoryBot.create(:note,
        project: project,
        user: user,
        message: "This is the second note."
      )
    }

    let!(:note3) {
      FactoryBot.create(:note,
        project: project,
        user: user,
        message: "First, preheat the oven."
      )
    }

   #====réduction=====

    context "Lorsqu'aucune donnée correspondante n'est trouvée" do
      it "Retourner une collection vide" do
        expect(Note.search("message")).to be_empty
     #Ajouter cette phrase
     expect(Note.count).to eq 3
      end
    end
  end
end

Cela passera le test. Laissez! Précharge pendant que le délai est lu. L'image est la même que précédemment et des données sont créées pour chaque bloc d'évaluation.

Cela signifie que les problèmes mentionnés ci-dessus n'ont pas été résolus. Dans ce cas, utiliser avant ou laisser! Dépend de la situation. Du point de vue de l'apprentissage de moi-même, je pense qu'il est normal d'utiliser avant et de laisser correctement.

En guise de conclusion, laissez essentiellement! Je veux continuer dans le sens de ne pas utiliser. Je voudrais laisser cela à avant lorsque le pré-chargement est nécessaire, plutôt que de faire des erreurs de lecture ou de pocha car il n'y a qu'une différence subtile entre let et let!.

shared_context

shared_context vous permet d'effectuer la configuration requise avec plusieurs fichiers de test. L'utilisation est similaire au module de support. Comparons avant et après réécriture. Everyday Rails - Empruntez le code pour vous familiariser avec les tests Rails avec RSpec.

tasks_controller.rb


require 'rails_helper'

RSpec.describe TasksController, type: :controller do
  #Résumez la configuration ici
  let(:user) { FactoryBot.create(:user) }
  let(:project) { FactoryBot.create(:project, owner: user) }
  let(:task) { project.tasks.create!(name: "Task task") }

  describe "#show" do
    it "responds with JSON formatted output" do
      sign_in user
      get :show, format: :json,
        params: { project_id: project.id, id: task.id }
      expect(response.content_type).to eq "application/json"
    end
  end

  describe "#create" do
    it "responds with JSON formatted output" do
      new_task = { name: "New test task" }
      sign_in user
      post :create, format: :json,
        params: { project_id: project.id, task: new_task }
      expect(response.content_type).to eq "application/json"
    end

    it "adds a new task to the project" do
      new_task = { name: "New test task" }
      sign_in user
      expect {
        post :create, format: :json,
          params: { project_id: project.id, task: new_task }
      }.to change(project.tasks, :count).by(1)
    end

    it "requires authentication" do
      new_task = { name: "New test task" }
      # Don't sign in this time ...
      expect {
        post :create, format: :json,
          params: { project_id: project.id, task: new_task }
      }.to_not change(project.tasks, :count)
      expect(response).to_not be_success
    end
  end
end

Créez un fichier séparé et combinez les 3 lignes de let avec shared_context Créer spec / support / contextts / project_setup.rb

project_setup.rb


RSpec.shared_context "project setup" do
  let(:user) { FactoryBot.create(:user) }
  let(:project) { FactoryBot.create(:project, owner: user) }
  let(:task) { project.tasks.create!(name: "Task task") }
end

Revenez à tasks_controller.rb et réécrivez la partie let.

tasks_controller.rb


require 'rails_helper'

RSpec.describe TasksController, type: :controller do
  #Supprimer let et projet_setup.Incluez rb.
  include_context "project setup"

#====Omis ci-dessous=====

Vous disposez maintenant de plusieurs fichiers de test pour combiner les configurations requises en un seul.

Sommaire

・ Module d'assistance Vous pouvez supprimer les traitements courants tels que les méthodes d'assistance dans un fichier séparé et les inclure si nécessaire. · Laisser Il est possible de créer des données à utiliser pour les tests. Impact sur les tests en raison d'un chargement retardé On peut s'attendre à ce qu'il améliore les performances. ・ Shared_context Il est possible de rassembler la configuration utilisée pour les tests. Découpez-le dans un autre fichier et utilisez-le comme inclusion.

Merci de rester avec nous jusqu'à la fin. Si vous avez des suggestions ou des conseils, je vous serais reconnaissant de bien vouloir commenter.

Recommended Posts

Réglez RSpec sur DRY
[RSpec] Comment tester les messages d'erreur définis indépendamment par Shoulda-Matchers
Comment définir nginx de Docker
Comment définir des constantes Java
Éléments à définir après l'installation de RubyMine
Comment configurer Spring Boot + PostgreSQL
Comment définir Lombok dans Eclipse
à_ ○
Définissez le résultat de l'authentification Spring Security sur JSON
Un moyen simple de définir les icônes des applications iOS
[Note] Comment démarrer avec Rspec
Comment configurer et utiliser kapt
[Java] Comment régler la date sur 00:00:00
Comment définir JAVA_HOME avec l'appassembler-maven-plugin de Maven
Nécessaire pour iOS 14? Comment définir NSUserTrackingUsageDescription