¿Cómo aplico TDD a las funciones de lectura / escritura?

10

Parece un problema de pollo y huevo.

Puede hacer que una función de escritura escriba en algún almacén de datos, pero nunca se sabe que la guardó correctamente sin una función de lectura probada.

Puede hacer que una función de lectura se lea desde un almacén de datos, pero ¿cómo se colocan las cosas en ese almacén de datos, para leer, sin una función de escritura probada?

EDITAR:

Me estoy conectando y realizando transacciones con una base de datos SQL para guardar y cargar objetos para su uso. No tiene sentido probar las funciones de acceso que proporciona el DB, pero envuelvo dichas funciones de DB para serializar / deserializar los objetos. Quiero estar seguro de que estoy escribiendo y leyendo las cosas correctas hacia y desde la base de datos correctamente.

No es como agregar / eliminar, como menciona @snowman. Quiero saber que los contenidos que he escrito son correctos, pero eso requiere una función de lectura bien probada. Cuando leo, quiero estar seguro de que mi lectura ha creado correctamente un objeto igual a lo escrito; pero eso requiere una función de escritura bien probada.

usuario2738698
fuente
¿Está escribiendo su propio almacén de datos o está utilizando uno existente? Si está utilizando uno existente, suponga que ya funciona. Si está escribiendo el suyo, esto funciona de la misma manera que escribir cualquier otro software con TDD.
Robert Harvey
1
Si bien está estrechamente relacionado, no creo que sea un duplicado de esa pregunta en particular. El objetivo de duplicación está hablando de agregar / eliminar, este es de lectura / escritura. La diferencia es que una dependencia de lectura / escritura probablemente depende del contenido de los objetos que se leen / escriben, mientras que una simple prueba de agregar / eliminar probablemente sería mucho más simple: el objeto existe o no.
2
Puede organizar una base de datos con datos y sin función de escritura para probar una función de lectura.
JeffO

Respuestas:

7

Comience con la función de lectura.

  • En la configuración de prueba : cree la base de datos y agregue datos de prueba. ya sea a través de scripts de migración o desde una copia de seguridad. Como este no es su código, no requiere una prueba en TDD

  • En la prueba : instancia tu repositorio, apunta a tu base de datos de prueba y llama al método Read. Compruebe que se devuelven los datos de prueba.

Ahora que tiene una función de lectura totalmente probada, puede pasar a la función de escritura, que puede usar la lectura existente para verificar sus propios resultados

Ewan
fuente
Supongo que podría crear un DB en la memoria para acelerar las cosas, pero eso podría ser demasiado complejo. ¿Por qué no usar simulacros en lugar de pruebas unitarias?
BЈовић
1
Un poco de Devil's Advocate aquí, pero ¿cómo se prueba que la base de datos se creó correctamente? Como OP declaró, pollo y huevo.
user949300
1
@Ewan: estoy totalmente de acuerdo en que no debe probar su código de base de datos. Pero, ¿cómo sabe que su código de configuración de base de datos no olvidó un INSERT en algún lugar o puso un valor incorrecto en una columna?
user949300
1
desde un enfoque TDD puro, la prueba ES el requisito. entonces lógicamente no puede estar equivocado. obvs en el mundo real hay que mirarlo a los ojos
Ewan
1
Quis custodiet ipsos custodes? O "¿Quién prueba las pruebas?" :-) Estoy de acuerdo con usted en que, en un mundo TDD purista, esta sería la forma horriblemente tediosa y propensa a errores (especialmente si se tratara de una estructura de tabla múltiple complicada con 8 UNIONES) para hacerlo. Votado
user949300
6

A menudo solo escribo seguido de una lectura. por ejemplo (pseudocódigo)

Foo foo1 = setup some object to write
File tempfile = create a tempfile, possibly in memory 
writeFoo(foo1, tempfile) 
Foo foo2 = readFoo(tempfile) 
assertEquals(foo1, foo2); 
clean-up goes here

