Dovrei + FactoryGirl:Posso rendere i miei test più veloci?
-
13-09-2019 - |
Domanda
Sto cercando un modo per velocizzare il mio Avrei dovuto + Operaia test.
Il modello che sto cercando di testare (StudentExam
) ha associazioni con altri modelli.Questi oggetti associati devono esistere prima di poter creare un file StudentExam
.Per questo motivo vengono creati in setup
.
Tuttavia, uno dei nostri modelli (School
) richiede molto tempo per essere creato.Perché setup
viene chiamato prima di ogni should
istruzione, l'intero test case richiede eoni per essere eseguito: crea un nuovo @school
, @student
, @topic
E @exam
per ogni istruzione dovrebbe essere eseguita.
Sto cercando un modo per creare questi oggetti una volta e solo una volta.C'è qualcosa come a startup
per before_all
metodo che mi consentirebbe di creare record che persisteranno per tutto il resto del test case?
Fondamentalmente sto cercando qualcosa esattamente come quello di RSpec prima(:tutto).Non sono preoccupato per il problema delle dipendenze poiché questi test non modificheranno mai quegli oggetti costosi.
Ecco un esempio di caso di test.Ci scusiamo per il codice lungo (ho anche creato un file sostanza):
# A StudentExam represents an Exam taken by a Student.
# It records the start/stop time, room number, etc.
class StudentExamTest < ActiveSupport::TestCase
should_belong_to :student
should_belong_to :exam
setup do
# These objects need to be created before we can create a StudentExam. Tests will NOT modify these objects.
# @school is a very time-expensive model to create (associations, external API calls, etc).
# We need a way to create the @school *ONCE* -- there's no need to recreate it for every single test.
@school = Factory(:school)
@student = Factory(:student, :school => @school)
@topic = Factory(:topic, :school => @school)
@exam = Factory(:exam, :topic => @topic)
end
context "A StudentExam" do
setup do
@student_exam = Factory(:student_exam, :exam => @exam, :student => @student, :room_number => "WB 302")
end
should "take place at 'Some School'" do
assert_equal @student_exam, 'Some School'
end
should "be in_progress? when created" do
assert @student_exam.in_progress?
end
should "not be in_progress? when finish! is called" do
@student_exam.finish!
assert !@student_exam.in_progress
end
end
end
Soluzione
Se il problema riguarda la creazione di questi record solo una volta, è possibile utilizzare una variabile di classe.Non è un approccio pulito ma almeno dovrebbe funzionare.
# A StudentExam represents an Exam taken by a Student.
# It records the start/stop time, room number, etc.
class StudentExamTest < ActiveSupport::TestCase
should_belong_to :student
should_belong_to :exam
# These objects need to be created before we can create a StudentExam. Tests will NOT modify these objects.
# @school is a very time-expensive model to create (associations, external API calls, etc).
# We need a way to create the @school *ONCE* -- there's no need to recreate it for every single test.
@@school = Factory(:school)
@@student = Factory(:student, :school => @@school)
@@topic = Factory(:topic, :school => @@school)
@@exam = Factory(:exam, :topic => @@topic)
context "A StudentExam" do
setup do
@student_exam = Factory(:student_exam, :exam => @@exam, :student => @@student, :room_number => "WB 302")
end
should "take place at 'Some School'" do
assert_equal @student_exam, 'Some School'
end
should "be in_progress? when created" do
assert @student_exam.in_progress?
end
should "not be in_progress? when finish! is called" do
@@student_exam.finish!
assert !@student_exam.in_progress
end
end
end
MODIFICARE:Per risolvere la soluzione estremamente brutta, rinviare la valutazione con un metodo di istanza.
# A StudentExam represents an Exam taken by a Student.
# It records the start/stop time, room number, etc.
class StudentExamTest < ActiveSupport::TestCase
...
private
def school
@@school ||= Factory(:school)
end
# use school instead of @@school
def student
@@school ||= Factory(:student, :school => school)
end
end
Altri suggerimenti
Che tipo di test stai cercando di scrivere?Se vuoi davvero assicurarti che tutti questi oggetti siano coordinati in modo appropriato, stai scrivendo un test di integrazione e la velocità non è la tua preoccupazione principale.Tuttavia, se stai provando a testare l'unità del modello, potresti ottenere risultati migliori effettuando uno stubbing aggressivo.
Ad esempio, se stai cercando di verificare che un esame utilizzi il nome della sua associazione scolastica quando chiami exam.location (o come lo chiami), non hai bisogno di un intero oggetto scolastico.Devi solo assicurarti che l'esame chiami il metodo giusto a scuola.Per testarlo, potresti fare qualcosa di simile (usando Test::Unit e Mocha perché è quello con cui ho familiarità):
test "exam gets location from school name" do
school = stub_everything
school.expects(:name).returns(:a_school_name)
exam = Factory(:exam, :school => school)
assert_equal :a_school_name, exam.location
end
Fondamentalmente, se hai bisogno di accelerare i test unitari perché gli oggetti sono troppo costosi da costruire, non stai effettivamente testando le unità.Tutti i casi di test sopra riportati sembrano dovrebbero essere a livello di test unitario, quindi stub stub!
http://m.onkey.org/2009/9/20/make-your-shoulda-tests-faster-with-fast_context è un post eccellente su come rendere più veloci i tuoi test Shoulda/factory-girl, utilizzando un gioiello chiamato fast_context.Fammi sapere se non è quello che ti serve.
C'è un plugin chiamato contesto_veloce (collegamento github) che unisce le istruzioni dovrebbero in un unico contesto, velocizzando i test.
L'altra cosa che utilizzo per velocizzare i miei test è precompilare i dati delle partite.FactoryGirl è lenta perché crea tali record ogni volta che viene eseguito il blocco di installazione.
Ho scritto un plugin chiamato Fixie che utilizza ActiveRecord per precompilare il database dei test, quindi i record necessari per i test sono già creati.Puoi utilizzare Fixie insieme a FactoryGirl se desideri creare nuovi record anche in fase di esecuzione.