¿Debo evitar los métodos privados si realizo TDD?

91

Ahora estoy aprendiendo TDD. Tengo entendido 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. Tengo entendido que los métodos privados hacen que los objetos estén más encapsulados, por lo tanto, más resistentes a los cambios y errores. Por lo tanto, deben usarse de forma predeterminada y solo deben publicarse los métodos que son importantes para los clientes.

Bueno, es posible para mí crear un objeto que solo tenga métodos privados e interactúe con otros objetos escuchando sus eventos. Esto sería muy encapsulado, pero completamente imposible de probar.

Además, se considera una mala práctica agregar métodos para realizar pruebas.

¿Esto significa que TDD está en desacuerdo con la encapsulación? ¿Cuál es el balance apropiado? Ahora me inclino a hacer públicos la mayoría o todos mis métodos ...

    
pregunta pup 14.02.2012 - 16:58
fuente

12 respuestas

42

Prefiere las pruebas a la interfaz en lugar de las pruebas en la implementación.

  

Tengo entendido que los métodos privados no se pueden probar

Esto depende de su entorno de desarrollo, vea más abajo.

  

[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 re-factor. Debería ser posible modificar el factor sin cambiar la interfaz o el comportamiento de 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í crear un objeto que solo tenga métodos privados e interactúe con otros objetos escuchando sus eventos. Esto sería muy encapsulado, pero completamente imposible de probar.

Incluso si la clase no tiene métodos públicos, sus controladores de eventos son interfaz pública , y es contra que interfaz pública puedo probar.

Dado que los eventos son la interfaz, son los eventos que necesitarás generar para probar ese objeto.

Considere utilizar objetos simulados como pegamento para su 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 del receptor).

  

Además, se considera una mala práctica agregar métodos para realizar pruebas.

Absolutamente, debes ser muy cauteloso al exponer el estado interno.

  

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

Absolutamente no.

TDD no debería cambiar la implementación de sus clases para no simplificarlas (aplicando YAGNI desde un punto anterior).

La mejor práctica con TDD es idéntica a la mejor práctica sin TDD, simplemente descubra por qué antes, porque está utilizando la interfaz a medida que la desarrolla.

  

Me inclino a hacer que la mayoría o todos mis métodos sean públicos ahora ...

Esto sería más bien arrojar al bebé con el agua del baño.

No deberías necesitar hacer públicos todos los métodos para que puedas desarrollarlos de forma TDD. Vea mis notas a continuación para ver si sus métodos privados realmente no se pueden probar.

Una mirada más detallada sobre la prueba de métodos privados

Si absolutamente debe la unidad prueba algunos comportamientos privados de una clase, dependiendo del idioma / entorno, puede tener tres opciones:

  1. Coloque las pruebas en la clase que desea evaluar.
  2. Coloque las pruebas en otra clase / archivo fuente & exponga los métodos privados que desea probar como métodos públicos.
  3. Use un entorno de prueba que le permita mantener los códigos de prueba y producción separados, y al mismo tiempo permitir el acceso de 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) Coloque las pruebas en la clase que desea probar (no ideal)

El almacenamiento de casos de prueba en la misma clase / archivo 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 de preprocesador, terminará con el código de prueba hinchando 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. / p>

2) Exponga los métodos privados que desea probar como métodos públicos (en realidad no es una buena idea)

Como se sugiere, esta es una práctica muy deficiente, 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 de Eclipse, 3. se puede lograr utilizando fragmentos . En el mundo C #, podríamos usar clases parciales . Otros idiomas / entornos a menudo tienen una funcionalidad similar, solo necesitas encontrarla.

Suponiendo que 1. o 2. son las únicas opciones que podrían resultar en un software de producción inflado con código de prueba o interfaces de clase desagradables que lavan su ropa sucia en público. * 8 ')

  • Sin embargo, en general, es mucho mejor no probar la implementación privada.
respondido por el Mark Booth 14.02.2012 - 18:28
fuente
74

Por supuesto, puedes tener métodos privados y, por supuesto, puedes probarlos.

O bien hay alguna forma de ejecutar el método privado, en cuyo caso puede probarlo de esa manera, o existe no para obtener la privada corre, en cuyo caso: ¿por qué diablos estás tratando de probarlo, simplemente borra la maldita cosa?

