Comment puis-je obtenir Factory Girl de ne jamais toucher la base de données si j'appelle Factory.construire afin de rendre mes tests de contrôleur rapides?
Je suis en quête de rendre mes tests Rails plus rapides. Je n'ai que 520 tests, mais ils prennent 62 secondes pour courir en bash, et 82 secondes pour courir en Rubymine.
Comme exemple d'un test de contrôleur typique, j'utilisais ce code pour sign_in en tant que @ user et créer le @comment de base dans un CommentsController pour mes tests de contrôleur RSpec:
before(:each) do
@user = Factory.create(:user)
sign_in @user
@comment = Factory.create(:comment)
end
Comme vous le savez peut-être... c'est lent. Il construit un @user
, mais construit également les associations pour cet utilisateur. De même pour le @comment
.
J'ai donc pensé qu'appeler {[8] } le résoudrait... mais je reçois des erreurs étranges. Par exemple, current_user
renvoie nil
.
Donc... J'ai décidé d'utiliser Factory.build()
et de supprimer tous les filtres avant dans mon contrôleur parent. Cependant, mon journal rspec dit toujours qu'une tonne d'inserts frappent la base de données lorsque j'inspecte le journal RSPec par la suite (nous parlons de centaines de lignes de code pour seulement 3 tests!)
before(:each) do
@user = Factory.build(:user)
#sign_in @user
controller.stub(:authenticate_user!) #before_filter
controller.stub(:add_secure_model_data) #before_filter
controller.stub(:current_user).and_return(@user)
@comment = Factory.build(:comment)
end
Le triste fait est que le bloc before(:each)
ci-dessus n'a aucun effet sur le test performance. Comme je l'ai découvert, appeler {[11] } appellera toujours en interne Factory.create()
sur les associations d'enfants.
Voici un bloc before(:each)
qui supprime efficacement les pourriels produits dans le journal RSpec. Il m'a donné un coup de pouce de performance de test 35-40%
before(:each) do
@user = Factory.build(:user, :role => Factory.build(:role))
#sign_in @user
controller.stub(:authenticate_user!)
controller.stub(:add_secure_model_data)
controller.stub(:current_user).and_return(@user)
# both of these are still super slow. WTF?!
@site_update = Factory.build(:site_update, :id => 5, :author => Factory.build(:user, :role => Factory.build(:role)))
@comment = Factory.build(:comment,
:author => Factory.build(:user, :role => Factory.build(:role)),
:commentable => @site_update)
end
Cela rend les tests plus rapides, mais c'est aussi laid que sin. Nous ne pouvons pas écrire sérieusement cela pour chaque test... faisons-nous? Que c'est des noix. Je ne suis pas le faire.
Je tiens également à souligner que l'une de ces lignes Factory.build()
prend toujours Au sujet.15 secondes même si elles ne sont pas frapper la base de données!
L'exécution de seulement 3 tests entraîne toujours environ .3 à .35 secondes de temps pris par factory_girl par test! Je pense que c'est totalement inacceptable. Si vous supprimez les lignes Factory.build()
, les tests s'exécutent en 0,00001 secondes.
Je pense que le jury est dans: factory_girl est une bibliothèque vraiment lente. Est la seule solution pour ne pas l'utiliser?
Voici mon factories.rb
:
Factory.define :role do |f|
f.name "Admin"
end
Factory.define :user do |f|
f.first_name "Banoo"
f.last_name "Smith"
f.sequence(:email) { |n| "Banoo.Smith#{n}@gmail.com" }
f.password "secretpassword"
f.association :role
end
Factory.define :admin do |f|
f.first_name "Banoo"
f.last_name "Smith"
f.sequence(:email) { |n| "admin#{n}@gmail.com" }
f.password "secretpassword"
f.association :role
end
Factory.define :course_provider do |f|
f.first_name "Josh"
f.last_name "Bolson"
f.sequence(:email) { |n| "josh.bolson#{n}@gmail.com" }
f.password "secretpassword"
f.association :role
end
Factory.define :director do |f|
f.first_name "Director"
f.last_name "Dude"
f.sequence(:email) { |n| "director#{n}@gmail.com" }
f.password "secretpassword"
f.association :role
end
Factory.define :instructor do |f|
f.first_name "Instructor"
f.last_name "Dude"
f.sequence(:email) { |n| "instructor#{n}@gmail.com" }
f.password "secretpassword"
f.association :role
end
Factory.define :trainee do |f|
f.first_name "Trainee"
f.last_name "Dude"
f.sequence(:email) { |n| "trainee#{n}@gmail.com" }
f.password "secretpassword"
f.association :role
end
Factory.define :private_message do |f|
f.subject "Subject"
f.content "content"
f.is_deleted_by_sender false
f.association :sender, :factory => :user
end
Factory.define :recipient do |f|
f.is_read false
f.is_deleted false
f.association :receiver, :factory => :user
f.association :private_message
end
Factory.define :course_template do |f|
f.name "name"
f.description "description"
f.association :course_provider
end
Factory.define :site_update do |f|
f.subject "Subject"
f.intro "intro"
f.content "content"
f.association :author, :factory => :user
end
Factory.define :comment do |f|
f.content "content"
f.association :author, :factory => :user
f.association :commentable, :factory => :site_update
end
Factory.define :country do |f|
f.name "Liberty"
end
Factory.define :province do |f|
f.name "Freedom"
f.association :country
end
Factory.define :payment_plan do |f|
f.name "name"
f.monthly_amount 79
f.audience "Enterprises"
f.active_courses "500-2000"
end
Factory.define :company do |f|
f.name "name"
f.phone_number "455-323-2132"
f.address "address"
f.postal_code "N7G-5F4"
f.association :province
f.association :payment_plan
end
Factory.define :company_user do |f|
f.first_name "Dan"
f.last_name "Grayson"
f.sequence(:email) { |n| "dan.grayson#{n}@gmail.com" }
f.password "secretpassword"
f.association :role
f.association :company
end
Factory.define :course do |f|
f.notes "notes"
f.difficulty 100
f.association :course_template
f.association :instructor, :factory => :company_user
end
Factory.define :study_group do |f|
f.name "name"
end
Factory.define :help_category do |f|
f.name "name"
end
Factory.define :help_document do |f|
f.question "question"
f.content "content"
f.association :category, :factory => :help_category
end
Factory.define :tag do |f|
f.name "name"
end
Factory.define :partial_mapping do |f|
f.from_suffix "ing"
f.to_suffix "ing"
end
Factory.define :newsletter do |f|
f.subject "subject"
f.content "content"
end
Factory.define :press_contact do |f|
f.full_name "Banoo Smith"
f.email 'Banoo.Smith@gmail.com'
f.phone_number "455-323-2132"
f.address "address"
f.postal_code "N9B-3W5"
f.association :province
end
Factory.define :press_release do |f|
f.headline "Headline"
f.origin "origin"
f.intro "intro"
f.body "body"
f.association :contact, :factory => :press_contact
end
Factory.define :theme do |f|
end
Et benchmark intéressant. Il faut .1 à .14 secondes en moyenne pour faire un appel de Factory.create(:user)
:
$ rails runner 'Benchmark.bm {|x| x.report { 100.times { Factory.create(:user) } } }'
user system total real
9.940000 0.080000 10.020000 ( 14.872736)
, Même un Factory.build(:user)
prend une éternité... et c'est avec :default_strategy => :build
allumé!
$ rails runner 'Benchmark.bm {|x| x.report { 100.times { Factory.build(:user) } } }'
user system total real
9.350000 0.030000 9.380000 ( 11.798339)
Clairement, c'est la preuve que quelque chose ne va pas avec factory_girl. La solution est de s'en débarrasser ou de s'assurer qu'il utilise Factory.build
. C'est la réponse.
, Puisque j'ai résolu mon problème, je me demande pourquoi Factory_girl est si populaire, et pourquoi est-il "bon sens"? On peut objectivement conclure que quels que soient les avantages peut être gagné en utilisant Factory Girl-et il y a beaucoup de belles choses à ce sujet - il ne vaut pas le coût de la performance. Je suis sûr qu'une meilleure gemme d'usine pourrait être développée qui est beaucoup plus performante... mais factory_girl est malheureusement et malheureusement pas.
Ma solution ci-dessous utilise l'instanciation d'objet de base et les stubs, et les tests continuent à passer. Je pense que l'utilisation de Ruby de base, de stubs et le remplissage manuel des valeurs d'objet par test est la "bonne" chose à faire si vous le souhaitez pour éviter les appareils et également obtenir des performances élevées lors de l'exécution des tests.
2 réponses
Eh bien, je suppose que je vais répondre à ma propre question. Je pense que c'est la bonne réponse, et peut-être que d'autres peuvent en apprendre car j'ai dû passer quelques heures à l'apprendre.
Voici comment j'ai obtenu une amélioration de la vitesse de 2000% (ou 20x):
before(:each) do
@user = User.new
controller.stub(:authenticate_user!)
controller.stub(:current_user).and_return(@user)
controller.stub(:add_secure_model_data)
@site_update = SiteUpdate.new
@comment = Comment.new
end
La solution est simplement de ne pas utiliser D'usines d'aucune sorte pour les tests de contrôleur (et peut-être d'autres types de tests). Je suggère seulement d'utiliser L'usine quand c'est trop douloureux pour faire autrement.
Les 3 tests s'exécutent maintenant en 0.07 secondes! Avant il était de 1,4 secondes pour exécuter les 3 tests.
Factory_girl est simplement une bibliothèque terriblement lente. Je ne sais pas ce qu'il fait, mais il n'est pas profilé correctement.
Oui, je sais que cela fait beaucoup plus que de simples MyClass.new
déclarations... mais même pour un langage de script plus lent comme Ruby, la performance est beaucoup plus lente que l'instanciation de classe de base. Il doit subir une optimisation massive pour que Factory.build(:my_class)
soit plus conforme à MyClass.new
Je voudrais suggérez aux implémenteurs de Factory_girl d'essayer de l'obtenir pour que son overhead ne soit pas beaucoup plus lent qu'un appel MyClass.new
de base (à l'exclusion du overhead de la base de données... qui ne peut être évité). Il devrait fournir un bon moyen de construire des objets et vous ne devriez pas avoir à payer une pénalité de performance 20x pour obtenir cet avantage. Ce n'est pas un compromis acceptable.
Tout cela est vraiment dommage, car {[5] } serait bien dans les contrôleurs lorsque vous avez render_views
activé à l'intérieur de vos spécifications de contrôleur. Y devrait être une motivation importante pour corriger cela.
En attendant, utilisez simplement les classes Ruby/Rails de base. Je pense que vous serez étonné de voir à quelle vitesse ils sont réellement....
J'ai eu le même problème que @FireEmblem et j'ai finalement réduit le problème à FactoryGirl.build
. FactoryGirl.stub
je ne voulais pas faire les choses tout mieux.
J'ai finalement réalisé que c'était parce qu'un de mes modèles avait une logique de validation qui faisait une requête HTTP lorsqu'un certain champ était présent. L'usine a mis une valeur dans ce domaine, donc à l'extérieur, il semblait que FactoryGirl ralentissait mes tests. En réalité, c'était le cas, mais seulement parce qu'il a déclenché la requête HTTP. Enlever une ligne d'une de mes usines éliminé la requête HTTP, provoquant une amélioration des performances 60x.