A veces los objetos solo necesitan estar estrechamente acoplados. Por ejemplo, una CsvFile
clase probablemente necesitará trabajar estrechamente con la CsvRecord
clase (o ICsvRecord
interfaz).
Sin embargo, de lo que aprendí en el pasado, uno de los principios principales del desarrollo basado en pruebas es "Nunca pruebe más de una clase a la vez". Lo que significa que debe usar ICsvRecord
simulacros o trozos en lugar de instancias reales de CsvRecord
.
Sin embargo, después de probar este enfoque, noté que burlarse de la CsvRecord
clase puede ser un poco difícil. Lo que me lleva a una de dos conclusiones:
- ¡Es difícil escribir pruebas unitarias! ¡Eso es un olor a código! Refactor!
- Burlarse de cada dependencia es irracional.
Cuando reemplacé mis simulacros con CsvRecord
instancias reales , las cosas salieron mucho mejor. Cuando busqué los pensamientos de otras personas, me encontré con esta publicación de blog , que parece respaldar el n. ° 2 anterior. Para los objetos que están naturalmente estrechamente unidos, no debemos preocuparnos tanto por burlarse.
¿Estoy fuera de lugar? ¿Hay alguna desventaja en el supuesto # 2 anterior? ¿Debería estar pensando en refactorizar mi diseño?
Respuestas:
Si realmente necesita coordinación entre esas dos clases, escriba una
CsvCoordinator
clase que encapsule sus dos clases y pruébelo.Sin embargo, disputo la noción que
CsvRecord
no se puede comprobar de forma independiente.CsvRecord
es básicamente una clase DTO , ¿no es así? Es solo una colección de campos, con quizás un par de métodos auxiliares. YCsvRecord
se puede usar en otros contextos ademásCsvFile
; puede tener una colección o conjunto deCsvRecord
s, por ejemplo.Prueba
CsvRecord
primero. Asegúrese de que pase todas sus pruebas. Luego, adelante y utilíceloCsvRecord
con suCsvFile
clase durante el examen. Úselo como un trozo / prueba simulada; llénelo con datos de prueba relevantes, páseloCsvFile
y escriba sus casos de prueba en contra de eso.fuente
CsvRecord
rompe, entonces obviamenteCsvData
falla; pero esto está bien, porque pruebaCsvRecord
primero, y si eso falla, susCsvFile
pruebas no tienen sentido. Todavía puede distinguir entre errores enCsvRecord
y enCsvFile
.La razón para probar una clase a la vez es que no desea que las pruebas para una clase tengan dependencias en el comportamiento de una segunda clase. Eso significa que si su prueba para la Clase A ejerce alguna de las funciones de la Clase B, entonces debe burlarse de la Clase B para eliminar la dependencia de una funcionalidad particular dentro de la Clase B.
Una clase como
CsvRecord
me parece que es principalmente para almacenar datos, no es una clase con demasiada funcionalidad propia. Es decir, puede tener constructores, captadores, establecedores, pero no métodos con lógica sustancial real. Por supuesto, supongo que aquí, tal vez hayas escrito una clase llamadaCsvRecord
que hace numerosos cálculos complejos.Pero si
CsvRecord
no tiene una lógica propia, no se gana nada burlándose de ella. Esto es realmente la vieja máxima: "no te burles de los objetos de valor" .Entonces, al considerar si se burlará de una clase en particular (para una prueba de una clase diferente), debe tener en cuenta cuánta lógica tiene esa clase y cuánta lógica se ejecutará en el transcurso de su prueba.
fuente
No. # 2 está bien. Las cosas pueden y deben estar estrechamente acopladas si sus conceptos están estrechamente acoplados. Esto debería ser raro y generalmente evitado, pero en el ejemplo que proporcionó tiene sentido.
fuente
Las clases "acopladas" dependen mutuamente entre sí. Este no debería ser el caso en lo que está describiendo: un CsvRecord realmente no debería preocuparse por el CsvFile que lo contiene, por lo que la dependencia solo va en un sentido. Eso está bien, y no es un acoplamiento apretado.
Después de todo, si una clase contiene el nombre de cadena variable, no diría que está estrechamente acoplado a la cadena, ¿verdad?
Entonces, la unidad prueba el CsvRecord para ver su comportamiento deseado.
Luego use un marco de imitación (Mockito es genial) para probar si su unidad está interactuando con los objetos de los que depende correctamente. El comportamiento que desea probar, realmente, es que el CsvFile maneja los CsvRcords de la manera esperada. El funcionamiento interno de CvsRecord no debería importar: es cómo CvsFile se comunica con él.
Finalmente, TDD no se trata solo de pruebas unitarias. Ciertamente, puede (y debe) comenzar con pruebas funcionales que analicen el comportamiento funcional de cómo funcionan sus componentes más grandes, es decir, su historia o escenario de usuario. Sus pruebas de unidades establecen las expectativas y verifican las piezas, las pruebas funcionales hacen lo mismo para el conjunto.
fuente
CsvFile
está estrechamente acoplado aCsvRecord
(pero no al revés). El OP pregunta si es una buena idea probarCsvFile
al desacoplarlo aCsvRecord
través de unICsvRecord
, no viceversa.CsvFile
depende del funcionamiento interno deCsvRecord
, es decir, la cantidad de suposiciones que tiene el archivo sobre el registro. Las interfaces ayudan a documentar y hacer cumplir tales supuestos (o más bien la ausencia de otros supuestos), pero la cantidad de acoplamiento sigue siendo la misma, excepto que con una interfaz, puede conectar una clase de registro diferenteCsvFile
. Presentar la interfaz solo para que pueda decir que tiene un acoplamiento reducido es una tontería.Realmente hay dos preguntas aquí. La primera es si existen situaciones en las que no es aconsejable burlarse de un objeto. Eso es indudablemente cierto, como lo demuestran las otras excelentes respuestas. La segunda pregunta es si su caso particular es una de esas situaciones. Sobre esa cuestión no estoy convencido.
Probablemente la razón más común para no burlarse de una clase es si es una clase de valor. Sin embargo, debes mirar la razón detrás de la regla. No es porque la clase burlada sea mala de alguna manera, es porque será esencialmente idéntica a la original. Si ese fuera el caso, la prueba de su unidad no sería más fácil usando la clase original.
Es muy posible que su código sea una de las raras excepciones en las que la refactorización no ayudaría, pero solo debe declararlo después de que los esfuerzos diligentes de refactorización no hayan funcionado. Incluso los desarrolladores experimentados pueden tener problemas para ver alternativas a su propio diseño. Si no puede pensar en alguna forma posible de mejorarlo, pídale a alguien experimentado que lo revise por segunda vez.
La mayoría de la gente parece estar asumiendo que su
CsvRecord
es una clase de valor. Intenta hacerlo uno. Hazlo inmutable si puedes. Si tiene dos objetos con punteros entre sí, elimine uno de ellos y descubra cómo hacerlo funcionar. Busque lugares para dividir clases y funciones. Es posible que el mejor lugar para dividir una clase no siempre coincida con el diseño físico del archivo. Intente invertir la relación padre / hijo de las clases. Tal vez necesite una clase separada para leer y escribir archivos csv. Quizás necesite clases separadas para manejar el archivo de E / S y la interfaz para las capas superiores. Hay muchas cosas que probar antes de declarar que no es refactible.fuente