Sådan TØRRES dine RSpec-tests ud ved hjælp af delte eksempler

" Giv mig seks timer til at hugge ned et træ, så bruger jeg de første fire på at slibe øksen." - Abraham Lincoln

Da jeg ombyggede et projekt for et par uger siden, brugte jeg det meste af min tid på at skrive specifikationer. Efter at have skrevet flere lignende testsager til nogle API'er begyndte jeg at spekulere på, om jeg måske kunne slippe af med meget af denne duplikering.

Så jeg kastede mig ind i at læse om de bedste fremgangsmåder til TØRRING af test (gentag ikke dig selv). Og sådan lærte jeg om shared examplesog shared contexts.

I mit tilfælde endte jeg med at bruge delte eksempler. Og her er hvad jeg hidtil har lært fra at anvende disse.

Når du har flere specifikationer, der beskriver lignende adfærd, kan det være bedre at udtrække overflødige eksempler i shared examplesog bruge dem i flere specifikationer.

Antag at du har to modeller Bruger og Indlæg , og en bruger kan have mange indlæg. Brugere skal kunne se listen over brugere og indlæg. Oprettelse af en indekshandling i brugerne og indlægskontrollerne tjener dette formål.

Skriv først specifikationer til din indekshandling for brugerens controller. Det har ansvaret for at hente brugere og gengive dem med korrekt layout. Skriv derefter nok kode til at få test bestået.

# users_controller_spec.rbdescribe "GET #index" do before do 5.times do FactoryGirl.create(:user) end get :index end it { expect(subject).to respond_with(:ok) } it { expect(subject).to render_template(:index) } it { expect(assigns(:users)).to match(User.all) }end
# users_controller.rbclass UsersController < ApplicationController .... def index @users = User.all end ....end

Typisk henter og samler indekshandling af enhver controller data fra få ressourcer efter behov. Det tilføjer også pagination, søgning, sortering, filtrering og scoping.

Endelig præsenteres alle disse data for visninger via HTML, JSON eller XML ved hjælp af API'er. For at forenkle mit eksempel henter controllers indekshandlinger bare data og viser dem derefter via visninger.

Det samme gælder for indekshandling i posts-controlleren:

describe "GET #index" do before do 5.times do FactoryGirl.create(:post) end get :index end it { expect(subject).to respond_with(:ok) } it { expect(subject).to render_template(:index) } it { expect(assigns(:posts)).to match(Post.all) }end
# posts_controller.rbclass PostsController < ApplicationController .... def index @posts = Post.all end ....end

RSpec-tests skrevet til både brugere og posts-controller er meget ens. I begge controllere har vi:

  • Svarskoden - skal være 'OK'
  • Begge indekshandlinger skal gøre korrekt delvis eller vist - i vores tilfælde index
  • De data, vi vil gengive, f.eks. Indlæg eller brugere

Lad os tørre specifikationerne for vores indekshandling ved hjælp af shared examples.

Hvor skal du placere dine delte eksempler

Jeg kan godt lide at placere delte eksempler inde i kataloget specs / support / shared_examples, så alle shared examplerelaterede filer indlæses automatisk.

Du kan læse om andre almindeligt anvendte konventioner til at finde din shared examplesher: dokumentation om delte eksempler

Sådan defineres et delt eksempel

Din indekshandling skal svare med 200 succeskoder (OK) og gengive din indeksskabelon.

RSpec.shared_examples "index examples" do it { expect(subject).to respond_with(:ok) } it { expect(subject).to render_template(:index) }end

Bortset fra dine itblokke - og før og efter dine kroge - kan du tilføje letblokke, kontekst og beskrive blokke, som også kan defineres indeni shared examples.

Jeg foretrækker personligt at holde delte eksempler enkle og koncise og ikke tilføje sammenhænge og lade blokke. Den shared examplesblok accepterer også parametre, som jeg vil dække nedenfor.

Sådan bruges delte eksempler

Tilføjelse include_examples "index examples"til dine brugere og indlæg controllerspecifikationer inkluderer "indekseksempler" til dine tests.

# users_controller_spec.rbdescribe "GET #index" do before do 5.times do FactoryGirl.create(:user) end get :index end include_examples "index examples" it { expect(assigns(:users)).to match(User.all) }end
# similarly, in posts_controller_spec.rbdescribe "GET #index" do before do 5.times do FactoryGirl.create(:post) end get :index end include_examples "index examples" it { expect(assigns(:posts)).to match(Post.all) }end

Du kan også bruge it_behaves_likeeller i it_should_behaves_likestedet for include_examplesi dette tilfælde. it_behaves_likeog it_should_behaves_likeer faktisk aliasser og fungerer på samme måde, så de kan bruges om hverandre. Men include_examplesog it_behaves_likeer forskellige.

Som anført i den officielle dokumentation:

  • include_examples - inkluderer eksempler i den aktuelle sammenhæng
  • it_behaves_likeog it_should_behave_likeinkluderer eksemplerne i en indlejret sammenhæng

Hvorfor betyder denne sondring noget?

RSpecs dokumentation giver et ordentligt svar:

Når du inkluderer parametriserede eksempler i den aktuelle kontekst flere gange, kan du tilsidesætte tidligere definitioner af metoden og den sidste erklæring vinder.

Så når du står over for en situation, hvor parametriserede eksempler indeholder metoder, der er i konflikt med andre metoder i samme sammenhæng, kan du erstatte include_examplesmed it_behaves_likemetode. Dette vil skabe en indlejret kontekst og undgå denne slags situationer.

Tjek følgende linje i dine brugercontrolspecifikationer, og post controller-specifikationer:

it { expect(assigns(:users)).to match(User.all) }it { expect(assigns(:posts)).to match(Post.all) }

Nu kan dine controllerspecifikationer re-factored yderligere ved at videregive parametre til delt eksempel som nedenfor:

# specs/support/shared_examples/index_examples.rb
# here assigned_resource and resource class are parameters passed to index examples block RSpec.shared_examples "index examples" do |assigned_resource, resource_class| it { expect(subject).to respond_with(:ok) } it { expect(subject).to render_template(:index) } it { expect(assigns(assigned_resource)).to match(resource_class.all) }end

Foretag nu følgende ændringer til dine brugere og indlæg controller-specifikationer:

# users_controller_spec.rbdescribe "GET #index" do before do ... end include_examples "index examples", :users, User.allend
# posts_controller_spec.rbdescribe "GET #index" do before do ... end include_examples "index examples", :posts, Post.allend

Nu ser controller-specifikationer rene, mindre overflødige og vigtigere ud, TØRRE. Desuden kan disse indekseksempler tjene som grundlæggende strukturer til at designe andre controllers indekshandling.

Konklusion

Ved at flytte almindelige eksempler til en separat fil kan du fjerne dobbeltarbejde og forbedre konsistensen af ​​dine controllerhandlinger i hele din applikation. Dette er meget nyttigt i tilfælde af design af API'er, da du kan bruge den eksisterende struktur af RSpec-tests til at designe tests og oprette API'er, der overholder din almindelige svarstruktur.

Når jeg arbejder med API'er, bruger jeg oftest shared examplesen fælles struktur til at designe lignende API'er.

Del gerne, hvordan du TØRRER dine specifikationer ved hjælp af shared examples.