Estoy tratando de practicar TDD, usándolo para desarrollar un simple como Bit Vector. Estoy usando Swift, pero esta es una pregunta independiente del lenguaje.
My BitVector
es un struct
que almacena un single UInt64
y presenta una API sobre él que le permite tratarlo como una colección. Los detalles no importan mucho, pero es bastante simple. Los 57 bits superiores son bits de almacenamiento, y los 6 bits inferiores son bits de "recuento", que le indica cuántos bits de almacenamiento almacenan realmente un valor contenido.
Hasta ahora, tengo un puñado de capacidades muy simples:
- Un inicializador que construye vectores de bits vacíos
- Una
count
propiedad de tipoInt
- Una
isEmpty
propiedad de tipoBool
- Un operador de igualdad (
==
). NB: este es un operador de igualdad de valores similar aObject.equals()
Java, no un operador de igualdad de referencia como==
en Java.
Me encuentro con un montón de dependencias cíclicas:
La prueba unitaria que prueba mi inicializador necesita verificar que el recién construido
BitVector
. Puede hacerlo de una de 3 maneras:- Comprobar
bv.count == 0
- Comprobar
bv.isEmpty == true
- Mira esto
bv == knownEmptyBitVector
El método 1 se basa
count
, el método 2 se basaisEmpty
(que en sí mismo dependecount
, por lo que no tiene sentido usarlo), el método 3 se basa==
. En cualquier caso, no puedo probar mi inicializador de forma aislada.- Comprobar
La prueba para
count
necesita operar en algo, que inevitablemente prueba mi (s) inicializador (es)La implementación de se
isEmpty
basa encount
La implementación de se
==
basa encount
.
Pude resolver en parte este problema introduciendo una API privada que construye a BitVector
partir de un patrón de bits existente (como a UInt64
). Esto me permitió inicializar valores sin probar ningún otro inicializador, de modo que pudiera "arrancar la correa" hacia arriba.
Para que mis pruebas unitarias sean realmente pruebas unitarias, me encuentro haciendo un montón de hacks, lo que complica sustancialmente mi código de prueba y prueba.
¿Cómo se resuelve exactamente este tipo de problemas?
fuente
BitVector
es un tamaño de unidad perfectamente fino para pruebas unitarias e inmediatamente resuelve sus problemas que los miembros públicos seBitVector
necesitan mutuamente para realizar pruebas significativas.Respuestas:
Te preocupas demasiado por los detalles de implementación.
No importa que en su implementación actual ,
isEmpty
se basecount
(o cualquier otra relación que pueda tener): todo lo que debe preocuparse es la interfaz pública. Por ejemplo, puede tener tres pruebas:count == 0
.isEmpty == true
Estas son todas las pruebas válidas, y se vuelven especialmente importantes si alguna vez decides refactorizar las partes internas de tu clase para que
isEmpty
tenga una implementación diferente que no dependacount
, siempre y cuando tus pruebas sigan aprobadas, sabes que no has retrocedido cualquier cosa.Cosas similares se aplican a sus otros puntos: recuerde probar la interfaz pública, no su implementación interna. Puede que encuentre TDD útil aquí, ya que luego escribiría las pruebas que necesita
isEmpty
antes de escribir cualquier implementación para ello.fuente
Revisas tu pensamiento sobre lo que es una "prueba unitaria".
Un objeto que gestiona datos mutables en la memoria es fundamentalmente una máquina de estados. Por lo tanto, cualquier caso de uso valioso va a, como mínimo, invocar un método para poner información en el objeto e invocar un método para leer una copia de la información del objeto. En los casos de uso interesantes, también va a invocar métodos adicionales que cambien la estructura de datos.
En la práctica, esto a menudo parece
o
La terminología de "prueba unitaria", bueno, tiene una larga historia de no ser muy buena.
Kent escribió la primera versión de SUnit en 1994 , el puerto a JUnit fue en 1998, el primer borrador del libro TDD fue a principios de 2002. La confusión tuvo mucho tiempo para extenderse.
La idea clave de estas pruebas (más exactamente llamadas "pruebas de programador" o "pruebas de desarrollador") es que las pruebas están aisladas unas de otras. Las pruebas no comparten ninguna estructura de datos mutables, por lo que pueden ejecutarse simultáneamente. No le preocupa que las pruebas se ejecuten en un orden específico para medir correctamente la solución.
El caso de uso principal para estas pruebas es que el programador las ejecuta entre las ediciones de su propio código fuente. Si está realizando el protocolo de refactorización rojo verde, un ROJO inesperado siempre indica una falla en su última edición; revierte ese cambio, verifica que las pruebas son VERDES e intenta nuevamente. No hay muchas ventajas en tratar de invertir en un diseño en el que todos y cada uno de los posibles errores sean detectados por una sola prueba.
Por supuesto, si una fusión introduce una falla, encontrar esa falla ya no es trivial. Hay varios pasos que puede seguir para asegurarse de que las fallas sean fáciles de localizar. Ver
fuente
En general (incluso si no usa TDD), debe esforzarse por escribir pruebas tanto como sea posible mientras finge que no sabe cómo se implementa.
Si realmente está haciendo TDD, ese ya debería ser el caso. Sus pruebas son una especificación ejecutable del programa.
El aspecto del gráfico de llamada debajo de las pruebas es irrelevante, siempre que las pruebas en sí sean sensatas y estén bien mantenidas.
Creo que su problema es su comprensión de TDD.
Su problema en mi opinión es que está "mezclando" sus personajes TDD. Sus personas de "prueba", "código" y "refactorizador" operan de manera completamente independiente, idealmente. En particular, sus personas de codificación y refactorización no tienen obligaciones con las pruebas que no sean hacer / mantenerlas funcionando en verde.
Claro, en principio, sería mejor si todas las pruebas fueran ortogonales e independientes entre sí. Pero eso no es una preocupación de sus otras dos personas TDD, y definitivamente no es un requisito estricto o incluso necesariamente realista de sus pruebas. Básicamente: no deseche sus sentimientos de sentido común sobre la calidad del código para tratar de cumplir un requisito que nadie le está pidiendo.
fuente