ruby-on-rails - ingles - qué significa en español
¿Cómo puedo hacer que Factory Girl NUNCA llegue a la base de datos si llamo a Factory.build para que mis pruebas de controlador sean RÁPIDAS? (2)
Estoy en una búsqueda para hacer que mis pruebas de Rails sean más rápidas. Solo tengo 520 pruebas, pero tardan 62 segundos en ejecutarse en bash y 82 segundos en ejecutarse en Rubymine.
Como ejemplo de una prueba de controlador típica, estaba usando este código para iniciar sesión como @usuario y crear el @comment básico en un controlador de comentarios para mis pruebas de controlador RSpec:
before(:each) do
@user = Factory.create(:user)
sign_in @user
@comment = Factory.create(:comment)
end
Como te darás cuenta ... esto es lento. @user
un @user
, pero también construye las asociaciones para ese usuario. Lo mismo para el @comment
.
Así que pensé que llamar a Factory.build(:user)
lo resolvería ... pero me salen errores raros. Por ejemplo, current_user
devuelve nil
.
Entonces ... decidí usar Factory.build()
y apagar todos los filtros anteriores en mi controlador principal. Sin embargo, mi registro rspec todavía dice que una TONADA de inserciones están llegando a la base de datos cuando inspecciono el registro RSPec después (¡estamos hablando de cientos de líneas de código para solo 3 pruebas!)
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
Lo triste es que el bloque before(:each)
tiene un efecto CERO en el rendimiento de la prueba. Como descubrí, llamar a Factory.build()
todavía llamará internamente a Factory.create()
en las asociaciones secundarias.
Aquí hay un bloque before(:each)
que elimina efectivamente la basura producida en el registro de RSpec. Me dio un aumento de rendimiento de prueba de 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
Esto hace que las pruebas se ejecuten más rápido, pero también es feo como pecado. No podemos escribir esto seriamente para cada prueba ... ¿verdad? Eso es una locura. No lo haré.
¡También quiero señalar que cualquiera de estas líneas de Factory.build()
todavía toma aproximadamente 15 segundos, a pesar de que NO están llegando a la base de datos!
¡Ejecutar solo 3 pruebas todavía resulta en alrededor de .3 a .35 segundos de tiempo ocupado por factory_girl PER test! Creo que eso es totalmente inaceptable. Si elimina las líneas Factory.build()
, las pruebas se ejecutan en 0.00001 segundos.
Creo que el jurado está en: factory_girl es una biblioteca muy lenta. ¿Es la única solución para no usarlo?
Aquí están mis 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 ''[email protected]''
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
Y referente interesante. Se tarda de .1 a .14 segundos en promedio para hacer una llamada a 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)
Incluso un Factory.build(:user)
toma para siempre ... y esto es con :default_strategy => :build
activada!
$ rails runner ''Benchmark.bm {|x| x.report { 100.times { Factory.build(:user) } } }''
user system total real
9.350000 0.030000 9.380000 ( 11.798339)
Claramente, esto es evidencia de que algo está mal con factory_girl. La solución es deshacerse de él o asegurarse de que utiliza Factory.build
. Esa es la respuesta.
Ya que básicamente he resuelto mi propio problema, me pregunto por qué Factory_girl es tan popular y por qué es "sabiduría común". Se puede llegar a la conclusión objetiva de que cualquier beneficio que se pueda obtener al usar Factory Girl, y hay muchas cosas buenas al respecto, no vale la pena el costo de rendimiento. Estoy seguro de que se podría desarrollar una mejor gema de fábrica que sea mucho más eficaz ... pero factory_girl desafortunadamente no lo es.
Mi solución a continuación utiliza la creación de instancias y apéndices de objetos básicos, y las pruebas continúan pasando Creo que el uso de Ruby básico, los apéndices y el llenado de los valores de los objetos de forma manual para cada prueba es lo "correcto" que se debe hacer si desea evitar los dispositivos y obtener un alto rendimiento al ejecutar pruebas.
Bueno, supongo que voy a responder mi propia pregunta. Creo que es la respuesta correcta, y tal vez otros puedan aprender de ella, ya que tuve que pasar algunas horas para aprenderla.
Así es como obtuve una mejora de velocidad del 2000% (o 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 solución es simplemente no utilizar Fábricas de ningún tipo para las pruebas de los controladores (y quizás otros tipos de pruebas). Sugiero que solo uses Factory cuando sea demasiado doloroso hacer lo contrario.
¡Las 3 pruebas ahora corren en 0.07 segundos! Antes de que fueran 1.4 segundos para ejecutar las 3 pruebas.
Factory_girl es simplemente una biblioteca terriblemente lenta. No sé qué diablos está haciendo, pero no está perfilado correctamente.
Sí, sé que está haciendo mucho más que simples declaraciones de MyClass.new
... pero incluso para un lenguaje de scripts más lento como Ruby, el rendimiento es muchos órdenes de magnitud más lento que la creación de instancias de clase básica. Debe someterse a una optimización masiva para que Factory.build(:my_class)
se Factory.build(:my_class)
más en línea con MyClass.new
Le sugeriría a los implementadores de Factory_girl que intenten obtenerlo para que su sobrecarga no sea mucho más lenta que una llamada MyClass.new
básica (excluyendo la sobrecarga de la base de datos ... eso no se puede evitar). Debería proporcionar una buena forma de construir objetos y no debería tener que pagar una multa de rendimiento de 20x para obtener este beneficio. Eso no es una compensación aceptable.
Todo esto es realmente muy malo, porque Factory.build
sería bueno en los controladores cuando tienes render_views
activadas dentro de las especificaciones de tu controlador. Debe haber una motivación significativa para corregir esto.
Mientras tanto, solo usa las clases básicas de Ruby / Rails. Creo que te sorprenderás de lo rápido que son ...
Tuve el mismo problema que @FireEmblem y finalmente lo reduje a FactoryGirl.build
. FactoryGirl.stub
no mejoró las cosas.
Finalmente me di cuenta de que uno de mis modelos tenía una lógica de validación que hacía una solicitud HTTP cuando un campo determinado estaba presente. La fábrica puso un valor en ese campo, por lo que en el exterior, parecía que FactoryGirl estaba desacelerando mis pruebas. En realidad, lo fue, pero solo porque activó la solicitud HTTP. Al eliminar una línea de una de mis fábricas, se eliminó la solicitud HTTP, lo que provocó una mejora del rendimiento 60x.