En tu ejemplo:

  

Bueno, es posible para mí crear un objeto que solo tenga métodos privados e interactúe con otros objetos escuchando sus eventos. Esto sería muy encapsulado, pero completamente imposible de probar.

¿Por qué sería imposible de probar? Si se invoca el método como reacción a un evento, simplemente 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, entonces use eventos.

Para el caso más común de los métodos de ayuda privada, 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 obtener una prueba fallida 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 un Extract Method Refactoring , cuando se sacan 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, generalmente los métodos privados solo aparecen cuando se extraen de métodos públicos ya probados y, por lo tanto, también se prueban.

    
respondido por el Jörg W Mittag 14.02.2012 - 17:19
fuente
24

Cuando creas una nueva clase en tu código, lo haces para responder a algunos requisitos. Los requisitos dicen qué debe hacer el código, no cómo . Esto hace que sea fácil entender 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 las excepciones apropiadas cuando se espera, etc. No nos importa realmente cómo implementa el código el desarrollador. 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, puede probar esto también 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 de la unidad 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.

    
respondido por el Arseni Mourzenko 14.02.2012 - 17:27
fuente
5
  

Tengo entendido que los métodos privados no se pueden probar

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 a diferentes métodos privados. Tal vez el autor quería tener métodos "pequeños" y extrajo parte del código en un método privado inteligentemente llamado.

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 declaraciones de rama (if / switch) en un método privado nunca se ha cubierto en sus pruebas, entonces tiene un problema. O se perdió 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 Cobertura y NCover mucho, 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.

    
respondido por el Jalayn 14.02.2012 - 17:20
fuente
5

Su ejemplo aún se puede probar perfectamente siempre que use 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 la CUT realiza o no las acciones correctas en sus dependencias.

Por otra parte, si tiene un lenguaje con buena compatibilidad con eventos, puede tomar un camino ligeramente diferente. No me gusta cuando los objetos se suscriben a los eventos en sí mismos, en lugar de eso, la fábrica que crea el objeto conecta los eventos a los métodos públicos del objeto. Es más fácil de probar, y lo hace visible externamente qué tipo de eventos necesita la prueba CUT.

    
respondido por el Chris Pitman 14.02.2012 - 17:23
fuente
5

No deberías tener que abandonar el uso de 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 lo que sabes que hará que tu estómago se retuerza porque sientes que has ensuciado tu código.

Estas son las cosas que tengo en cuenta para intentar lograr un equilibrio viable.

  1. Minimice la cantidad de métodos privados y propiedades que usa. La mayoría de las cosas que necesita hacer para su clase tienden a estar expuestas públicamente de todos modos, así que piense si realmente necesita hacer privado ese método inteligente.
  2. Minimice la cantidad de código en sus métodos privados (debería hacerlo de todos modos) y realice pruebas indirectas donde pueda a través del comportamiento de otros métodos. Nunca esperas obtener una cobertura de prueba del 100%, y quizás necesites revisar algunos valores a través del depurador. El uso de métodos privados para lanzar Excepciones puede ser fácilmente probado indirectamente. Es posible que las propiedades privadas deban probarse manualmente o mediante otro método.
  3. Si la verificació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 realmente el código que ejecuta sus pruebas. El inconveniente es que esto puede resultar en un pequeño código interno adicional para asegurarse de que el evento se activará 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 la forma en que 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 que hacen "cosas" para sus métodos públicos, puede haber una nueva clase a la espera de ser extraída. Puede probar esto directamente como una clase separada, pero implementarlo como un compuesto privado dentro de la clase que lo usa.

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, ojalá estén bien. código factorizado y débilmente acoplado, y lo que es más importante, le dará más opciones a usted mismo. Mantener las cosas pequeñas tiende a ahorrarle tiempo al final, porque reduce la cantidad de cosas que necesita revisar individualmente en cada clase, y tiende a reducir naturalmente el código de los espaguetis que a veces pueden suceder cuando una clase es grande y tiene muchos comportamiento de código interdependiente internamente.

    
respondido por el S.Robins 14.02.2012 - 23:16
fuente
3
  

Bueno, es posible que yo haga un objeto que solo tenga privado   Métodos e interactúa con otros objetos escuchando sus eventos.   Esto sería muy encapsulado, pero completamente imposible de probar.

