2012-05-09 15 views
21

Tengo una función de modelo que quiero asegurarme de que utiliza una transacción. Por ejemplo:Cómo probar que una determinada función utiliza una transacción en Rails y rspec 2

class Model 
    def method 
    Model.transaction do 
     # do stuff 
    end 
    end 
end 

Mi enfoque actual es una llamada al método código auxiliar dentro del bloque para elevar una excepción ActiveRecord::Rollback, y después comprobar para ver si la base de datos ha cambiado realmente. Pero esto implica que si por alguna razón la implementación dentro del bloque cambiara, entonces la prueba se rompería.

¿Cómo probarías esto?

Respuesta

25

Debería ver el problema desde una perspectiva diferente. La prueba de si una función utiliza una transacción es inútil desde un punto de vista de comportamiento. No proporciona información sobre si la función SE ACONSEJA como se esperaba.

Lo que debe probar es el comportamiento, es decir, el resultado esperado es correcto. Para mayor claridad, digamos que ejecuta la operación A y la operación B dentro de la función (ejecutada dentro de una transacción). La Operación A acredita a un usuario 100 USD en su aplicación. La Operación B carga la tarjeta de crédito de los usuarios con 100 USD.

Ahora debe proporcionar información de entrada no válida para la prueba, de modo que el débito de la tarjeta de crédito del usuario falla. Envuelva toda la llamada a la función en un expect { ... }.not_to change(User, :balance).

De esta forma, prueba el COMPORTAMIENTO esperado: si el débito de la tarjeta de crédito falla, no acredite al usuario el importe. Además, si solo refactoriza su código (p.deja de usar transacciones y deshacer cosas manualmente), entonces el resultado de su caso de prueba no debería verse afectado.

Dicho esto, aún debe probar ambas operaciones en forma aislada como se menciona en @luacassus. Además, es correcto que su caso de prueba falle en caso de que haya realizado un cambio "incompatible" (es decir, cambie el comportamiento) con el código fuente mencionado como @ rb512.

+2

O un poco mejor 'esperar {...} .no_hacer cambios (Usuario,: ​​saldo)' – Andrew

+0

Gracias por el comentario @Andrew, siempre estoy atento a estos pequeños ajustes :) Lo cambié en mi respuesta. – emrass

+1

Esta respuesta es útil en teoría, pero no hay una gran manera de probar un comportamiento de concurrencia como este. Además, suponiendo que confiemos en que la implementación de 'transacción 'de ActiveRecord es correcta, solo necesitamos probar que se le pasó el bloque en particular (no tengo una buena manera de probar esto, por cierto ...) –

2

Primero, debe encerrar su bloque Model.transaction do ... end con begin rescue end bloques.

La única manera de verificar las reversiones de transacciones es presentar una excepción. Entonces su enfoque actual es todo bueno. En cuanto a su preocupación, un cambio en la implementación siempre significaría cambiar los casos de prueba en consecuencia. No creo que sea posible tener un caso de prueba de unidad genérica que no requiera cambios, incluso si la implementación del método cambia.

Espero que ayude!

+0

Esto no es así para 'raise ActiveRecord :: Rollback' esto es manejado directamente por la transacción y no se vuelve a generar. –

5

En general, debe utilizar la prueba de especs de "pure" para probar los trozos de la aplicación (clases y métodos) en el aislamiento. Por ejemplo, si tiene el siguiente código:

class Model 
    def method 
    Model.transaction do 
    first_operation 
    second_operation 
    end 
end 

debe probar first_operation y second_operation en escenarios de prueba separadas, idealmente sin chocar con la base de datos. Más tarde puede escribir una prueba para Model#method y simular esos dos métodos.

En el siguiente paso puede escribir una prueba de integración de alto nivel con https://www.relishapp.com/rspec/rspec-rails/docs/request-specs/request-spec y comprobar cómo este código afectará a la base de datos en diferentes condiciones, por ejemplo cuando second_method fallará.

En mi opinión, este es el enfoque más pragmático para probar un código que produce consultas de bases de datos complejas.

1

He estado haciendo lo mismo pero ahora creo que quizás todo lo que necesita hacer es probar que el método de 'transacción' ha sido llamado en el modelo en una especificación y luego probar el cuerpo del bloque en otras especificaciones separadas . Aunque eso no aseguraría que la transacción ajuste sus llamadas a los métodos como lo hace su prueba actual y no algún otro código que pueda estar allí.

+1

"prueba el cuerpo del bloque" ¿Cómo es esto posible? –

+0

Bueno, no puede llamar al bloque específicamente, a menos que lo separe de una función o lambda, pero puede llamar a la función adjunta y probar que ha hecho las cosas que quería. Pero como dije, eso no le dará ninguna información sobre si las cosas se hicieron en una transacción. – Renra

-2

pruebas con sangre En los carriles modo consola Sandbox

rieles consola --sandbox

+0

OP pregunta por pruebas automatizadas (vea las etiquetas "rspec2" y "rspec-rails"), sin verificar que funcione en la consola de Rails solo una vez. – awendt

12

hay que mencionar una gran Gotcha: Cuando la verificación de operaciones, es necesario desactivar transactional_fixtures. Esto se debe a que el marco de prueba (por ejemplo, Rspec) envuelve el caso de prueba en el bloque de transacción. El after_commit nunca se llama porque nada está realmente comprometido. Esperar la reversión dentro de la transacción tampoco funciona, incluso si usa: requires_new => true. En cambio, la transacción se retrotrae después de que se ejecuta la prueba. Ref. http://api.rubyonrails.org/classes/ActiveRecord/Transactions/ClassMethods.html transacciones anidadas.

+0

era consciente de eso. Gracias por su respuesta de todos modos. – tdgs

+0

Este es un gran problema que debe tener en cuenta al escribir especificaciones para el código que trata transacciones AR. –

Cuestiones relacionadas