2009-11-03 7 views
5

Estoy buscando una manera de acelerar mis Shoulda + FactoryGirl pruebas.Shoulda + FactoryGirl: ¿Puedo hacer que mis pruebas sean más rápidas?

El modelo que estoy tratando de probar (StudentExam) tiene asociaciones con otros modelos. Estos objetos asociados deben existir antes de que pueda crear un StudentExam. Por esa razón, se crean en setup.

Sin embargo, uno de nuestros modelos (School) lleva mucho tiempo crearlo. Como se llama a setup antes de cada instrucción should, todo el caso de prueba tarda mucho en ejecutarse: crea un nuevo @school, @student, @topic y @exam por cada declaración de deber ejecutada.

Estoy buscando una forma de crear estos objetos una vez y una sola vez. ¿Hay algo así como un método startup para before_all que me permita crear registros que persistirán durante el resto del caso de prueba?

Básicamente estoy buscando algo exactamente como RSpec before(:all). No me preocupa el tema de las dependencias, ya que estas pruebas nunca modificarán esos objetos caros.

Aquí hay un ejemplo de caso de prueba. Disculpas por el código largo (También he creado un gist):

# 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 [email protected]_exam.in_progress 
    end 

    end 

end 

Respuesta

2

Si el problema es crear estos registros solo una vez, puede usar una variable de clase. No es un enfoque limpio, pero al menos debería funcionar.

# 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 [email protected]_exam.in_progress 
    end 

    end 

end 

EDIT: Para corregir la solución super-feo posponer la evaluación con un método de instancia.

# 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 
+0

Me gusta más este enfoque, pero parece que no funciona correctamente. '@@ school = Factory (: school)' genera un error de validación, que 'name' ya está tomado (it' validates_uniqueness_of'). Intenté usar '@@ school || = Factory (: school)' y funcionará si la base de datos de prueba está limpia. Así que terminé con la super-fea '@@ school || = School.first || Factory (: school) ' –

+0

Para solucionar la solución súper fea, posponga la evaluación con un método de instancia. (ver mi edición) –

0

http://m.onkey.org/2009/9/20/make-your-shoulda-tests-faster-with-fast_context es un excelente post sobre cómo hacer sus pruebas Shoulda/fábrica-chica más rápido, utilizando una gema llamada fast_context. Avísame si no es lo que necesitas.

+0

Vi fast_context, pero no creo que ayude en absoluto. Puedo ver que todavía está creando el registro '@ school' en todas y cada una de las pruebas. Los comentarios en esa publicación también me inspiraron a probar algo como esto, pero no funcionó: http://gist.github.com/221668 –

+0

¿@school || = Factory (: school) funciona? –

0

hay un plugin llamado fast_context (github link) que combina declaraciones deben en un solo contexto, la aceleración de las pruebas.

La otra cosa que he estado usando para acelerar mis pruebas es rellenar los datos del dispositivo. FactoryGirl es lento porque está creando esos registros cada vez que se ejecuta el bloque de configuración.

Escribí un complemento llamado Fixie que usa ActiveRecord para rellenar previamente la base de datos de prueba, por lo que los registros que necesita para sus pruebas ya están creados. Puede usar Fixie junto con FactoryGirl si también desea crear nuevos registros en tiempo de ejecución.

+0

(Ver mi comentario arriba re: fast_context). No quiero rellenar previamente la base de datos de prueba, es por eso que estoy usando FactoryGirl en primer lugar (a diferencia de los accesorios). Escribir tus pruebas contra un conjunto de datos previamente poblado es bastante frágil. Prefiero crear los datos de prueba dentro del caso de prueba (simplemente no quiero que vuelva a crear cada afirmación, básicamente). Voy a seguir buscando una forma de tener las cosas inicializadas exactamente una vez por caso de prueba. –

+0

No estoy de acuerdo, pero cada uno es suyo. Si el objeto es caro de crear (especialmente las llamadas API), ¿por qué no rescatar algunas o todas esas cosas? –

2

¿Qué tipo de pruebas estás tratando de escribir? Si realmente desea asegurarse de que todos estos objetos se están coordinando adecuadamente, está escribiendo una prueba de integración y la velocidad no es su principal preocupación. Sin embargo, si está tratando de probar el modelo por unidades, podría lograr mejores resultados pegando agresivamente.

Por ejemplo, si está tratando de verificar que un examen usa el nombre de su asociación escolar cuando llama a exam.location (o lo que sea que llame), no necesita un objeto escolar completo.Solo necesita asegurarse de que el examen llame al método correcto en la escuela. Para probar esto, usted podría hacer algo como lo siguiente (utilizando Test :: Unidad y Mocha, porque eso es lo que estoy familiarizado):

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 

Básicamente, si usted necesita para acelerar las pruebas unitarias porque los objetos son Demasiado caro para construir, en realidad no estás probando unidades. Todos los casos de prueba anteriores se sienten como si estuvieran en el nivel de prueba de la unidad, por lo tanto, stub stub!

+0

Tal vez sea porque no estoy demasiado familiarizado con la práctica, pero por alguna razón no estoy interesado en la idea de usar talones. Me gustaría ejecutar estas pruebas contra instancias de modelo reales. Además, algunas de las pruebas posteriores aseguran que un estudiante no haya escrito un examen más de "n" veces, lo que requerirá consultas en la base de datos: ¿estas situaciones pueden ser útiles? –

+0

Básicamente, creo que lo que estaba tratando de comunicar es que las diferentes pruebas tienen diferentes propósitos. Si está escribiendo una prueba unitaria, está probando una sola "unidad" de código: su modelo en sí. En este caso, lo único que te importa es que funciona bien en el nivel de interfaz, por lo que debes asumir que el otro objeto devuelve datos agradables (por ejemplo, burlarse) y asegurarte de que tu modelo se comporta en un mundo perfecto. Cuando realmente quiere probar varias "unidades" de su sistema (varias clases a la vez), debe tener una prueba de integración más lenta que realmente crea todos los objetos. – Kyle

Cuestiones relacionadas