2010-01-27 9 views
42

me gustaría saber cómo escribir pruebas unitarias para un módulo que se mezcla en un par de clases pero no se sabe muy bien cómo ir sobre él:Rieles: ¿Cómo escribo las pruebas para un módulo Ruby?

  1. puedo probar los métodos de instancia por escrito prueba en uno de los archivos de prueba para una clase que los incluye (no parece correcto) o puede de alguna manera mantener las pruebas de los métodos incluidos en un archivo separado específico para el módulo?

  2. La misma pregunta se aplica a los métodos de clase.

  3. ¿Debo tener un archivo de prueba separado para cada una de las clases en el módulo, como lo hacen los modelos de rieles normales, o viven en el archivo de prueba del módulo general, si eso existe?

Respuesta

55

en mi humilde opinión, que debería estar haciendo cobertura de la prueba funcional que cubrirá todos los usos del módulo, y luego probarlo en forma aislada en una prueba de unidad:

setup do 
    @object = Object.new 
    @object.extend(Greeter) 
end 

should "greet person" do 
    @object.stubs(:format).returns("Hello {{NAME}}") 
    assert_equal "Hello World", @object.greet("World") 
end 

should "greet person in pirate" do 
    @object.stubs(:format).returns("Avast {{NAME}} lad!") 
    assert_equal "Avast Jim lad!", @object.greet("Jim") 
end 

Si las pruebas unitarias son buenas, usted debe estar capaz de probar la funcionalidad del humo en los módulos en los que se mezcla.

O ...

Escribir un ayudante de prueba, que afirma el comportamiento correcto, a continuación, utilizar eso contra cada clase se mezcla en uso sería la siguiente:.

setup do 
    @object = FooClass.new 
end 

should_act_as_greeter 

Si las pruebas unitarias son buenas , esto puede ser una simple prueba de humo del comportamiento esperado, verificar que se llamen los delegados, etc.

+0

Cuando dice "cobertura de prueba funcional", supongo que se refiere a la funcionalidad que adquieren los modelos y no a las pruebas de controlador almacenadas prueba/funcional. Gracias por su respuesta. Me gusta la idea de probar el módulo de forma aislada y escribir un ayudante que las otras clases puedan llamar para usar ese módulo. – tsdbrown

+1

Por funcional quiero decir desde el exterior hacia adentro. Esta suele ser una prueba de controlador, pero no siempre. De cualquier forma, la cobertura funcional debe tocar (o al menos pastar) todas las áreas del sistema. Si las pruebas de su unidad son fuertes, las pruebas funcionales a menudo son suficientes para cubrir su culo. Escribir demasiadas pruebas de bajo nivel puede ser una mala inversión. Si nunca va a fallar solo, ¿atrapa errores? ¿Se ha guardado el "tiempo probable de depuración" * "probabilidad de un error"> "tiempo para escribir la prueba"? Ignora esto si un error puede matar a personas o a tu empresa. cwninja

+0

No. Las pruebas de controlador son (casi) siempre malas ideas (las historias de Pepino hacen lo mismo mejor), y no son relevantes para el tema en cuestión de todos modos. Solo prueba de unidad como en la primera muestra de código. –

3

general pondría a prueba el módulo con el mayor aislamiento posible, esencialmente a prueba los métodos, con el código sólo lo suficiente, se burla y talones para que funcione.

Probablemente también tendré pruebas para las clases en las que están incluidos los módulos. No puedo probar todas las clases, pero probaría lo suficiente de las clases para obtener una buena cobertura y tener una idea de los problemas que surjan. Estas pruebas no necesitan probar explícitamente el módulo, pero ciertamente probarían su uso en escenarios particulares.

Cada conjunto de pruebas tendría su propio archivo.

+0

Gracias, estoy de acuerdo con lo que dices sobre probar la funcionalidad en las clases en las que está incluida. ¿Tendrás un archivo de prueba para cada clase adicional en el módulo o un archivo de prueba para el módulo en general? Supongo que estoy más pendiente de los archivos de prueba reales (nombres de archivo, ubicaciones, etc.) en lugar de qué probar. – tsdbrown