¿Cómo reacciona este objeto ante esos eventos? Presumiblemente, debe invocar métodos en otros objetos. Puedes probarlo verificando si esos métodos son llamados. Haga que llame a un objeto simulado y luego podrá 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 está pasando dentro de un objeto. Así que no, no deberías tener más métodos públicos que antes.

    
respondido por el Winston Ewert 14.02.2012 - 17:21
fuente
3

También he luchado con este mismo problema. Realmente, la forma de solucionarlo es esta: ¿Cómo espera que el resto de su programa se interconecte con esa clase? Pruebe su clase en consecuencia. Esto lo forzará a diseñar su clase según la forma en que el resto del programa se interconecte con él y, de hecho, fomentará la encapsulación y el buen diseño de su clase.

    
respondido por el Chance 14.02.2012 - 19:20
fuente
3

En lugar de uso privado, modificador por defecto. Entonces puede probar esos métodos individualmente, no solo junto con métodos públicos. Esto requiere que sus pruebas tengan la misma estructura de paquetes que su código principal.

    
respondido por el siamii 14.02.2012 - 20:32
fuente
2

Algunos métodos privados no suelen ser un problema. Simplemente los prueba a través de la API pública como si el código estuviera dentro de 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.

Por ejemplo, es posible que tenga un controlador de eventos que haga muchas llamadas a la base de datos en respuesta a esos eventos. Como obviamente es una mala práctica instanciar un controlador de eventos para hacer llamadas a la base de datos, la tentación es hacer que todos los métodos de llamadas relacionadas con la base de datos sean privados, cuando en realidad deberían ser retirados a una clase separada.

    
respondido por el Karl Bielefeldt 14.02.2012 - 17:32
fuente
1
  

¿Esto significa que TDD está en desacuerdo con la encapsulación? Cuál es el   balance apropiado? Me inclino a hacer la mayoría o todos mis métodos.   público ahora.

TDD no está reñido con la encapsulación. Tome el ejemplo más simple de un método o propiedad getter, dependiendo de su idioma de elección. Digamos que tengo un objeto Cliente y quiero que tenga un campo de identificación. La primera prueba que voy a escribir es una que dice algo así como "customer_id_initializes_to_zero". Defino al getter para lanzar una excepción no implementada y ver cómo falla la prueba. Entonces, lo más simple que puedo hacer para hacer que la prueba pase es hacer que el receptor devuelva cero.

A partir de ahí, me dirijo a otras pruebas, presumiblemente aquellas que involucran la identificación del cliente como un campo funcional real. En algún momento, es probable que tenga que crear un campo privado que la clase del cliente utilice para realizar un seguimiento de lo que debe devolver el getter. ¿Cómo hago exactamente un seguimiento de esto? ¿Es un int simple respaldo? ¿Hago un seguimiento de una cadena y luego la convierto a int? ¿Mantengo un registro de 20 pulgadas y las promedia? Al mundo exterior no le importa, y a tus pruebas TDD no les importa. Ese es un detalle encapsulado .

Creo que esto no siempre es inmediatamente obvio cuando se inicia TDD (no estás probando qué métodos hacen internamente), estás probando las preocupaciones menos detalladas de la clase. Por lo tanto, no está buscando probar que el método DoSomethingToFoo() crea una instancia de Barra, llama a un método, agrega dos a una de sus propiedades, etc. Está probando que después de mutar el estado de su objeto, algún estado El accessor ha cambiado (o no). Ese es el patrón general de sus pruebas: "cuando hago X en mi clase bajo prueba, puedo observar Y posteriormente". La forma en que llega a Y no es ninguna de las preocupaciones de las pruebas, y esto es lo que se encapsula y esta es la razón por la que TDD no está reñida con la encapsulación.

    
respondido por el Erik Dietrich 14.02.2012 - 17:54
fuente
1

¿Evitar usar? No.
¿Evitas comenzar con ? Sí.

Observo que no preguntaste si está bien tener clases abstractas con TDD; Si comprende cómo emergen las clases abstractas durante la TDD, el mismo principio se aplica también a los métodos privados.

No puede probar métodos directamente en clases abstractas, como tampoco puede probar métodos privados directamente, 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.

    
respondido por el user34530 15.02.2012 - 00:44
fuente

Lea otras preguntas en las etiquetas