RSpec - Guida rapida
RSpec è un framework di unit test per il linguaggio di programmazione Ruby. RSpec è diverso dai tradizionali framework xUnit come JUnit perché RSpec è uno strumento di sviluppo guidato dal comportamento. Ciò significa che i test scritti in RSpec si concentrano sul "comportamento" di un'applicazione testata. RSpec non pone l'accento su come funziona l'applicazione ma piuttosto su come si comporta, in altre parole, su cosa fa effettivamente l'applicazione.
Ambiente RSpec
Prima di tutto, dovrai installare Ruby sul tuo computer. Tuttavia, se non l'hai già fatto prima, puoi scaricare e installare Ruby dal sito Web principale di Ruby - Ruby .
Se stai installando Ruby su Windows, dovresti avere il programma di installazione di Ruby per Windows qui su - http://www.rubyinstaller.org
Per questo tutorial, avrai solo bisogno di un editor di testo, come Blocco note e una console a riga di comando. Gli esempi qui useranno cmd.exe su Windows.
Per eseguire cmd.exe, è sufficiente fare clic sul menu Start e digitare "cmd.exe", quindi premere il tasto Invio.
Al prompt dei comandi nella finestra cmd.exe, digita il seguente comando per vedere quale versione di Ruby stai utilizzando:
ruby -v
Dovresti vedere l'output di seguito simile a questo:
ruby 2.2.3p173 (2015-08-18 revision 51636) [x64-mingw32]
Gli esempi in questo tutorial useranno Ruby 2.2.3 ma qualsiasi versione di Ruby superiore alla 2.0.0 sarà sufficiente. Successivamente, dobbiamo installare la gem RSpec per la tua installazione di Ruby. Una gemma è una libreria Ruby che puoi usare nel tuo codice. Per installare una gemma, è necessario utilizzare ilgem comando.
Installiamo ora la gemma Rspec. Torna alla finestra cmd.exe e digita quanto segue:
gem install rspec
Dovresti avere un elenco di gemme dipendenti che sono state installate, queste sono gemme di cui la gem rspec ha bisogno per funzionare correttamente. Alla fine dell'output, dovresti vedere qualcosa che assomiglia a questo -
Done installing documentation for diff-lcs, rspec-support, rspec-mocks,
rspec-expectations, rspec-core, rspec after 22 seconds
6 gems installed
Non preoccuparti, se il tuo output non è esattamente lo stesso. Inoltre, se utilizzi un computer Mac o Linux, potrebbe essere necessario eseguire entrambigem install rspec comando utilizzando sudo oppure usa uno strumento come HomeBrew o RVM per installare rspec gem.
Hello World
Per iniziare, creiamo una directory (cartella) per memorizzare i nostri file RSpec. Nella finestra cmd.exe, digita quanto segue:
cd \
Quindi digitare -
mkdir rspec_tutorial
E infine, digita -
cd rspec_tutorial
Da qui, creeremo un'altra directory denominata spec, fallo digitando -
mkdir spec
Memorizzeremo i nostri file RSpec in questa cartella. I file RSpec sono noti come "specifiche". Se questo ti sembra confuso, puoi pensare a un file spec come a un file di prova. RSpec utilizza il termine "specifica" che è una forma abbreviata per "specifica".
Poiché RSpec è uno strumento di test BDD, l'obiettivo è concentrarsi su ciò che fa l'applicazione e se segue o meno una specifica. Nello sviluppo guidato dal comportamento, la specifica è spesso descritta in termini di "User Story". RSpec è progettato per chiarire se il codice di destinazione si sta comportando correttamente, in altre parole seguendo la specifica.
Torniamo al nostro codice Hello World. Apri un editor di testo e aggiungi il seguente codice:
class HelloWorld
def say_hello
"Hello World!"
end
end
describe HelloWorld do
context “When testing the HelloWorld class” do
it "should say 'Hello World' when we call the say_hello method" do
hw = HelloWorld.new
message = hw.say_hello
expect(message).to eq "Hello World!"
end
end
end
Quindi, salvalo in un file denominato hello_world_spec.rb nella cartella delle specifiche che hai creato sopra. Ora torna nella finestra cmd.exe, esegui questo comando -
rspec spec spec\hello_world_spec.rb
Al termine del comando, dovresti vedere un output simile a questo:
Finished in 0.002 seconds (files took 0.11101 seconds to load)
1 example, 0 failures
Congratulazioni, hai appena creato ed eseguito il tuo primo unit test RSpec!
Nella sezione successiva, continueremo a discutere la sintassi dei file RSpec.
Diamo uno sguardo più da vicino al codice del nostro HelloWorldesempio. Prima di tutto, nel caso non fosse chiaro, stiamo testando la funzionalità diHelloWorldclasse. Questa ovviamente è una classe molto semplice che contiene un solo metodosay_hello().
Ecco di nuovo il codice RSpec -
describe HelloWorld do
context “When testing the HelloWorld class” do
it "The say_hello method should return 'Hello World'" do
hw = HelloWorld.new
message = hw.say_hello
expect(message).to eq "Hello World!"
end
end
end
La parola chiave di descrizione
La parola describeè una parola chiave RSpec. Viene utilizzato per definire un "gruppo di esempio". Puoi pensare a un "gruppo di esempio" come a una raccolta di test. Ildescribela parola chiave può accettare un nome di classe e / o un argomento stringa. È inoltre necessario passare un argomento di blocco adescribe, questo conterrà i singoli test, o come sono conosciuti in RSpec, gli "Esempi". Il blocco è solo un blocco Ruby designato dal Rubydo/end parole chiave.
La parola chiave di contesto
Il context la parola chiave è simile a describe. Anch'esso può accettare un nome di classe e / o un argomento stringa. Dovresti usare un blocco concontextanche. L'idea di contesto è che racchiude test di un certo tipo.
Ad esempio, puoi specificare gruppi di esempi con contesti diversi come questo:
context “When passing bad parameters to the foobar() method”
context “When passing valid parameters to the foobar() method”
context “When testing corner cases with the foobar() method”
Il context la parola chiave non è obbligatoria, ma aiuta ad aggiungere ulteriori dettagli sugli esempi che contiene.
La parola chiave it
La parola itè un'altra parola chiave RSpec utilizzata per definire un "Esempio". Un esempio è fondamentalmente un test o un test case. Di nuovo, mi piacedescribe e context, it accetta sia il nome della classe che gli argomenti stringa e deve essere utilizzato con un argomento blocco, designato con do/end. In caso diit, è consuetudine passare solo una stringa e un argomento di blocco. L'argomento stringa utilizza spesso la parola "dovrebbe" e ha lo scopo di descrivere quale comportamento specifico dovrebbe verificarsi all'interno delit block. In altre parole, descrive che il risultato atteso è per l'esempio.
Notare la it block dal nostro esempio HelloWorld -
it "The say_hello method should return 'Hello World'" do
La stringa rende chiaro cosa dovrebbe accadere quando chiamiamo say hello su un'istanza della classe HelloWorld. Questa parte della filosofia RSpec, un esempio, non è solo un test, è anche una specifica (una specifica). In altre parole, un esempio documenta e verifica il comportamento previsto del codice Ruby.
La parola chiave attesa
Il expectla parola chiave viene utilizzata per definire una "aspettativa" in RSpec. Questo è un passaggio di verifica in cui controlliamo che una specifica condizione prevista sia stata soddisfatta.
Dal nostro esempio HelloWorld, abbiamo:
expect(message).to eql "Hello World!"
L'idea con expectaffermazioni è che si leggono come l'inglese normale. Puoi dirlo ad alta voce come "Aspettati che il messaggio variabile sia uguale alla stringa" Hello World "". L'idea è che sia descrittiva e anche di facile lettura, anche per gli stakeholder non tecnici come i project manager.
The to keyword
Il to la parola chiave è utilizzata come parte di expectdichiarazioni. Nota che puoi anche usare ilnot_toparola chiave per esprimere il contrario, quando si desidera che l'aspettativa sia falsa. Puoi vedere che a è usato con un punto,expect(message).to,perché in realtà è solo un normale metodo Ruby. In effetti, tutte le parole chiave RSpec sono in realtà solo metodi Ruby.
The eql keyword
Il eqlparola chiave è una parola chiave RSpec speciale chiamata Matcher. Utilizzi Matcher per specificare il tipo di condizione che stai testando per essere vera (o falsa).
Nel nostro HelloWorld expect dichiarazione, è chiaro che eqlsignifica uguaglianza delle stringhe. Nota che ci sono diversi tipi di operatori di uguaglianza in Ruby e di conseguenza diversi Matcher corrispondenti in RSpec. Esploreremo i molti diversi tipi di Matcher in una sezione successiva.
In questo capitolo, creeremo una nuova classe Ruby, la salveremo nel suo file e creeremo un file spec separato per testare questa classe.
Innanzitutto, nella nostra nuova classe, si chiama StringAnalyzer. È una classe semplice che, hai indovinato, analizza le stringhe. La nostra classe ha un solo metodohas_vowels?che, come suggerisce il nome, restituisce true se una stringa contiene vocali e false se non lo fa. Ecco l'implementazione perStringAnalyzer -
class StringAnalyzer
def has_vowels?(str)
!!(str =~ /[aeio]+/i)
end
end
Se hai seguito la sezione HelloWorld, hai creato una cartella chiamata C: \ rspec_tutorial \ spec.
Elimina il file hello_world.rb se ce l'hai e salva il codice StringAnalyzer sopra in un file chiamato string_analyzer.rb nella cartella C: \ rspec_tutorial \ spec.
Ecco la fonte del nostro file delle specifiche per testare StringAnalyzer -
require 'string_analyzer'
describe StringAnalyzer do
context "With valid input" do
it "should detect when a string contains vowels" do
sa = StringAnalyzer.new
test_string = 'uuu'
expect(sa.has_vowels? test_string).to be true
end
it "should detect when a string doesn't contain vowels" do
sa = StringAnalyzer.new
test_string = 'bcdfg'
expect(sa.has_vowels? test_string).to be false
end
end
end
Salvalo nella stessa directory delle specifiche, assegnandogli il nome string_analyzer_test.rb.
Nella finestra cmd.exe, accedi alla cartella C: \ rspec_tutorial ed esegui questo comando: dir spec
Dovresti vedere quanto segue:
Directory di C: \ rspec_tutorial \ spec
09/13/2015 08:22 AM <DIR> .
09/13/2015 08:22 AM <DIR> ..
09/12/2015 11:44 PM 81 string_analyzer.rb
09/12/2015 11:46 PM 451 string_analyzer_test.rb
Ora eseguiremo i nostri test, eseguire questo comando: rspec spec
Quando passi il nome di una cartella a rspec, esegue tutti i file delle specifiche all'interno della cartella. Dovresti vedere questo risultato -
No examples found.
Finished in 0 seconds (files took 0.068 seconds to load)
0 examples, 0 failures
Il motivo per cui ciò è accaduto è che, per impostazione predefinita, rspecesegue solo file i cui nomi terminano con "_spec.rb". Rinomina string_analyzer_test.rb in string_analyzer_spec.rb. Puoi farlo facilmente eseguendo questo comando:
ren spec\string_analyzer_test.rb string_analyzer_spec.rb
Adesso corri rspec di nuovo spec, dovresti vedere un output simile a questo -
F.
Failures:
1) StringAnalyzer With valid input should detect when a string contains vowels
Failure/Error: expect(sa.has_vowels? test_string).to be true
expected true
got false
# ./spec/string_analyzer_spec.rb:9:in `block (3 levels) in <top (required)>'
Finished in 0.015 seconds (files took 0.12201 seconds to load)
2 examples, 1 failure
Failed examples:
rspec ./spec/string_analyzer_spec.rb:6 # StringAnalyzer With valid
input should detect when a string contains vowels
Do you see what just happened? Our spec failed because we have a bug in
StringAnalyzer. The bug is simple to fix, open up string_analyzer.rb
in a text editor and change this line:
!!(str =~ /[aeio]+/i)
to this:
!!(str =~ /[aeiou]+/i)
Ora, salva le modifiche appena apportate in string_analyizer.rb ed esegui di nuovo il comando rspec spec, ora dovresti vedere l'output che assomiglia a -
..
Finished in 0.002 seconds (files took 0.11401 seconds to load)
2 examples, 0 failures
Congratulazioni, ora gli esempi (test) nel file delle specifiche vengono superati. Abbiamo corretto un bug nell'espressione regolare che ha il metodo delle vocali ma i nostri test sono lungi dall'essere completi.
Avrebbe senso aggiungere altri esempi che testino vari tipi di stringhe di input con il metodo has vowels.
La tabella seguente mostra alcune delle permutazioni che potrebbero essere aggiunte in nuovi esempi (blocca)
Stringa di input | Descrizione | Risultato previsto con has_vowels? |
---|---|---|
"aaa", "eee", "iii", "o" | Solo una vocale e nessun'altra lettera. | vero |
"abcefg" | 'Almeno una vocale e alcune consonanti' | vero |
"mnklp" | Solo consonanti. | falso |
'' | Stringa vuota (senza lettere) | falso |
"abcde55345 & ??" | Vocali, consonanti, numeri e caratteri di punteggiatura. | vero |
"423432 %%% ^ &" | Solo numeri e caratteri di punteggiatura. | falso |
"AEIOU" | Solo vocali maiuscole. | vero |
"AeiOuuuA" | Solo vocali maiuscole e minuscole. | vero |
"AbCdEfghI" | Vocali e consonanti maiuscole e minuscole. | vero |
"BCDFG" | Solo consonanti maiuscole. | falso |
'' | Solo caratteri di spazio vuoto. | falso |
Sta a te decidere quali esempi aggiungere al file delle specifiche. Esistono molte condizioni da verificare, è necessario determinare quale sottoinsieme di condizioni è più importante e testare al meglio il codice.
Il rspec Il comando offre molte opzioni diverse, per vederle tutte, digita rspec-Aiuto. La tabella seguente elenca le opzioni più popolari e descrive cosa fanno.
Sr.No. | Opzione / bandiera e descrizione |
---|---|
1 | -I PATH Aggiunge PATH al percorso di caricamento (richiesto) that rspec utilizza quando si cercano i file sorgenti di Ruby. |
2 | -r, --require PATH Aggiunge un file di origine specifico da richiedere nelle specifiche. File). |
3 | --fail-fast Con questa opzione, rspec interromperà l'esecuzione delle specifiche dopo che il primo esempio fallisce. Per impostazione predefinita, rspec esegue tutti i file spec specificati, indipendentemente dal numero di errori presenti. |
4 | -f, --format FORMATTER Questa opzione consente di specificare diversi formati di output. Vedere la sezione sui formattatori per maggiori dettagli sui formati di output. |
5 | -o, --out FILE Questa opzione indica a rspec di scrivere i risultati del test nel file di output FILE invece che nell'output standard. |
6 | -c, --color Abilita il colore nell'output di rspec. I risultati di esempio riusciti verranno visualizzati in testo verde, gli errori verranno stampati in testo rosso. |
7 | -b, --backtrace Visualizza tutti i backtrace degli errori nell'output di rspec. |
8 | -w, --warnings Visualizza gli avvisi di Ruby nell'output di rspec. |
9 | -P, --pattern PATTERN Carica ed esegui i file delle specifiche che corrispondono al pattern PATTERN. Ad esempio, se passi -p "* .rb", rspec eseguirà tutti i file Ruby, non solo quelli che terminano con "_spec.rb". |
10 | -e, --example STRING Questa opzione indica a rspec di eseguire tutti gli esempi che contengono il testo STRING nelle loro descrizioni. |
11 | -t, --tag TAG Con questa opzione, rspec eseguirà solo esempi che contengono il tag TAG. Nota che TAG è specificato come un simbolo Ruby. Vedere la sezione sui tag RSpec per maggiori dettagli. |
Se ricordi il nostro esempio originale Hello World, conteneva una riga simile a questa:
expect(message).to eq "Hello World!"
La parola chiave eql è un file RSpec"Matcher". Qui introdurremo gli altri tipi di matcher in RSpec.
Corrispondenze di uguaglianza / identità
Matcher per testare l'uguaglianza di oggetti o valori.
Matcher | Descrizione | Esempio |
---|---|---|
eq | Passa quando effettivo == previsto | aspettarsi (effettivo) .to eq atteso |
eql | Passa quando actual.eql? (Previsto) | aspettarsi (effettivo) .to eql previsto |
essere | Passa quando effettivo.uguale? (Previsto) | aspettarsi (effettivo) |
pari | Passa anche quando effettivo.uguale? (Previsto) | aspettarsi (effettivo) .per eguagliare il previsto |
Esempio
describe "An example of the equality Matchers" do
it "should show how the equality Matchers work" do
a = "test string"
b = a
# The following Expectations will all pass
expect(a).to eq "test string"
expect(a).to eql "test string"
expect(a).to be b
expect(a).to equal b
end
end
Quando il codice precedente viene eseguito, produrrà il seguente output. Il numero di secondi potrebbe essere leggermente diverso sul tuo computer -
.
Finished in 0.036 seconds (files took 0.11901 seconds to load)
1 example, 0 failures
Matcher di confronto
Matcher per il confronto con i valori.
Matcher | Descrizione | Esempio |
---|---|---|
> | Passa quando effettivo> previsto | aspettarsi (effettivo) .per essere> previsto |
> = | Passa quando effettivo> = previsto | aspettarsi che (effettivo) sia> = previsto |
< | Passa quando effettivo <previsto | aspettarsi che (effettivo) sia <previsto |
<= | Passa quando effettivo <= previsto | aspettarsi che (effettivo) sia <= previsto |
essere_tra compreso | Passa quando effettivo è <= min e> = max | aspettarsi che (effettivo) sia_between (min, max) .inclusive |
be_between esclusivo | Passa quando il valore effettivo è <min e> max | aspettarsi che (effettivo) sia_between (min, max) .exclusive |
incontro | Passa quando actual corrisponde a un'espressione regolare | aspettati che (effettivo) corrisponda (/ regex /) |
Esempio
describe "An example of the comparison Matchers" do
it "should show how the comparison Matchers work" do
a = 1
b = 2
c = 3
d = 'test string'
# The following Expectations will all pass
expect(b).to be > a
expect(a).to be >= a
expect(a).to be < b
expect(b).to be <= b
expect(c).to be_between(1,3).inclusive
expect(b).to be_between(1,3).exclusive
expect(d).to match /TEST/i
end
end
Quando il codice precedente viene eseguito, produrrà il seguente output. Il numero di secondi potrebbe essere leggermente diverso sul tuo computer -
.
Finished in 0.013 seconds (files took 0.11801 seconds to load)
1 example, 0 failures
Corrisponditori di classe / tipo
Matcher per testare il tipo o la classe degli oggetti.
Matcher | Descrizione | Esempio |
---|---|---|
be_instance_of | Passa quando actual è un'istanza della classe prevista. | aspettarsi (effettivo) .essere_istanza_di (atteso) |
be_kind_of | Passa quando actual è un'istanza della classe prevista o di una delle sue classi padre. | aspettarsi (effettivo) .essere_kind_of (previsto) |
rispondere a | Passa quando effettivo risponde al metodo specificato. | aspettarsi (effettivo) .per rispondere_to (atteso) |
Esempio
describe "An example of the type/class Matchers" do
it "should show how the type/class Matchers work" do
x = 1
y = 3.14
z = 'test string'
# The following Expectations will all pass
expect(x).to be_instance_of Fixnum
expect(y).to be_kind_of Numeric
expect(z).to respond_to(:length)
end
end
Quando il codice precedente viene eseguito, produrrà il seguente output. Il numero di secondi potrebbe essere leggermente diverso sul tuo computer -
.
Finished in 0.002 seconds (files took 0.12201 seconds to load)
1 example, 0 failures
Corrispondenze vero / falso / zero
Matcher per verificare se un valore è vero, falso o nullo.
Matcher | Descrizione | Esempio |
---|---|---|
essere vero | Passa quando effettivo == vero | aspettarsi che (effettivo) sia vero |
essere falso | Passa quando effettivo == falso | aspettarsi che (effettivo). sia falso |
be_truthy | Passa quando il valore effettivo non è falso o nullo | aspettarsi (effettivo) .essere_truthy |
be_falsey | Passa quando il valore effettivo è falso o nullo | aspettarsi (effettivo) .to be_falsey |
be_nil | Passa quando l'attuale è nullo | aspettarsi (effettivo) .to be_nil |
Esempio
describe "An example of the true/false/nil Matchers" do
it "should show how the true/false/nil Matchers work" do
x = true
y = false
z = nil
a = "test string"
# The following Expectations will all pass
expect(x).to be true
expect(y).to be false
expect(a).to be_truthy
expect(z).to be_falsey
expect(z).to be_nil
end
end
Quando il codice precedente viene eseguito, produrrà il seguente output. Il numero di secondi potrebbe essere leggermente diverso sul tuo computer -
.
Finished in 0.003 seconds (files took 0.12301 seconds to load)
1 example, 0 failures
Corrispondenze di errore
Matcher per il test, quando un blocco di codice genera un errore.
Matcher | Descrizione | Esempio |
---|---|---|
raise_error (ErrorClass) | Passa quando il blocco genera un errore di tipo ErrorClass. | aspettati {block} .to raise_error (ErrorClass) |
raise_error ("messaggio di errore") | Passa quando il blocco genera un errore con il messaggio "messaggio di errore". | aspettati {block} .to raise_error ("messaggio di errore") |
raise_error (ErrorClass, "messaggio di errore") | Passa quando il blocco genera un errore di tipo ErrorClass con il messaggio "messaggio di errore" | aspettati {block} .to raise_error (ErrorClass, "messaggio di errore") |
Esempio
Salva il codice seguente in un file con il nome error_matcher_spec.rb ed eseguilo con questo comando - rspec error_matcher_spec.rb.
describe "An example of the error Matchers" do
it "should show how the error Matchers work" do
# The following Expectations will all pass
expect { 1/0 }.to raise_error(ZeroDivisionError)
expect { 1/0 }.to raise_error("divided by 0")
expect { 1/0 }.to raise_error("divided by 0", ZeroDivisionError)
end
end
Quando il codice precedente viene eseguito, produrrà il seguente output. Il numero di secondi potrebbe essere leggermente diverso sul tuo computer -
.
Finished in 0.002 seconds (files took 0.12101 seconds to load)
1 example, 0 failures
In questo capitolo, discuteremo RSpec Double, noto anche come RSpec Mocks. Un Double è un oggetto che può "sostituire" un altro oggetto. Probabilmente ti starai chiedendo cosa significhi esattamente e perché ne avresti bisogno.
Supponiamo che tu stia creando un'applicazione per una scuola e che tu abbia una classe che rappresenta un'aula di studenti e un'altra classe per gli studenti, ovvero una classe in Aula e una classe Studente. Devi prima scrivere il codice per una delle classi, quindi diciamo che, inizia con la classe Classroom -
class ClassRoom
def initialize(students)
@students = students
end
def list_student_names
@students.map(&:name).join(',')
end
end
Questa è una classe semplice, ha un metodo list_student_names, che restituisce una stringa delimitata da virgole di nomi di studenti. Ora, vogliamo creare test per questa classe, ma come possiamo farlo se non abbiamo ancora creato la classe Studente? Abbiamo bisogno di un doppio di prova.
Inoltre, se abbiamo una classe "fittizia" che si comporta come un oggetto Student, i nostri test ClassRoom non dipenderanno dalla classe Student. Chiamiamo questo isolamento di prova.
Se i nostri test ClassRoom non si basano su altre classi, allora quando un test fallisce, possiamo sapere immediatamente che c'è un bug nella nostra classe ClassRoom e non in qualche altra classe. Tieni presente che, nel mondo reale, potresti creare un corso che deve interagire con un altro corso scritto da qualcun altro.
È qui che RSpec Double (mock) diventa utile. Il nostro metodo list_student_names chiama il metodo name su ogni oggetto Student nella sua variabile membro @students. Pertanto, abbiamo bisogno di un Double che implementa un metodo di nome.
Ecco il codice per ClassRoom insieme a un esempio RSpec (test), ma si noti che non è stata definita alcuna classe Student -
class ClassRoom
def initialize(students)
@students = students
end
def list_student_names
@students.map(&:name).join(',')
end
end
describe ClassRoom do
it 'the list_student_names method should work correctly' do
student1 = double('student')
student2 = double('student')
allow(student1).to receive(:name) { 'John Smith'}
allow(student2).to receive(:name) { 'Jill Smith'}
cr = ClassRoom.new [student1,student2]
expect(cr.list_student_names).to eq('John Smith,Jill Smith')
end
end
Quando il codice precedente viene eseguito, produrrà il seguente output. Il tempo trascorso potrebbe essere leggermente diverso sul tuo computer -
.
Finished in 0.01 seconds (files took 0.11201 seconds to load)
1 example, 0 failures
Come puoi vedere, utilizzando un file test doubleti permette di testare il tuo codice anche quando si basa su una classe che non è definita o non disponibile. Inoltre, questo significa che quando si verifica un fallimento del test, puoi subito dire che è a causa di un problema nella tua classe e non di una classe scritta da qualcun altro.
Se hai già letto la sezione su RSpec Double (aka Mocks), allora hai già visto RSpec Stubs. In RSpec, uno stub è spesso chiamato Method Stub, è un tipo speciale di metodo che "sostituisce" un metodo esistente o un metodo che non esiste ancora.
Ecco il codice dalla sezione su RSpec Doubles -
class ClassRoom
def initialize(students)
@students = students
End
def list_student_names
@students.map(&:name).join(',')
end
end
describe ClassRoom do
it 'the list_student_names method should work correctly' do
student1 = double('student')
student2 = double('student')
allow(student1).to receive(:name) { 'John Smith'}
allow(student2).to receive(:name) { 'Jill Smith'}
cr = ClassRoom.new [student1,student2]
expect(cr.list_student_names).to eq('John Smith,Jill Smith')
end
end
Nel nostro esempio, il metodo allow () fornisce gli stub del metodo di cui abbiamo bisogno per testare la classe ClassRoom. In questo caso, abbiamo bisogno di un oggetto che agisca proprio come un'istanza della classe Student, ma quella classe non esiste (ancora). Sappiamo che la classe Student deve fornire un metodo name () e usiamo allow () per creare uno stub del metodo per name ().
Una cosa da notare è che la sintassi di RSpec è cambiata leggermente nel corso degli anni. Nelle versioni precedenti di RSpec, gli stub del metodo sopra sarebbero stati definiti in questo modo:
student1.stub(:name).and_return('John Smith')
student2.stub(:name).and_return('Jill Smith')
Prendiamo il codice sopra e sostituiamo i due allow() righe con la vecchia sintassi RSpec -
class ClassRoom
def initialize(students)
@students = students
end
def list_student_names
@students.map(&:name).join(',')
end
end
describe ClassRoom do
it 'the list_student_names method should work correctly' do
student1 = double('student')
student2 = double('student')
student1.stub(:name).and_return('John Smith')
student2.stub(:name).and_return('Jill Smith')
cr = ClassRoom.new [student1,student2]
expect(cr.list_student_names).to eq('John Smith,Jill Smith')
end
end
Vedrai questo output quando esegui il codice sopra -
.
Deprecation Warnings:
Using `stub` from rspec-mocks' old `:should` syntax without explicitly
enabling the syntax is deprec
ated. Use the new `:expect` syntax or explicitly enable `:should` instead.
Called from C:/rspec_tuto
rial/spec/double_spec.rb:15:in `block (2 levels) in <top (required)>'.
If you need more of the backtrace for any of these deprecations
to identify where to make the necessary changes, you can configure
`config.raise_errors_for_deprecations!`, and it will turn the
deprecation warnings into errors, giving you the full backtrace.
1 deprecation warning total
Finished in 0.002 seconds (files took 0.11401 seconds to load)
1 example, 0 failures
Si consiglia di utilizzare la nuova sintassi allow () quando è necessario creare stub di metodo negli esempi di RSpec, ma qui abbiamo fornito lo stile precedente in modo che lo riconoscerai se lo vedi.
Quando si scrivono unit test, è spesso conveniente eseguire il codice di installazione e smontaggio prima e dopo i test. Il codice di installazione è il codice che configura o "imposta" le condizioni per un test. Il codice di smontaggio esegue la pulizia, si assicura che l'ambiente sia in uno stato coerente per i test successivi.
In generale, i tuoi test dovrebbero essere indipendenti l'uno dall'altro. Quando esegui un'intera suite di test e uno di essi fallisce, vuoi avere la certezza che non sia riuscito perché il codice che sta testando ha un bug, non perché il test precedente ha lasciato l'ambiente in uno stato incoerente.
Gli hook più comuni utilizzati in RSpec sono prima e dopo gli hook. Forniscono un modo per definire ed eseguire il codice di installazione e smontaggio di cui abbiamo discusso in precedenza. Consideriamo questo codice di esempio:
class SimpleClass
attr_accessor :message
def initialize()
puts "\nCreating a new instance of the SimpleClass class"
@message = 'howdy'
end
def update_message(new_message)
@message = new_message
end
end
describe SimpleClass do
before(:each) do
@simple_class = SimpleClass.new
end
it 'should have an initial message' do
expect(@simple_class).to_not be_nil
@simple_class.message = 'Something else. . .'
end
it 'should be able to change its message' do
@simple_class.update_message('a new message')
expect(@simple_class.message).to_not be 'howdy'
end
end
Quando esegui questo codice, otterrai il seguente output:
Creating a new instance of the SimpleClass class
.
Creating a new instance of the SimpleClass class
.
Finished in 0.003 seconds (files took 0.11401 seconds to load)
2 examples, 0 failures
Diamo uno sguardo più da vicino a cosa sta succedendo. Il metodo before (: each) è dove definiamo il codice di configurazione. Quando passi l'argomento: each, stai istruendo il metodo before da eseguire prima di ogni esempio nel tuo gruppo di esempi, cioè i due blocchi all'interno del blocco di descrizione nel codice sopra.
Nella riga: @simple_class = SimpleClass.new, stiamo creando una nuova istanza della classe SimpleClass e assegnandola a una variabile di istanza di un oggetto. Quale oggetto ti starai chiedendo? RSpec crea una classe speciale dietro le quinte nell'ambito del blocco di descrizione. Ciò consente di assegnare valori alle variabili di istanza di questa classe, a cui è possibile accedere all'interno dei blocchi it negli Esempi. Questo semplifica anche la scrittura di codice più pulito nei nostri test. Se ogni test (Esempio) necessita di un'istanza di SimpleClass, possiamo mettere quel codice nell'hook precedente e non doverlo aggiungere a ogni esempio.
Si noti che, la riga "Creazione di una nuova istanza della classe SimpleClass" viene scritta due volte sulla console, questo mostra che, prima che hook fosse chiamato in ciascuna delle it blocks.
Come abbiamo accennato, RSpec ha anche un hook after e entrambi gli hook prima e dopo possono prendere: all come argomento. L'hook after verrà eseguito dopo il target specificato. Il target: all significa che l'hook verrà eseguito prima / dopo tutti gli esempi. Ecco un semplice esempio che illustra quando viene chiamato ogni hook.
describe "Before and after hooks" do
before(:each) do
puts "Runs before each Example"
end
after(:each) do
puts "Runs after each Example"
end
before(:all) do
puts "Runs before all Examples"
end
after(:all) do
puts "Runs after all Examples"
end
it 'is the first Example in this spec file' do
puts 'Running the first Example'
end
it 'is the second Example in this spec file' do
puts 'Running the second Example'
end
end
Quando esegui il codice sopra, vedrai questo output:
Runs before all Examples
Runs before each Example
Running the first Example
Runs after each Example
.Runs before each Example
Running the second Example
Runs after each Example
.Runs after all Examples
I tag RSpec forniscono un modo semplice per eseguire test specifici nei file delle specifiche. Per impostazione predefinita, RSpec eseguirà tutti i test nei file spec che esegue, ma potrebbe essere necessario eseguire solo un sottoinsieme di essi. Supponiamo che tu abbia alcuni test che vengono eseguiti molto rapidamente e che hai appena apportato una modifica al codice dell'applicazione e desideri eseguire solo i test rapidi, questo codice mostrerà come farlo con i tag RSpec.
describe "How to run specific Examples with Tags" do
it 'is a slow test', :slow = > true do
sleep 10
puts 'This test is slow!'
end
it 'is a fast test', :fast = > true do
puts 'This test is fast!'
end
end
Ora salva il codice sopra in un nuovo file chiamato tag_spec.rb. Dalla riga di comando, esegui questo comando: rspec --tag slow tag_spec.rb
Vedrai questo output -
Opzioni di esecuzione: include {: slow => true}
This test is slow!
.
Finished in 10 seconds (files took 0.11601 seconds to load)
1 example, 0 failures
Quindi, esegui questo comando: rspec --tag fast tag_spec.rb
Vedrai questo output -
Run options: include {:fast = >true}
This test is fast!
.
Finished in 0.001 seconds (files took 0.11201 seconds to load)
1 example, 0 failures
Come puoi vedere, RSpec Tags rende molto facile un sottoinsieme di test!
Uno dei punti di forza di RSpec è che fornisce molti modi per scrivere test, test puliti. Quando i tuoi test sono brevi e ordinati, diventa più facile concentrarsi sul comportamento previsto e non sui dettagli di come sono scritti i test. Gli argomenti RSpec sono un'altra scorciatoia che ti consente di scrivere test semplici e diretti.
Considera questo codice:
class Person
attr_reader :first_name, :last_name
def initialize(first_name, last_name)
@first_name = first_name
@last_name = last_name
end
end
describe Person do
it 'create a new person with a first and last name' do
person = Person.new 'John', 'Smith'
expect(person).to have_attributes(first_name: 'John')
expect(person).to have_attributes(last_name: 'Smith')
end
end
In realtà è abbastanza chiaro così com'è, ma potremmo usare la funzionalità oggetto di RSpec per ridurre la quantità di codice nell'esempio. Lo facciamo spostando l'istanza dell'oggetto persona nella riga di descrizione.
class Person
attr_reader :first_name, :last_name
def initialize(first_name, last_name)
@first_name = first_name
@last_name = last_name
end
end
describe Person.new 'John', 'Smith' do
it { is_expected.to have_attributes(first_name: 'John') }
it { is_expected.to have_attributes(last_name: 'Smith') }
end
Quando esegui questo codice, vedrai questo output:
..
Finished in 0.003 seconds (files took 0.11201 seconds to load)
2 examples, 0 failures
Nota quanto è più semplice il secondo esempio di codice. Abbiamo preso quelloit block nel primo esempio e lo ha sostituito con due it blocks che finiscono per richiedere meno codice e sono altrettanto chiari.
A volte i tuoi esempi RSpec necessitano di un modo semplice per condividere codice riutilizzabile. Il modo migliore per farlo è con gli helper. Gli helper sono fondamentalmente normali metodi Ruby che condividi attraverso esempi. Per illustrare il vantaggio dell'utilizzo di helper, consideriamo questo codice:
class Dog
attr_reader :good_dog, :has_been_walked
def initialize(good_or_not)
@good_dog = good_or_not
@has_been_walked = false
end
def walk_dog
@has_been_walked = true
end
end
describe Dog do
it 'should be able to create and walk a good dog' do
dog = Dog.new(true)
dog.walk_dog
expect(dog.good_dog).to be true
expect(dog.has_been_walked).to be true
end
it 'should be able to create and walk a bad dog' do
dog = Dog.new(false)
dog.walk_dog
expect(dog.good_dog).to be false
expect(dog.has_been_walked).to be true
end
end
Questo codice è chiaro, ma è sempre una buona idea ridurre il codice ripetuto quando possibile. Possiamo prendere il codice sopra e ridurre alcune di queste ripetizioni con un metodo di supporto chiamato create_and_walk_dog ().
class Dog
attr_reader :good_dog, :has_been_walked
def initialize(good_or_not)
@good_dog = good_or_not
@has_been_walked = false
end
def walk_dog
@has_been_walked = true
end
end
describe Dog do
def create_and_walk_dog(good_or_bad)
dog = Dog.new(good_or_bad)
dog.walk_dog
return dog
end
it 'should be able to create and walk a good dog' do
dog = create_and_walk_dog(true)
expect(dog.good_dog).to be true
expect(dog.has_been_walked).to be true
end
it 'should be able to create and walk a bad dog' do
dog = create_and_walk_dog(false)
expect(dog.good_dog).to be false
expect(dog.has_been_walked).to be true
end
end
Quando esegui il codice sopra, vedrai questo output:
..
Finished in 0.002 seconds (files took 0.11401 seconds to load)
2 examples, 0 failures
Come puoi vedere, siamo stati in grado di inserire la logica per creare e portare a spasso un oggetto per cani in un aiutante che consente ai nostri esempi di essere più brevi e puliti.
RSpec è uno strumento flessibile e potente. La funzionalità dei metadati in RSpec non fa eccezione. I metadati si riferiscono generalmente a "dati sui dati". In RSpec, questo significa che i dati sul tuodescribe, context e it blocks.
Diamo un'occhiata a un esempio:
RSpec.describe "An Example Group with a metadata variable", :foo => 17 do
context 'and a context with another variable', :bar => 12 do
it 'can access the metadata variable of the outer Example Group' do |example|
expect(example.metadata[:foo]).to eq(17)
end
it 'can access the metadata variable in the context block' do |example|
expect(example.metadata[:bar]).to eq(12)
end
end
end
Quando esegui il codice sopra, vedrai questo output:
..
Finished in 0.002 seconds (files took 0.11301 seconds to load)
2 examples, 0 failures
I metadati forniscono un modo per assegnare variabili in vari ambiti all'interno dei file RSpec. La variabile example.metadata è un hash Ruby che contiene altre informazioni sui tuoi gruppi di esempi e di esempi.
Ad esempio, riscriviamo il codice sopra in questo modo:
RSpec.describe "An Example Group with a metadata variable", :foo => 17 do
context 'and a context with another variable', :bar => 12 do
it 'can access the metadata variable in the context block' do |example|
expect(example.metadata[:foo]).to eq(17)
expect(example.metadata[:bar]).to eq(12)
example.metadata.each do |k,v|
puts "#{k}: #{v}"
end
end
end
Quando eseguiamo questo codice, vediamo tutti i valori nell'hash example.metadata -
.execution_result: #<RSpec::Core::Example::ExecutionResult:0x00000002befd50>
block: #<Proc:0x00000002bf81a8@C:/rspec_tutorial/spec/metadata_spec.rb:7>
description_args: ["can access the metadata variable in the context block"]
description: can access the metadata variable in the context block
full_description: An Example Group with a metadata variable and a context
with another variable can access the metadata variable in the context block
described_class:
file_path: ./metadata_spec.rb
line_number: 7
location: ./metadata_spec.rb:7
absolute_file_path: C:/rspec_tutorial/spec/metadata_spec.rb
rerun_file_path: ./metadata_spec.rb
scoped_id: 1:1:2
foo: 17
bar: 12
example_group:
{:execution_result=>#<RSpec::Core::Example::ExecutionResult:
0x00000002bfa0e8>, :block=>#<
Proc:0x00000002bfac00@C:/rspec_tutorial/spec/metadata_spec.rb:2>,
:description_args=>["and a context with another variable"],
:description=>"and a context with another variable",
:full_description=>"An Example Group with a metadata variable
and a context with another variable", :described_class=>nil,
:file_path=>"./metadata_spec.rb",
:line_number=>2, :location=>"./metadata_spec.rb:2",
:absolute_file_path=>"C:/rspec_tutorial/spec/metadata_spec.rb",
:rerun_file_path=>"./metadata_spec.rb",
:scoped_id=>"1:1", :foo=>17, :parent_example_group=>
{:execution_result=>#<
RSpec::Core::Example::ExecutionResult:0x00000002c1f690>,
:block=>#<Proc:0x00000002baff70@C:/rspec_tutorial/spec/metadata_spec.rb:1>
, :description_args=>["An Example Group with a metadata variable"],
:description=>"An Example Group with a metadata variable",
:full_description=>"An Example Group with a metadata variable",
:described_class=>nil, :file_path=>"./metadata_spec.rb",
:line_number=>1, :location=>"./metadata_spec.rb:1",
:absolute_file_path=>
"C:/rspec_tutorial/spec/metadata_spec.rb",
:rerun_file_path=>"./metadata_spec.rb",
:scoped_id=>"1", :foo=>17},
:bar=>12}shared_group_inclusion_backtrace: []
last_run_status: unknown .
.
Finished in 0.004 seconds (files took 0.11101 seconds to load)
2 examples, 0 failures
Molto probabilmente, non sarà necessario utilizzare tutti questi metadati, ma guarda il valore della descrizione completa:
Un gruppo di esempio con una variabile di metadati e un contesto con un'altra variabile possono accedere alla variabile di metadati nel blocco di contesto.
Questa è una frase creata dalla descrizione del blocco di descrizione + la descrizione del blocco di contesto contenuto + la descrizione per il it block.
Ciò che è interessante notare qui è che queste tre stringhe insieme si leggono come una normale frase inglese. . . che è una delle idee alla base di RSpec, con test che suonano come descrizioni del comportamento in inglese.
Si consiglia di leggere la sezione sui metadati RSpec prima di leggere questa sezione perché, a quanto pare, il filtro RSpec si basa sui metadati RSpec.
Immagina di avere un file spec e che contenga due tipi di test (esempi): test funzionali positivi e test negativi (errore). Definiamoli in questo modo -
RSpec.describe "An Example Group with positive and negative Examples" do
context 'when testing Ruby\'s build-in math library' do
it 'can do normal numeric operations' do
expect(1 + 1).to eq(2)
end
it 'generates an error when expected' do
expect{1/0}.to raise_error(ZeroDivisionError)
end
end
end
Ora salva il testo sopra come file chiamato 'filter_spec.rb' e poi eseguilo con questo comando -
rspec filter_spec.rb
Vedrai un output simile a questo -
..
Finished in 0.003 seconds (files took 0.11201 seconds to load)
2 examples, 0 failures
E se volessimo rieseguire solo i test positivi in questo file? O solo i test negativi? Possiamo farlo facilmente con RSpec Filters. Cambia il codice sopra in questo -
RSpec.describe "An Example Group with positive and negative Examples" do
context 'when testing Ruby\'s build-in math library' do
it 'can do normal numeric operations', positive: true do
expect(1 + 1).to eq(2)
end
it 'generates an error when expected', negative: true do
expect{1/0}.to raise_error(ZeroDivisionError)
end
end
end
Salva le modifiche in filter_spec.rb ed esegui questo comando leggermente diverso -
rspec --tag positive filter_spec.rb
Ora vedrai un output simile a questo:
Run options: include {:positive=>true}
.
Finished in 0.001 seconds (files took 0.11401 seconds to load)
1 example, 0 failures
Specificando --tag positive, stiamo dicendo a RSpec di eseguire solo esempi con la variabile di metadati: positive definita. Potremmo fare la stessa cosa con i test negativi eseguendo il comando in questo modo:
rspec --tag negative filter_spec.rb
Tieni presente che questi sono solo esempi, puoi specificare un filtro con qualsiasi nome desideri.
Formattatori RSpec
I formattatori consentono a RSpec di visualizzare l'output dei test in modi diversi. Creiamo un nuovo file RSpec contenente questo codice -
RSpec.describe "A spec file to demonstrate how RSpec Formatters work" do
context 'when running some tests' do
it 'the test usually calls the expect() method at least once' do
expect(1 + 1).to eq(2)
end
end
end
Ora salvalo in un file chiamato formatter_spec.rb ed esegui questo comando RSpec -
rspec formatter_spec.rb
Dovresti vedere un output simile a questo -
.
Finished in 0.002 seconds (files took 0.11401 seconds to load)
1 example, 0 failures
Ora esegui lo stesso comando ma questa volta specifica un formattatore, come questo -
rspec --format progress formatter_spec.rb
Questa volta dovresti vedere lo stesso output -
.
Finished in 0.002 seconds (files took 0.11401 seconds to load)
1 example, 0 failures
Il motivo è che il formattatore "progress" è il formattatore predefinito. Proviamo un altro formattatore successivo, prova a eseguire questo comando -
rspec --format doc formatter_spec.rb
Ora dovresti vedere questo output -
A spec file to demonstrate how RSpec Formatters work
when running some tests
the test usually calls the expect() method at least once
Finished in 0.002 seconds (files took 0.11401 seconds to load)
1 example, 0 failures
Come puoi vedere, l'output è abbastanza diverso con il formattatore "doc". Questo formattatore presenta l'output in uno stile simile alla documentazione. Forse ti starai chiedendo come appaiono queste opzioni quando si verifica un errore in un test (Esempio). Cambiamo il codice informatter_spec.rb per assomigliare a questo -
RSpec.describe "A spec file to demonstrate how RSpec Formatters work" do
context 'when running some tests' do
it 'the test usually calls the expect() method at least once' do
expect(1 + 1).to eq(1)
end
end
end
L'aspettativa expect(1 + 1).to eq(1)dovrebbe fallire. Salva le modifiche e riesegui i comandi precedenti -
rspec --format progress formatter_spec.rb e ricorda, poiché il formattatore "progress" è quello predefinito, potresti semplicemente eseguire: rspec formatter_spec.rb. Dovresti vedere questo output -
F
Failures:
1) A spec file to demonstrate how RSpec Formatters work when running some tests
the test usually calls the expect() method at least once
Failure/Error: expect(1 + 1).to eq(1)
expected: 1
got: 2
(compared using ==)
# ./formatter_spec.rb:4:in `block (3 levels) in <top (required)>'
Finished in 0.016 seconds (files took 0.11201 seconds to load)
1 example, 1 failure
Failed examples:
rspec ./formatter_spec.rb:3 # A spec file to demonstrate how RSpec
Formatters work when running some tests the test usually calls
the expect() method at least once
Ora, proviamo il formattatore di documenti, esegui questo comando -
rspec --format doc formatter_spec.rb
Ora, con il test fallito, dovresti vedere questo output:
A spec file to demonstrate how RSpec Formatters work
when running some tests
the test usually calls the expect() method at least once (FAILED - 1)
Failures:
1) A spec file to demonstrate how RSpec Formatters work when running some
tests the test usually calls the expect() method at least once
Failure/Error: expect(1 + 1).to eq(1)
expected: 1
got: 2
(compared using ==)
# ./formatter_spec.rb:4:in `block (3 levels) in <top (required)>'
Finished in 0.015 seconds (files took 0.11401 seconds to load)
1 example, 1 failure
Esempi falliti
rspec ./formatter_spec.rb:3 # Un file spec per dimostrare come funzionano RSpec Formatters durante l'esecuzione di alcuni test, il test di solito chiama il metodo wait () almeno una volta.
RSpec Formatters offre la possibilità di modificare il modo in cui vengono visualizzati i risultati dei test, è anche possibile creare il proprio formattatore personalizzato, ma questo è un argomento più avanzato.
Quando impari RSpec, potresti leggere molto sulle aspettative e all'inizio può creare un po 'di confusione. Ci sono due dettagli principali che dovresti tenere a mente quando vedi il termine Aspettativa:
Un'aspettativa è semplicemente una dichiarazione in un file it block che utilizza l'estensione expect()metodo. Questo è tutto. Non è più complicato di così. Quando hai un codice come questo:expect(1 + 1).to eq(2), hai un'aspettativa nel tuo esempio. Ti aspetti che l'espressione1 + 1 valuta a 2. La formulazione è tuttavia importante poiché RSpec è un framework di test BDD. Chiamando questa dichiarazione un'aspettativa, è chiaro che il codice RSpec descrive il "comportamento" del codice che sta testando. L'idea è che tu esprima come dovrebbe comportarsi il codice, in un modo che si legge come la documentazione.
La sintassi delle aspettative è relativamente nuova. Prima diexpect() è stato introdotto (nel 2012), RSpec ha utilizzato una sintassi diversa basata su should()metodo. L'aspettativa di cui sopra è scritta in questo modo nella vecchia sintassi:(1 + 1).should eq(2).
È possibile incontrare la vecchia sintassi RSpec per le aspettative quando si lavora con un codice precedente o una versione precedente di RSpec. Se utilizzi la vecchia sintassi con una nuova versione di RSpec, vedrai un avviso.
Ad esempio, con questo codice -
RSpec.describe "An RSpec file that uses the old syntax" do
it 'you should see a warning when you run this Example' do
(1 + 1).should eq(2)
end
end
Quando lo esegui, otterrai un output simile a questo -
. Deprecation Warnings:
Using `should` from rspec-expectations' old `:should`
syntax without explicitly enabling the syntax is deprecated.
Use the new `:expect` syntax or explicitly enable
`:should` with `config.expect_with( :rspec) { |c| c.syntax = :should }`
instead. Called from C:/rspec_tutorial/spec/old_expectation.rb:3 :in
`block (2 levels) in <top (required)>'.
If you need more of the backtrace for any of these deprecations to
identify where to make the necessary changes, you can configure
`config.raise_errors_for_deprecations!`, and it will turn the deprecation
warnings into errors, giving you the full backtrace.
1 deprecation warning total
Finished in 0.001 seconds (files took 0.11201 seconds to load)
1 example, 0 failures
A meno che non sia necessario utilizzare la vecchia sintassi, si consiglia vivamente di utilizzare wait () invece di should ().