Agregado más tarde

Además de que esta solución es "prgamática" y "suficientemente buena", se podría argumentar que las otras soluciones prueban algo incorrecto . Probar si las cadenas o las declaraciones SQL coinciden no es una idea terrible, lo he hecho yo mismo, pero está probando un efecto secundario y es frágil. ¿Qué sucede si cambia las mayúsculas, agrega un campo o actualiza un número de versión dentro de sus datos? ¿Qué sucede si su controlador SQL cambia el orden de las llamadas por eficiencia o si su serializador XML actualizado agrega un espacio adicional o cambia una versión de esquema?

Ahora, si debe adherirse estrictamente a alguna especificación oficial, entonces estoy de acuerdo en que verificar los detalles finos es apropiado.

user949300
fuente
1
¿Porque es un seudocódigo realmente denso en un 90%? No estoy seguro. ¿Quizás resaltar el texto y hacer que el código sea menos ruidoso?
RubberDuck
1
Sí @Ewan. El fanático desaprobaría esto, sin embargo, el programador pragmático diría "lo suficientemente bueno" y seguiría adelante.
RubberDuck
1
Leí un poco la pregunta como ... "suponiendo que siga a TDD como un fanático ..."
Ewan
1
mi interpretación del OP y TDD es que su prueba debe escribirse primero y no usar tanto lectura como escritura a menos que también se pruebe en otro lugar.
Ewan
2
estás probando, 'leer debería devolver lo que escribo' pero los requisitos son 'leer debería devolver datos de la base de datos' y 'escribir debería escribir datos en la base de datos'
Ewan
4

No lo hagas No pruebe la unidad de E / S. Es una pérdida de tiempo.

Lógica de prueba unitaria. Si hay una gran cantidad de lógica que desea probar en el código de E / S, debe refactorizar su código para separar la lógica de cómo hacer E / S y qué E / S hace del negocio real de hacer E / S (que es casi imposible de probar).

Para elaborar un poco, si desea probar un servidor HTTP, debe hacerlo a través de dos tipos de pruebas: pruebas de integración y pruebas unitarias. Las pruebas unitarias no deberían interactuar con E / S en absoluto. Eso es lento e introduce muchas condiciones de error que no tienen nada que ver con la exactitud de su código. ¡Las pruebas unitarias no deben estar sujetas al estado de su red!

Su código debe separar:

  • La lógica de determinar qué información enviar
  • La lógica de determinar qué bytes enviar para enviar un bit de información en particular (cómo codifico una respuesta, etc. en bytes sin formato), y
  • El mecanismo de escribir realmente esos bytes en un socket.

Los dos primeros implican lógica y decisiones y necesitan pruebas unitarias. Lo último no implica tomar muchas decisiones, si es que hay alguna, y puede probarse maravillosamente usando pruebas de integración.

En realidad, este es un buen diseño en general, pero una de las razones es que facilita la prueba.


Aquí hay unos ejemplos:

  • Si está escribiendo código que obtiene datos de una base de datos relacional, puede realizar una prueba unitaria de cómo asigna los datos devueltos de las consultas relacionales a su modelo de aplicación.
  • Si está escribiendo código que escribe datos en una base de datos relacional, puede probar la unidad de datos que desea escribir en la base de datos sin probar realmente las consultas SQL particulares que utiliza. Por ejemplo, puede guardar dos copias del estado de su aplicación en la memoria: una copia que representa el aspecto de la base de datos y la copia de trabajo. Cuando desee sincronizar con la base de datos, debe diferenciarlos y escribir las diferencias en la base de datos. Puede probar fácilmente la unidad de ese código diff.
  • Si está escribiendo código que lee algo de un archivo de configuración, desea probar su analizador de formato de archivo de configuración, pero con cadenas del archivo fuente de prueba en lugar de las cadenas que obtiene del disco.
Miles Rout
fuente
2

No sé si es una práctica estándar o no, pero funciona bien para mí.

