¿Debo evitar métodos privados si realizo TDD?

101

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 ...

cachorro
fuente
99
La mala práctica y la realidad en la industria del software son animales diferentes. Una situación ideal suele ser una realidad distorsionada en el mundo de los negocios. Haz lo que tenga sentido y cúmplelo durante toda la aplicación. Prefiero tener una mala práctica en lugar de que el sabor del mes se extienda por la aplicación.
Aaron McIver
10
¿"los métodos privados no son verificables"? ¿Cual idioma? En algunos idiomas es inconveniente. En otros idiomas es perfectamente simple. Además, ¿está diciendo que el principio de diseño de la encapsulación siempre debe implementarse con muchos métodos privados? Eso parece un poco extremo. Algunos idiomas no tienen métodos privados, sin embargo, todavía parecen tener diseños muy bien encapsulados.
S.Lott
"Tengo entendido 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". Esto me parece un punto de vista contrario de lo que TDD está tratando de lograr. TDD es una metodología de desarrollo que lo lleva a crear un diseño simple, viable y abierto a los cambios. Mirar "desde privado" y "solo publicitar ..." cambia completamente. Olvídese de que existe un método privado para adoptar TDD. Más tarde, hazlos según sea necesario; como parte de la refactorización.
herby
Posible duplicado de Prueba de métodos privados como protegido
mosquito
Entonces, @gnat, ¿crees que esto debería cerrarse como un duplicado de la pregunta que surgió de mi respuesta a esta pregunta? * 8 ')
Mark Booth

Respuestas:

50

Prefiere probar a la interfaz a probar en la implementación.

Tengo entendido que los métodos privados no son verificables

Esto depende de su entorno de desarrollo, consulte a continuación.

[los métodos privados] no deberían preocuparse porque la API pública proporcionará suficiente información para verificar la integridad de un objeto.

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.

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.

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).

Además, se considera una mala práctica agregar métodos en aras de las pruebas.

Absolutamente, debe tener mucho cuidado con exponer el estado interno.

¿Esto significa que TDD está en desacuerdo con la encapsulación? ¿Cuál es el equilibrio apropiado?

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.

Estoy inclinado a hacer públicos la mayoría o todos mis métodos ahora ...

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:

  1. Ponga las pruebas en la clase que desea evaluar.
  2. Coloque las pruebas en otra clase / archivo fuente y exponga los métodos privados que desea probar como métodos públicos.
  3. Use un entorno de prueba que le permita mantener el código de prueba y de producción por separado, y aún así permitir el acceso del código de prueba a métodos privados del código de producción.

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 ')

  • En general, es mucho mejor no probar contra la implementación privada.
Mark Booth
fuente
55
No estoy seguro de estar de acuerdo con ninguna de las tres opciones que sugiere. Preferiría probar solo la interfaz pública como dijiste antes, pero asegurarme de que al hacerlo, se ejerzan métodos privados. Parte de la ventaja de hacerlo es encontrar un código muerto, lo que es poco probable que ocurra si obliga a su código de prueba a romper el uso normal del lenguaje.
Magus
Sus métodos deberían hacer una cosa, y una prueba no debería considerar la implementación de ninguna manera. Los métodos privados son detalles de implementación. Si probar solo métodos públicos significa que sus pruebas son pruebas de integración, tiene un problema de diseño.
Magus
¿Qué pasa si el método está predeterminado / protegido y se crea una prueba en un proyecto de prueba con el mismo paquete?
RichardCypher
@RichardCypher Eso es efectivamente lo mismo que 2) ya que está alterando las especificaciones de su método idealmente para acomodar una deficiencia en su entorno de prueba, por lo que definitivamente sigue siendo una mala práctica.
Mark Booth
75

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:

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.

¿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.