4

Intento mantener mis pruebas centradas solo en el contrato para esa clase/módulo en particular. Si compruebo el comportamiento del módulo en una clase de prueba para ese módulo (generalmente incluyendo ese módulo en una clase de prueba declarada en la especificación para ese módulo), entonces no duplicaré esa prueba para una clase de producción que use ese módulo. Pero si hay un comportamiento adicional que deseo probar para la clase de producción, o cuestiones de integración, escribiré pruebas para la clase de producción.

Por ejemplo, tengo un módulo llamado AttributeValidator que realiza validaciones livianas similares a ActiveRecord. Escribo pruebas para el comportamiento del módulo en la especificación del módulo:

before(:each) do 
    @attribute_validator = TestAttributeValidator.new 
end 

describe "after set callbacks" do 
    it "should be invoked when an attribute is set" do 
    def @attribute_validator.after_set_attribute_one; end 
    @attribute_validator.should_receive(:after_set_attribute_one).once 
    @attribute_validator.attribute_one = "asdf" 
    end 
end 

class TestAttributeValidator 
    include AttributeValidator 
    validating_str_accessor [:attribute_one, /\d{2,5}/]  
end 

Ahora en una clase de producción que incluye el módulo, no voy a volver a afirmar que las devoluciones de llamada se realizan, pero puedo afirmar que la clase incluido tiene un cierto conjunto de validación con cierta expresión regular, algo particular para esa clase, pero no reproduce las pruebas que escribí para el módulo.En la especificación para la clase de producción, quiero garantizar que se establezcan validaciones particulares, pero no que las validaciones funcionen en general. Esta es una especie de prueba de integración, pero que no repita los mismos afirmaciones que hice para el módulo:

describe "ProductionClass validation" do 
    it "should return true if the attribute is valid" do 
    @production_class.attribute = @valid_attribute 
    @production_class.is_valid?.should be_true 
    end 
    it "should return false if the attribute is invalid" do 
    @production_class.attribute = @invalid_attribute 
    @production_class.is valid?.should be_false 
    end 
end 

Hay alguna duplicación aquí (como la mayoría de las pruebas de integración tendrán), pero las pruebas demuestran dos cosas diferentes para mi Un conjunto de pruebas comprueba el comportamiento general del módulo, el otro demuestra las preocupaciones particulares de implementación de una clase de producción que usa ese módulo. A partir de estas pruebas, sé que el módulo validará atributos y realizará devoluciones de llamadas, y sé que mi clase de producción tiene un conjunto específico de validaciones para criterios específicos exclusivos de la clase de producción.

Espero que ayude.

+0

Gracias por una respuesta completa con ejemplos. –

+0

La desventaja de este enfoque es que realmente crea una clase que puede colisionar con otras pruebas. Consulte las respuestas de mayor calificación para los enfoques que no dejan efectos secundarios. – mrm

13

clases de uso de línea (no estoy haciendo ninguna flexmock de lujo o Stubba/uso de moka sólo para mostrar el punto)

def test_should_callout_to_foo 
    m = Class.new do 
    include ModuleUnderTest 
    def foo 
     3 
    end 
    end.new 
    assert_equal 6, m.foo_multiplied_by_two 
end 

Cualquier burlones/stubbing biblioteca por ahí debería darle una forma más limpia de hacer esto. También se pueden utilizar estructuras:

instance = Struct.new(:foo).new 
class<<instance 
    include ModuleUnderTest 
end 
instance.foo = 4 

Si tengo un módulo que está siendo utilizado en muchos lugares que tengo una prueba de unidad del mismo que hace precisamente eso (deslizar un objeto de prueba en los métodos de módulo y prueba si el módulo los métodos funcionan correctamente en ese objeto).

Cuestiones relacionadas