En mis implementaciones de métodos de lectura y escritura que no son de base de datos, uso mi propio tipo específico toString()y fromString()métodos como detalles de implementación.

Estos se pueden probar fácilmente de forma aislada:

 assertEquals("<xml><car type='porsche'>....", new Car("porsche").toString());

Para los métodos de lectura y escritura reales, tengo una prueba de integración que lee y escribe físicamente en una prueba

Por cierto: ¿Hay algo malo en tener una prueba que lea / escriba juntas?

k3b
fuente
Puede que no se sienta bien o "puro", pero esta es la solución pragmática.
RubberDuck
También me gusta la idea de probar leer y escribir juntos. Su toString () es un buen compromiso pragmático.
user949300
1

Los datos conocidos deben formatearse de manera conocida. La forma más fácil de implementar esto es usar una cadena constante y comparar el resultado, como se describe en @ k3b.

Sin embargo, no estás limitado a constantes. Puede haber una serie de propiedades de los datos escritos que puede extraer utilizando un tipo diferente de analizador, como expresiones regulares, o incluso sondeos ad hoc que buscan características de los datos.

En cuanto a leer o escribir los datos, puede ser útil tener un sistema de archivos en memoria que le permita ejecutar sus pruebas sin la posibilidad de interferencia de otras partes del sistema. Si no tiene acceso a un buen sistema de archivos en memoria, use un árbol de directorios temporal.

BobDalgleish
fuente
1

Utiliza la inyección de dependencia y la burla.

No desea probar su controlador SQL y no desea probar si su base de datos SQL está en línea y configurada correctamente. Eso sería parte de una prueba de integración o sistema. Desea probar si su código envía las declaraciones SQL que se supone que debe enviar y si interpreta las respuestas de la manera en que se supone que debe hacerlo.

Entonces, cuando tiene un método / clase que se supone que debe hacer algo con una base de datos, no haga que obtenga esa conexión de base de datos por sí misma. Cámbielo para que se le pase el objeto que representa la conexión de la base de datos.

En su código de producción, pase el objeto real de la base de datos.

En sus pruebas unitarias, pase un objeto simulado que simplemente se comporte como si una base de datos real no contactara realmente con un servidor de base de datos. Solo haga que verifique si recibe las declaraciones SQL que se supone que debe recibir y luego responde con respuestas codificadas.

De esta forma, puede probar la capa de abstracción de la base de datos sin necesidad de una base de datos real.

Philipp
fuente
Abogado del Diablo: ¿Cómo sabes qué sentencias SQL se "debe recibir"? ¿Qué sucede si el controlador DB optimiza el orden de lo que aparece en el código?
user949300
@ user949300 Un objeto simulado de la base de datos generalmente reemplaza al controlador de la base de datos.
Philipp
cuando está probando un repositorio no tiene sentido inyectar un cliente de base de datos simulado. Debe probar que su código ejecuta sql que funciona en la base de datos. de lo contrario terminas probando tu simulacro
Ewan
@Ewan De eso no se trata la prueba de unidad. Una prueba unitaria prueba una unidad de código, aislada del resto del mundo. No está probando las interacciones entre componentes, como su código y la base de datos. Para eso están las pruebas de integración.
Philipp
si. Estoy diciendo que no hay unidad de punto que pruebe un repositorio de db. prueba de integración es lo único que vale la pena hacer
Ewan
0

Si está utilizando un mapeador relacional de objetos, generalmente hay una biblioteca asociada que se puede usar para probar que sus mapeos funcionan correctamente creando un agregado, persistiéndolo y recargándolo desde una nueva sesión, seguido de la verificación del estado contra El objeto original.

NHibernate ofrece pruebas de especificación de persistencia . Se puede configurar para trabajar contra un almacén en memoria para pruebas unitarias rápidas.

Si sigue la versión más simple de los patrones de Repositorio y Unidad de Trabajo, y prueba todas sus asignaciones, puede contar con que las cosas funcionarán.

pnschofield
fuente