Jörg W Mittag
fuente
3
Las pruebas a través de métodos públicos funcionan bien el 99% del tiempo. El desafío es el 1% del tiempo cuando su método público único tiene varios cientos o miles de líneas de código complejo detrás de él y todos los estados intermedios son específicos de la implementación. Una vez que se vuelve lo suficientemente complejo, tratar de alcanzar todos los casos extremos del método público se vuelve doloroso en el mejor de los casos. Alternativamente, probar los casos límite rompiendo la encapsulación y exponiendo más métodos como privados, o usando un kludge para que las pruebas llamen a métodos privados directamente da como resultado casos de prueba frágiles además de ser feos.
Dan Neely
24
Los métodos privados grandes y complejos son un olor a código. Una implementación que es tan compleja que no puede descomponerse de manera útil en partes componentes (con interfaces públicas) es un problema en la capacidad de prueba que revela posibles problemas arquitectónicos y de diseño. El 1% de los casos en los que el código privado es enorme generalmente se beneficiará del reprocesamiento para descomponerse y exponerse.
S.Lott
13
El código de @Dan Neely como ese es bastante indetectable independientemente, y parte de las pruebas unitarias de escritura lo señala. Elimine estados, divida las clases, aplique todas las refactorizaciones típicas y luego escriba pruebas unitarias. También con TDD, ¿cómo llegaste a ese punto? Esta es una de las ventajas de TDD, escribir código comprobable se vuelve automático.
Bill K
Al menos los internalmétodos o métodos públicos en las internalclases deben probarse directamente con bastante frecuencia. Afortunadamente, .net admite InternalsVisibleToAttribute, pero sin él, probar esos métodos sería un PITA.
CodesInChaos
25

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.

Arseni Mourzenko
fuente
44
+1 Una característica importante de TDD es que te obliga a probar que se cumplen los REQUISITOS, en lugar de probar que los MÉTODOS hacen lo que creen que haces. Entonces, la pregunta "¿Puedo probar un método privado" es un poco contrario al espíritu de TDD: la pregunta podría ser "¿Puedo probar un requisito cuya implementación incluye métodos privados". Y la respuesta a esta pregunta es claramente sí.
Dawood ibn Kareem
6

Tengo entendido que los métodos privados no son verificables

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.

Jalayn
fuente
5

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.

Chris Pitman
fuente
Esa es una gran idea: "... hacer que la fábrica que crea el objeto conecte eventos a los métodos públicos del objeto. Es más fácil de probar y hace que sea visible externamente para qué tipos de eventos se debe probar el CUT. "
cachorro
5

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.

  1. Minimice la cantidad de métodos y propiedades privadas que usa. La mayoría de las cosas que necesita que su clase haga tienden a exponerse públicamente de todos modos, así que piense si realmente necesita hacer que ese método inteligente sea privado.
  2. Minimice la cantidad de código en sus métodos privados (realmente debería estar haciendo esto de todos modos) y pruebe indirectamente donde pueda a través del comportamiento de otros métodos. Nunca espere obtener una cobertura de prueba del 100%, y tal vez deba verificar manualmente algunos valores a través del depurador. El uso de métodos privados para lanzar Excepciones se puede probar fácilmente indirectamente. Es posible que las propiedades privadas deban probarse manualmente o mediante otro método.
  3. Si la comprobación indirecta o manual no le sienta bien, agregue un evento protegido y acceda a través de una interfaz para exponer algunas de las cosas privadas. Esto efectivamente "dobla" las reglas de encapsulación, pero evita la necesidad de enviar código que ejecute sus pruebas. El inconveniente es que esto puede generar un pequeño código interno adicional para asegurarse de que el evento se active cuando sea necesario.
  4. Si cree que un método público no es lo suficientemente "seguro", vea si hay formas de implementar algún tipo de proceso de validación en sus métodos para limitar cómo se usan. Lo más probable es que mientras piensa en esto piense en una mejor manera de implementar sus métodos o verá que otra clase comienza a tomar forma.
  5. Si tiene muchos métodos privados haciendo "cosas" para sus métodos públicos, puede haber una nueva clase esperando ser extraída. Puede probar esto directamente como una clase separada, pero implementarlo como un compuesto de forma privada dentro de la clase que lo utiliza.

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.

S.Robins
fuente
4

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.

¿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.

Winston Ewert
fuente
4

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.

Oportunidad
fuente
3

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.

siamii
fuente
... suponiendo que esto sea Java.
Dawood ibn Kareem
o internalen .net.
CodesInChaos
2

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.

Karl Bielefeldt
fuente
2

¿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.

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.

Erik Dietrich
fuente
2

¿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