2010-04-20 10 views
6

He estado tratando de comenzar con las pruebas unitarias mientras trabajo en un pequeño programa cli.¿Cómo debo volver a escribir la ejecución/confirmación de mi base de datos para que sea susceptible de pruebas unitarias?

Mi programa básicamente analiza los argumentos y opciones de la línea de comandos, y decide a qué función llamar. Cada una de las funciones realiza alguna operación en una base de datos.

Así, por ejemplo, podría tener una función de crear:

def create(self, opts, args): 
    #I've left out the error handling. 
    strtime = datetime.datetime.now().strftime("%D %H:%M") 
    vals = (strtime, opts.message, opts.keywords, False) 
    self.execute("insert into mytable values (?, ?, ?, ?)", vals) 
    self.commit() 

Debe mi caso de prueba llamada esta función, a continuación, ejecutar el select de SQL para comprobar que se ha introducido la fila? Eso suena razonable, pero también hace que las pruebas sean más difíciles de mantener. ¿Volverías a escribir la función para devolver algo y verificar el valor de retorno?

Gracias

+0

Creo que las pruebas unitarias son dos palabras. –

Respuesta

8

La respuesta de Alex cubre el enfoque de inyección de dependencia. Otra es factorizar tu método. Tal como está, tiene dos fases: construir una instrucción SQL y ejecutar la declaración SQL. No desea probar la segunda fase: no escribió el motor SQL o la base de datos, puede suponer que funcionan correctamente. La fase 1 es tu trabajo: construir una declaración de SQL. Por lo que puede volver a organizar el código de modo que usted puede probar simplemente la fase 1:

def create_sql(self, opts, args): 
    #I've left out the error handling. 
    strtime = datetime.datetime.now().strftime("%D %H:%M") 
    vals = (strtime, opts.message, opts.keywords, False) 
    return "insert into mytable values (?, ?, ?, ?)", vals 

def create(self, opts, args): 
    self.execute(*self.create_sql(opts, args)) 
    self.commit() 

La función create_sql es la fase 1, y ahora se expresa de una manera que le permite escribir pruebas directamente contra ella: toma valores y devuelve valores, por lo que puede escribir pruebas unitarias que cubran su funcionalidad. La función create en sí misma ahora es más simple y no necesita probarse de manera exhaustiva: podría tener algunas pruebas que demuestren que realmente ejecuta SQL correctamente, pero no tiene que cubrir todos los casos extremos en create.

BTW: This video from Pycon (Tests and Testability) podría ser interesante.

6

Sin duda refactorizar este método para facilitar la prueba - por ejemplo, la inyección de dependencia podría ayudar:

def create(self, opts, args, strtime=None, exec_and_commit=None): 
    #I've left out the error handling. 
    if strtime is None: 
     strtime = datetime.datetime.now().strftime("%D %H:%M") 
    vals = (strtime, opts.message, opts.keywords, False) 
    if exec_and_commit is None: 
     exec_and_commit = self.execute_and_commit 
    exec_and_commit("insert into mytable values (?, ?, ?, ?)", vals) 

esto por supuesto se supone que tiene un método que llama execute_and_commitexecute y luego commit métodos.

De esta manera, el código de prueba puede inyectar un valor conocido para strtime, e inyectar su propio invocable como exec_and_commit para verificar que se llama con los argumentos esperados.

0

No está familiarizado con la sintaxis de Python, pero si no está familiarizado con las pruebas unitarias, la forma más fácil de comenzar podría ser extraer un método que pase en la línea de comando args y recuperar el comando sql. En este método puede probar la parte de su código donde reside la lógica real. Pase diferentes tipos de argumentos y compruebe los resultados con respecto a lo que debería ser el comando sql. Una vez que comience a familiarizarse con las pruebas unitarias, puede aprender un poco sobre la burla y la inyección de dependencia para probar si llama correctamente a las funciones que actualizan la base de datos.

Esperemos que están más familiarizados con Java sintaxis de C# que estoy con la sintaxis de Python :)

public string GetSqlCommand(string[] commandLineArgs) 
{ 
    //do your parsing here 
    return sqlCommand; 
} 
[Test] 
public void emptyArgs_returnsEmptySqlCommand() 
{ 
    string expectedSqlCommand=""; 
    assert.AreEqual(expectedSqlCommand, GetSqlCommand(new string[]) 
} 
3

En general, le gustaría tener pruebas de unidad en su lugar antes de refactorización, para garantizar que no se realicen cambios de ruptura. Y sin embargo, puede ser necesaria una refactorización para permitir la capacidad de prueba ...una desafortunada paradoja, una que me ha mordido antes.

Dicho esto, hay algunas refactorizaciones pequeñas que se pueden realizar de forma segura, sin cambiar el comportamiento. Renombrar y extraer son dos.

Te recomiendo que eches un vistazo al libro de Michael Feathers, Trabajando con Legacy Code. Se centra en el código de refactorización para la capacidad de prueba. Los ejemplos están en Java, pero los conceptos se aplicarían igual de bien a Python.

+0

y el código C++ se usa en el libro – Gutzofter

Cuestiones relacionadas