Si tengo una función en mi código que dice así:
class Employee{
public string calculateTax(string name, int salary)
{
switch (name)
{
case "Chris":
doSomething($salary);
case "David":
doSomethingDifferent($salary);
case "Scott":
doOtherThing($salary);
}
}
Normalmente refactorizaría esto para usar Ploymorphism usando un patrón de estrategia y clase de fábrica:
public string calculateTax(string name)
{
InameHandler nameHandler = NameHandlerFactory::getHandler(name);
nameHandler->calculateTax($salary);
}
Ahora, si estuviera usando TDD, tendría algunas pruebas que funcionan en el original calculateTax()
antes de refactorizar.
ex:
calculateTax_givenChrisSalaryBelowThreshold_Expect111(){}
calculateTax_givenChrisSalaryAboveThreshold_Expect111(){}
calculateTax_givenDavidSalaryBelowThreshold_Expect222(){}
calculateTax_givenDavidSalaryAboveThreshold_Expect222(){}
calculateTax_givenScottSalaryBelowThreshold_Expect333(){}
calculateTax_givenScottSalaryAboveThreshold_Expect333(){}
Después de refactorizar, tendré una clase Factory NameHandlerFactory
y al menos 3 implementaciones de InameHandler
.
¿Cómo debo proceder para refactorizar mis pruebas? ¿Debo eliminar la prueba unitaria claculateTax()
de EmployeeTests
y crear una clase de prueba para cada implementación de InameHandler
?
¿Debería probar también la clase Factory?
salary
a la funcióncalculateTax()
. De esta manera, creo que duplicaré el código de prueba para la función original y las 3 implementaciones de la clase de estrategia.Comenzaré diciendo que no soy un experto en TDD o pruebas unitarias, pero así es como probaría esto (usaré un código similar a pseudo):
Por lo tanto, probaría que el
calculateTax()
método de clase de empleado solicita correctamenteNameHandlerFactory
un aNameHandler
y luego llama alcalculateTax()
método de devoluciónNameHandler
.fuente
Employee.calculateTax()
método. De esa manera, no necesita agregar pruebas de empleado adicionales cuando introduce un nuevo NameHandler.Estás tomando una clase (empleado que hace todo) y estás formando 3 grupos de clases: la fábrica, el empleado (que solo contiene una estrategia) y las estrategias.
Entonces haga 3 grupos de pruebas:
Por supuesto, puede realizar pruebas automatizadas para todo el shebang, pero ahora se parecen más a las pruebas de integración y deben tratarse como tales.
fuente
Antes de escribir cualquier código, comenzaría con una prueba para una Fábrica. Burlándome de las cosas que necesito, me obligaría a pensar en las implementaciones y los casos de uso.
Luego implementaría una Fábrica y continuaría con una prueba para cada implementación y finalmente las implementaciones en sí para esas pruebas.
Finalmente, eliminaría las viejas pruebas.
fuente
Mi opinión es que no debe hacer nada, lo que significa que no debe agregar ninguna prueba nueva.
Insisto en que esta es una opinión, y en realidad depende de la forma en que percibes las expectativas del objeto. ¿Crees que al usuario de la clase le gustaría proporcionar una estrategia para el cálculo de impuestos? Si no le importa, entonces las pruebas deberían reflejar eso, y el comportamiento reflejado en las pruebas unitarias debería ser que no debería importarles que la clase haya comenzado a usar un objeto de estrategia para calcular el impuesto.
De hecho, me encontré con este problema varias veces al usar TDD. Creo que la razón principal es que un objeto de estrategia no es una dependencia natural, a diferencia de una dependencia de límite arquitectónico como un recurso externo (un archivo, una base de datos, un servicio remoto, etc.). Como no es una dependencia natural, generalmente no baso el comportamiento de mi clase en esta estrategia. Mi instinto es que solo debería cambiar mis exámenes si las expectativas de mi clase han cambiado.
Hay una gran publicación del tío Bob, que habla exactamente sobre este problema cuando se usa TDD.
Creo que la tendencia a probar cada clase por separado es lo que está matando a TDD. Toda la belleza de TDD es que usa pruebas para estimular esquemas de diseño y no al revés.
fuente