El problema ocurre al hacer TDD. Después de un par de pasadas de prueba, los tipos de retorno de alguna clase / módulo cambian. En un lenguaje de programación de tipo estático, si se utilizó un objeto simulado anterior en las pruebas de otra clase y no se modificó para reflejar el cambio de tipo, se producirán errores de compilación.
Sin embargo, para lenguajes dinámicos, el cambio en los tipos de retorno podría no detectarse y las pruebas de la otra clase aún pasarán. Claro que podría haber pruebas de integración que deberían fallar más adelante, pero las pruebas unitarias pasarían erróneamente. ¿Hay alguna forma de evitar esto?
Actualización con una muestra trivial (en un lenguaje inventado) ...
Versión 1:
Calc = {
doMultiply(x, y) {return x * y}
}
//.... more code ....
// On some faraway remote code on a different file
Rect = {
computeArea(l, w) {return Calc.doMultipy(x*y)}
}
// test for Rect
testComputeArea() {
Calc = new Mock()
Calc.expect(doMultiply, 2, 30) // where 2 is the arity
assertEqual(30, computeArea)
}
Ahora, en la versión 2:
// I change the return types. I also update the tests for Calc
Calc = {
doMultiply(x, y) {return {result: (x * y), success:true}}
}
... Rect arrojará una excepción en el tiempo de ejecución, pero la prueba aún tendrá éxito.
fuente
class X
, sino de las pruebas de lasclass Y
cuales dependeX
y, por lo tanto, se prueba con un contrato diferente al que se ejecuta en la producción.Respuestas:
Hasta cierto punto, esto es solo parte del costo de hacer negocios con lenguajes dinámicos. Obtienes mucha flexibilidad, también conocida como "cuerda suficiente para ahorcarte". Ten cuidado con eso.
Para mí, el problema sugiere utilizar técnicas de refactorización diferentes a las que usaría en un lenguaje de tipo estático. En un lenguaje estático, reemplaza los tipos de retorno en parte para que pueda "apoyarse en el compilador" para encontrar qué lugares pueden romperse. Es seguro hacerlo, y probablemente sea más seguro que modificar el tipo de retorno en su lugar.
En un lenguaje dinámico, no puede hacer eso, por lo que es mucho más seguro modificar el tipo de retorno en lugar de reemplazarlo. Posiblemente, lo modifique agregando su nueva clase como miembro, para las clases que lo necesitan.
fuente
Si su código cambia y sus pruebas aún pasan, entonces hay algo mal con sus pruebas (es decir, le falta una afirmación), o el código en realidad no cambió.
¿Qué quiero decir con eso? Bueno, sus pruebas describen los contratos entre partes de su código. Si el contrato es "el valor de retorno debe ser iterable", entonces cambiar el valor de retorno de una matriz a una lista no es en realidad un cambio en el contrato y, por lo tanto, no necesariamente desencadenará una falla de prueba.
Para evitar aserciones faltantes, puede usar herramientas como el análisis de cobertura de código (no le dirá qué partes de su código se prueban, pero le dirá qué partes definitivamente no se prueban), la complejidad ciclomática y la complejidad de NPath (que le dará un límite inferior en el número de afirmaciones requiere para lograr la plena cobertura de código C1 y C2) y mutación probadores (que inyecte mutaciones en el código como girar
true
afalse
, los números negativos en positivos, objetos ennull
etc., y comprobar si ese hace que las pruebas fallen).fuente