Ahora estoy aprendiendo TDD. Entiendo que los métodos privados no son verificables y no deberían preocuparse porque la API pública proporcionará suficiente información para verificar la integridad de un objeto.
He entendido OOP por un tiempo. Entiendo que los métodos privados hacen que los objetos estén más encapsulados, por lo tanto, más resistentes al cambio y a los errores. Por lo tanto, deben usarse por defecto y solo aquellos métodos que son importantes para los clientes deben hacerse públicos.
Bueno, es posible para mí hacer un objeto que solo tenga métodos privados e interactúe con otros objetos escuchando sus eventos. Esto sería muy encapsulado, pero completamente inestable.
Además, se considera una mala práctica agregar métodos en aras de las pruebas.
¿Esto significa que TDD está en desacuerdo con la encapsulación? ¿Cuál es el equilibrio apropiado? Estoy inclinado a hacer públicos la mayoría o todos mis métodos ahora ...
fuente
Respuestas:
Prefiere probar a la interfaz a probar en la implementación.
Esto depende de su entorno de desarrollo, consulte a continuación.
Así es, TDD se enfoca en probar la interfaz.
Los métodos privados son un detalle de implementación que podría cambiar durante cualquier ciclo de refactorización. Debería ser posible re-factorizar sin cambiar la interfaz o el comportamiento de la caja negra . De hecho, eso es parte del beneficio de TDD, la facilidad con la que puede generar la confianza de que los cambios internos en una clase no afectarán a los usuarios de esa clase.
Incluso si la clase no tiene métodos públicos, sus controladores de eventos son su interfaz pública , y está en contra de esa interfaz pública que puede probar.
Dado que los eventos son la interfaz, son los eventos que necesitará generar para probar ese objeto.
Considera el uso de objetos simulados como pegamento para tu sistema de prueba. Debería ser posible crear un objeto simulado simple que genere un evento y recoja el cambio de estado resultante (posible por otro objeto simulado receptor).
Absolutamente, debe tener mucho cuidado con exponer el estado interno.
Absolutamente no.
TDD no debería cambiar la implementación de sus clases más que quizás simplificarlas (aplicando YAGNI desde un punto anterior).
La mejor práctica con TDD es idéntica a la mejor práctica sin TDD, solo averigua por qué antes, porque está utilizando la interfaz a medida que la desarrolla.
Esto sería más bien tirar al bebé con el agua del baño.
No debería necesitar hacer públicos todos los métodos para poder desarrollarse de forma TDD. Vea mis notas a continuación para ver si sus métodos privados realmente no son verificables.
Una mirada más detallada a la prueba de métodos privados.
Si absolutamente debe probar un poco el comportamiento privado de una clase, dependiendo del idioma / entorno, puede tener tres opciones:
Obviamente, la tercera opción es, con mucho, la mejor.
1) Ponga las pruebas en la clase que desea probar (no ideal)
Almacenar casos de prueba en el mismo archivo de clase / fuente que el código de producción bajo prueba es la opción más simple. Pero sin una gran cantidad de directivas o anotaciones previas al procesador, terminará con su código de prueba inflando su código de producción innecesariamente, y dependiendo de cómo haya estructurado su código, puede terminar exponiendo accidentalmente la implementación interna a los usuarios de ese código.
2) Exponga los métodos privados que desea probar como métodos públicos (realmente no es una buena idea)
Como se sugirió, esta es una práctica muy pobre, destruye la encapsulación y expondrá la implementación interna a los usuarios del código.
3) Utilice un mejor entorno de prueba (la mejor opción, si está disponible)
En el mundo Eclipse, 3. se puede lograr mediante el uso de fragmentos . En el mundo C #, podríamos usar clases parciales . Otros idiomas / entornos a menudo tienen una funcionalidad similar, solo necesita encontrarla.
Asumiendo ciegamente que 1. o 2. son las únicas opciones, es probable que el software de producción esté repleto de código de prueba o interfaces de clase desagradables que lavan su ropa sucia en público. * 8 ')
fuente
Por supuesto, puede tener métodos privados y, por supuesto, puede probarlos.
O hay alguna forma de hacer que el método privado se ejecute, en cuyo caso puede probarlo de esa manera, o no hay forma de hacer que el privado se ejecute, en cuyo caso: ¿por qué diablos está tratando de probarlo? eliminar la maldita cosa!
En tu ejemplo:
¿Por qué eso sería inestable? Si se invoca el método en reacción a un evento, solo haga que la prueba alimente al objeto con un evento apropiado.
No se trata de no tener métodos privados, se trata de no romper la encapsulación. Puede tener métodos privados, pero debe probarlos a través de la API pública. Si la API pública se basa en eventos, use eventos.
Para el caso más común de los métodos de ayuda privados, se pueden probar a través de los métodos públicos que los llaman. En particular, dado que solo se le permite escribir código para que una prueba que no pasa y sus pruebas estén probando la API pública, todo el código nuevo que escriba generalmente será público. Los métodos privados solo aparecen como resultado de una Refactorización de métodos de extracción , cuando se extraen de un método público ya existente. Pero en ese caso, la prueba original que prueba el método público también cubre el método privado, ya que el método público llama al método privado.
Por lo tanto, por lo general, los métodos privados solo aparecen cuando se extraen de métodos públicos ya probados y, por lo tanto, también se prueban.
fuente
internal
métodos o métodos públicos en lasinternal
clases deben probarse directamente con bastante frecuencia. Afortunadamente, .net admiteInternalsVisibleToAttribute
, pero sin él, probar esos métodos sería un PITA.Cuando crea una nueva clase en su código, lo hace para responder algunos requisitos. Los requisitos dicen qué debe hacer el código, no cómo . Esto facilita la comprensión de por qué la mayoría de las pruebas se realizan a nivel de métodos públicos.
A través de pruebas, verificamos que el código hace lo que se espera que haga , arroja excepciones apropiadas cuando se espera, etc. Realmente no nos importa cómo el desarrollador implementa el código. Si bien no nos importa la implementación, es decir, cómo el código hace lo que hace, tiene sentido evitar probar métodos privados.
En cuanto a las clases de prueba que no tienen ningún método público e interactúan con el mundo exterior solo a través de eventos, también puede probar esto enviando, a través de pruebas, los eventos y escuchando la respuesta. Por ejemplo, si una clase debe guardar un archivo de registro cada vez que recibe un evento, la prueba unitaria enviará el evento y verificará que el archivo de registro esté escrito.
Por último, pero no menos importante, en algunos casos es perfectamente válido probar métodos privados. Es por eso que, por ejemplo, en .NET, puede probar no solo las clases públicas, sino también las privadas, incluso si la solución no es tan sencilla como para los métodos públicos.
fuente
No estoy de acuerdo con esa declaración, o diría que no prueba los métodos privados directamente . Un método público puede llamar diferentes métodos privados. Quizás el autor quería tener métodos "pequeños" y extrajo parte del código en un método privado ingeniosamente nombrado.
Independientemente de cómo se escriba el método público, su código de prueba debe cubrir todas las rutas. Si después de sus pruebas descubre que una de las instrucciones de la rama (if / switch) en un método privado nunca se ha cubierto en sus pruebas, entonces tiene un problema. O te perdiste un caso y la implementación es correcta O la implementación es incorrecta, y esa rama nunca debería haber existido de hecho.
Es por eso que uso mucho Cobertura y NCover, para asegurarme de que mi prueba de método público también cubra métodos privados. Siéntase libre de escribir buenos objetos OO con métodos privados y no deje que TDD / Testing se interponga en su camino en este asunto.
fuente
Su ejemplo sigue siendo perfectamente comprobable siempre que use la inyección de dependencia para proporcionar las instancias con las que interactúa su CUT. Luego puede usar un simulacro, generar los eventos de interés y luego observar si el CUT toma o no las acciones correctas en sus dependencias.
Por otro lado, si tiene un lenguaje con buen soporte de eventos, puede tomar un camino ligeramente diferente. No me gusta cuando los objetos se suscriben a eventos en sí mismos, en su lugar tengo la fábrica que crea el objeto conectando eventos a los métodos públicos del objeto. Es más fácil de probar y lo hace visible externamente para qué tipos de eventos se debe probar el CUT.
fuente
No debería necesitar abandonar utilizando métodos privados. Es perfectamente razonable usarlos, pero desde una perspectiva de prueba son más difíciles de probar directamente sin romper la encapsulación o agregar código específico de prueba a sus clases. El truco es minimizar las cosas que sabes que harán que tu intestino se retuerza porque sientes que has ensuciado tu código.
Estas son las cosas que tengo en mente para tratar de lograr un equilibrio viable.
Piensa lateralmente. Mantenga sus clases pequeñas y sus métodos más pequeños, y use mucha composición. Parece más trabajo, pero al final terminarás con más elementos que se pueden probar individualmente, tus pruebas serán más simples, tendrás más opciones para usar simulacros simples en lugar de objetos reales, grandes y complejos, con suerte bien- código factorizado y poco acoplado, y lo más importante, te darás más opciones. Mantener las cosas pequeñas tiende a ahorrarle tiempo al final, porque reduce la cantidad de cosas que necesita verificar individualmente en cada clase, y tiende a reducir naturalmente el espagueti de código que a veces puede suceder cuando una clase se hace grande y tiene muchos comportamiento de código interdependiente internamente.
fuente
¿Cómo reacciona este objeto a esos eventos? Presumiblemente, debe invocar métodos en otros objetos. Puede probarlo comprobando si se llama a esos métodos. Haga que llame a un objeto simulado y luego puede afirmar fácilmente que hace lo que espera.
El problema es que solo queremos probar la interacción del objeto con otros objetos. No nos importa lo que sucede dentro de un objeto. Entonces no, no deberías tener más métodos públicos que antes.
fuente
También he luchado con este mismo problema. Realmente, la forma de evitarlo es la siguiente: ¿cómo espera que el resto de su programa interactúe con esa clase? Pon a prueba tu clase en consecuencia. Esto lo obligará a diseñar su clase en función de cómo el resto del programa interactúa con él y, de hecho, fomentará la encapsulación y el buen diseño de su clase.
fuente
En lugar del modificador predeterminado de uso privado. Luego puede probar esos métodos individualmente, no solo junto con los métodos públicos. Esto requiere que sus pruebas tengan la misma estructura de paquete que su código principal.
fuente
internal
en .net.Algunos métodos privados generalmente no son un problema. Simplemente los prueba a través de la API pública como si el código estuviera integrado en sus métodos públicos. Un exceso de métodos privados puede ser un signo de mala cohesión. Su clase debe tener una responsabilidad cohesiva, y a menudo las personas hacen que los métodos sean privados para dar la apariencia de cohesión donde realmente no existe ninguno.
Por ejemplo, es posible que tenga un controlador de eventos que realice muchas llamadas a la base de datos en respuesta a esos eventos. Dado que obviamente es una mala práctica crear instancias de un controlador de eventos para realizar llamadas a la base de datos, la tentación es hacer que todas las llamadas relacionadas con la base de datos sean métodos privados, cuando realmente deberían retirarse a una clase separada.
fuente
TDD no está reñido con la encapsulación. Tome el ejemplo más simple de un método o propiedad getter, según el idioma que elija. Digamos que tengo un objeto Cliente y quiero que tenga un campo Id. La primera prueba que voy a escribir es una que dice algo así como "customer_id_initializes_to_zero". Defino el getter para lanzar una excepción no implementada y veo que la prueba falla. Entonces, lo más simple que puedo hacer para hacer que la prueba pase es hacer que el getter regrese a cero.
A partir de ahí, paso a otras pruebas, presumiblemente las que involucran que la identificación del cliente sea un campo funcional real. En algún momento, probablemente tenga que crear un campo privado que la clase de cliente utilice para realizar un seguimiento de lo que debe devolver el captador. ¿Cómo hago un seguimiento exacto de esto? ¿Es un simple respaldo int? ¿Realizo un seguimiento de una cadena y luego la convierto en int? ¿Realizo un seguimiento de 20 entradas y las promedio? Al mundo exterior no le importa, y a sus pruebas de TDD no les importa. Ese es un detalle encapsulado .
Creo que esto no siempre es obvio de inmediato cuando se inicia TDD: no está probando qué métodos hacen internamente, está probando preocupaciones menos granulares de la clase. Por lo tanto, no está buscando probar que el método
DoSomethingToFoo()
crea una instancia de una barra, invoca un método en él, agrega dos a una de sus propiedades, etc. Está probando que después de mutar el estado de su objeto, algún acceso de estado ha cambiado (o no). Ese es el patrón general de sus pruebas: "cuando hago X a mi clase bajo prueba, puedo observar Y posteriormente". La forma en que llega a Y no es asunto de las pruebas, y esto es lo que está encapsulado y es por eso que TDD no está reñido con la encapsulación.fuente
¿Evitar el uso de? No. ¿
Evitar comenzar con ? Si.
Noté que no preguntaste si está bien tener clases abstractas con TDD; Si comprende cómo surgen las clases abstractas durante TDD, el mismo principio se aplica también a los métodos privados.
No puede probar directamente los métodos en clases abstractas como no puede probar directamente los métodos privados, pero es por eso que no comienza con clases abstractas y métodos privados; comienza con clases concretas y API públicas, y luego refactoriza la funcionalidad común a medida que avanza.
fuente