2010-01-07 14 views
25

Tenemos una tarea asíncrona que realiza un cálculo potencialmente de larga duración para un objeto. El resultado se almacena en caché en el objeto. Para evitar múltiples tareas de repetir el mismo trabajo, hemos añadido bloqueo con una actualización de SQL atómica:Simulando condiciones de carrera en las pruebas de unidades RSpec

UPDATE objects SET locked = 1 WHERE id = 1234 AND locked = 0 

El bloqueo es sólo para la tarea asíncrona. El objeto en sí aún puede ser actualizado por el usuario. Si eso sucede, cualquier tarea inacabada para una versión anterior del objeto debería descartar sus resultados, ya que es probable que estén desactualizados. Esto también es bastante fácil de hacer con una actualización de SQL atómica:

UPDATE objects SET results = '...' WHERE id = 1234 AND version = 1 

Si el objeto se ha actualizado, su versión no coincidirá y así será descartado los resultados.

Estas dos actualizaciones atómicas deben manejar cualquier condición de carrera posible. La pregunta es cómo verificar eso en pruebas unitarias.

El primer semáforo es fácil de probar, ya que es simplemente una cuestión de configurar dos pruebas diferentes con los dos escenarios posibles: (1) donde el objeto está bloqueado y (2) donde el objeto no está bloqueado. (No es necesario que verifiquemos la atomicidad de la consulta SQL, ya que eso debería ser responsabilidad del proveedor de la base de datos).

¿Cómo se prueba el segundo semáforo? El objeto debe ser cambiado por un tercero un tiempo después del primer semáforo, pero antes del segundo. Esto requeriría una pausa en la ejecución para que la actualización se realice de manera confiable y consistente, pero no conozco ningún soporte para inyectar puntos de interrupción con RSpec. ¿Hay alguna forma de hacer esto? ¿O hay alguna otra técnica que estoy pasando por alto para simular tales condiciones de carrera?

Respuesta

26

Puede tomar prestada una idea de la fabricación de productos electrónicos y colocar los ganchos de prueba directamente en el código de producción. Así como una placa de circuito puede ser fabricada con lugares especiales para que los equipos de prueba controlen y prueben el circuito, podemos hacer lo mismo con el código.

Supongamos que tenemos un cierto código insertar una fila en la base de datos:

class TestSubject 

    def insert_unless_exists 
    if !row_exists? 
     insert_row 
    end 
    end 

end 

Pero este código se ejecuta en varios equipos. Hay una condición de carrera, entonces, ya que otros procesos pueden insertar la fila entre nuestra prueba y nuestra inserción, lo que causa una excepción DuplicateKey. Queremos probar que nuestro código maneja la excepción que resulta de esa condición de carrera. Para hacer eso, nuestra prueba necesita insertar la fila después de la llamada al row_exists? pero antes de la llamada al insert_row. Así que vamos a añadir un gancho de prueba allí mismo:

class TestSubject 

    def insert_unless_exists 
    if !row_exists? 
     before_insert_row_hook 
     insert_row 
    end 
    end 

    def before_insert_row_hook 
    end 

end 

Cuando se ejecuta en la naturaleza, el gancho no hace nada excepto comer una pequeña cantidad de tiempo de CPU. Pero cuando el código está siendo probado para la condición de carrera, la prueba de mono-parches before_insert_row_hook:

class TestSubject 
    def before_insert_row_hook 
    insert_row 
    end 
end 

¿No es astuto? Al igual que una larva de avispa parásita que ha secuestrado el cuerpo de una oruga desprevenida, la prueba secuestró el código bajo prueba para que cree la condición exacta que necesitamos probar.

Esta idea es tan simple como el cursor XOR, por lo que sospecho que muchos programadores la han inventado de forma independiente. En general, he encontrado que es útil para probar código con condiciones de carrera. Espero que ayude.

+1

A-ha. Eso lo haría.Aunque en lugar de agregar un gancho explícito, podría usar 'alias_method_chain' para extender la funcionalidad de un método que _haga_ llamado entre los dos semáforos de todos modos: la tarea de larga ejecución. – Ian

+0

Ian, eso lo haría. –

+4

+1 por usar larvas de avispas parásitas en tu símil. – aronchick

Cuestiones relacionadas