Cómo evitar la necesidad de probar métodos privados

15

Sé que se supone que no debes probar métodos privados, y si parece que lo necesitas, es posible que haya una clase allí esperando para salir.

Pero, no quiero tener un millón de clases solo para poder probar sus interfaces públicas y encuentro que para muchas clases si solo pruebo los métodos públicos termino teniendo que burlarme de muchas dependencias y las pruebas unitarias son enorme y difícil de seguir.

Prefiero burlarme de los métodos privados cuando pruebo los públicos, y burlarme de las dependencias externas cuando pruebo los privados.

¿Estoy loco?

Fran Sevillano
fuente
Una prueba de unidad exhaustiva debe cubrir implícitamente a todos los miembros privados de una clase determinada, porque si bien no puede llamarlos directamente, su comportamiento seguirá teniendo un efecto en la salida. Si no lo hacen, ¿por qué están allí en primer lugar? Recuerde que en las pruebas unitarias lo que le interesa es el resultado, no cómo se llegó al resultado.
GordonM

Respuestas:

24

Tiene razón en parte: no debe probar directamente métodos privados. Los métodos privados en una clase deben ser invocados por uno o más de los métodos públicos (quizás indirectamente; un método privado llamado por un método público puede invocar otros métodos privados). Por lo tanto, cuando pruebe sus métodos públicos, también probará sus métodos privados. Si tiene métodos privados que no se han probado, sus casos de prueba son insuficientes o los métodos privados no se utilizan y se pueden eliminar.

Si está adoptando un enfoque de prueba de caja blanca, debe considerar los detalles de implementación de sus métodos privados al construir pruebas unitarias en torno a sus métodos públicos. Si está adoptando un enfoque de recuadro negro, no debería probar con los detalles de implementación, ya sea en los métodos públicos o privados, sino en el comportamiento esperado.

Personalmente, prefiero un enfoque de caja blanca para las pruebas unitarias. Puedo elaborar pruebas para poner a prueba los métodos y las clases en diferentes estados que causan un comportamiento interesante en mis métodos públicos y privados y luego afirmar que los resultados son lo que espero.

Entonces, no te burles de tus métodos privados. Úselos para comprender lo que necesita probar para proporcionar una buena cobertura de la funcionalidad que proporciona. Esto es especialmente cierto en el nivel de prueba de la unidad.

Thomas Owens
fuente
1
"no te burles de tus métodos privados" sí, puedo entender las pruebas, cuando te burlas de ellas es posible que hayas cruzado la línea fina hasta la locura
Ewan
Sí, pero como dije, mi problema con las pruebas de caja blanca es que, por lo general, burlarse de todas las dependencias de los métodos públicos y privados hace que los métodos de prueba sean realmente grandes. ¿Alguna idea de cómo abordar eso?
Fran Sevillano
8
@FranSevillano Si tienes que tropezar o burlarte tanto, miraría tu diseño general. Algo se siente mal.
Thomas Owens
Una herramienta de cobertura de código ayuda con esto.
Andy
55
@FranSevillano: No hay muchas buenas razones para que una clase que hace una cosa tenga toneladas de dependencias. Si tienes toneladas de dependencias, probablemente tengas una clase de Dios.
Mooing Duck
4

Creo que esta es una idea muy pobre.

El problema con la creación de pruebas unitarias de miembros privados es que encaja mal con el ciclo de vida del producto.

La razón por la que HAS ELEGIDO hacer que esos métodos sean privados es que no son fundamentales para lo que tus clases intentan hacer, solo ayudantes en cómo implementas actualmente esa funcionalidad. A medida que refactoriza, es probable que esos detalles privados sean candidatos para cambiar y causarán fricción con la refactorización.

Además, una diferencia clave entre los miembros públicos y privados es que debe pensar cuidadosamente su API pública, documentarla bien y verificarla bien (afirmaciones, etc.). Pero con una API privada, no tendría sentido pensar con tanto cuidado (un esfuerzo desperdiciado ya que su uso está muy localizado). Poner métodos privados en pruebas unitarias equivale a crear dependencias externas de esos métodos. Lo que significa que la API debe ser estable y bien documentada (ya que alguien tiene que descubrir por qué esas pruebas unitarias fallaron si / cuando lo hacen).

Te sugiero:

  • REPENSAR si esos métodos deben ser privados
  • Escriba pruebas únicas (solo para asegurarse de que sean correctas ahora, publíquelas temporalmente para que pueda probar y luego elimine la prueba)
  • Desarrolló pruebas condicionales en su implementación utilizando #ifdef y aserciones (las pruebas solo se realizan en compilaciones de depuración).

Apreciamos su inclinación a probar esta funcionalidad y es difícil hacerlo probar a través de su API pública actual. Pero personalmente valoro más la modularidad que la cobertura de prueba.

Lewis Pringle
fuente
66
Estoy en desacuerdo. En mi opinión, esto es 100% correcto para las pruebas de integración. Pero con las pruebas unitarias las cosas son diferentes; El objetivo de las pruebas unitarias es determinar dónde se encuentra un error, lo suficientemente estrecho para que pueda solucionarlo rápidamente. A menudo me encuentro en esta situación: tengo muy pocos métodos públicos porque ese es realmente el valor central de mis clases (como dijiste que uno debería hacer). Sin embargo, también evito escribir métodos de 400 líneas, ni públicas ni privadas. Entonces, mis pocos métodos públicos solo pueden lograr su objetivo con la ayuda de decenas de métodos privados. Eso es demasiado código para "arreglarlo rápidamente". Tengo que iniciar el depurador, etc.
marzo
66
@marstato: sugerencia: comience a escribir pruebas primero y cambie de opinión acerca de las pruebas unitarias: no encuentran errores, pero verifique que el código funcione como el desarrollador lo intentó.
Timothy Truckle
@marstato Gracias! Si. Las pruebas siempre pasan la primera vez, cuando registras cosas (¡o no lo habrías registrado!). Son útiles, a medida que evoluciona el código, le dan un aviso cuando rompe algo, y si tiene BUENAS pruebas de regresión, le brindan COMODIDAD / GUÍA sobre dónde buscar problemas (no en lo que es estable). y prueba de regresión bien).
Lewis Pringle
@marstato "El objetivo de las pruebas unitarias es determinar dónde se encuentra un error": ese es exactamente el malentendido que lleva a la pregunta del OP. El objetivo de las pruebas unitarias es verificar el comportamiento previsto (y preferiblemente documentado) de una API.
StackOverthrow
44
@marstato El nombre "prueba de integración" proviene de probar que varios componentes funcionan juntos (es decir, se integran correctamente). Las pruebas unitarias son pruebas de que un solo componente hace lo que se supone que debe hacer de forma aislada, lo que básicamente significa que su API pública funciona según lo documentado / requerido. Ninguno de esos términos dice nada acerca de si incluye pruebas de lógica de implementación interna como parte de garantizar que la integración funcione, o que la unidad única funcione.
Ben
3

UnitTests prueba el comportamiento público observable , no el código, donde "público" significa: valores de retorno y comunicación con dependencias.

Una "unidad" es cualquier código que resuelve el mismo problema (o más precisamente: tiene la misma razón para cambiar). Ese podría ser un método único o un montón de clases.

La razón principal por la que no desea probar private methodses: son detalles de implementación y es posible que desee cambiarlos durante la refactorización (mejore su código aplicando principios OO sin cambiar la funcionalidad). Esto es exactamente cuando no desea que cambien sus pruebas de unidad para que puedan garantizar que el comportamiento de su CuT no cambió durante la refactorización.

Pero, no quiero tener un millón de clases solo para poder probar sus interfaces públicas y encuentro que para muchas clases si solo pruebo los métodos públicos termino teniendo que burlarme de muchas dependencias y las pruebas unitarias son enorme y difícil de seguir.

Por lo general, experimento lo contrario: cuanto más pequeñas son las clases (menos responsabilidad tienen) menos dependencias tienen y más fáciles son las pruebas unitarias, tanto para escribir como para leer.

Idealmente, aplica el mismo nivel de patrón de abstracción a sus clases. Esto significa que sus clases proporcionan alguna lógica de negocios (preferiblemente como "funciones puras" que trabajan en sus parámetros solo sin mantener un estado propio) (x) o métodos de llamada en otros objetos, no ambos al mismo tiempo.

De esta forma, las pruebas unitarias del comportamiento comercial son pan comido y los objetos de "delegación" generalmente son demasiado simples para fallar (sin ramificación, sin cambio de estado), por lo que no se necesitan pruebas unitarias y sus pruebas pueden dejarse a pruebas de integración o módulo

Timothy Truckle
fuente
1

Las pruebas unitarias tienen cierto valor cuando usted es el único programador que trabaja en el código, especialmente si la aplicación es muy grande o muy compleja. Cuando las pruebas unitarias se vuelven esenciales es cuando tienes un mayor número de programadores trabajando en la misma base de código. El concepto de pruebas unitarias se introdujo para abordar algunas de las dificultades de trabajar en estos equipos más grandes.

La razón por la que las pruebas unitarias ayudan a los equipos más grandes se trata de contratos. Si mi código está haciendo llamadas al código escrito por otra persona, estoy haciendo suposiciones sobre lo que hará el código de la otra persona en diversas situaciones. Siempre que esos supuestos sigan siendo ciertos, mi código seguirá funcionando, pero ¿cómo sé qué supuestos son válidos y cómo sé cuándo han cambiado esos supuestos?

Aquí es donde entran las pruebas unitarias. El autor de una clase crea pruebas unitarias para documentar el comportamiento esperado de su clase. La prueba unitaria define todas las formas válidas de usar la clase, y ejecutar la prueba unitaria valida que estos casos de uso funcionen como se esperaba. Otro programador que quiera hacer uso de su clase puede leer sus pruebas unitarias para comprender el comportamiento que pueden esperar para su clase, y usar esto como base para sus suposiciones sobre cómo funciona su clase.

De esta manera, las firmas de método público de la clase y las pruebas unitarias juntas forman un contrato entre el autor de la clase y los otros programadores que hacen uso de esta clase en su código.

En este escenario, ¿qué sucede si incluye pruebas de métodos privados? Claramente no tiene sentido.

Si usted es el único programador que trabaja en su código y desea usar pruebas unitarias como una forma de depurar su código, entonces no veo ningún daño en eso, es solo una herramienta, y puede usarlo de cualquier manera que funcione usted, pero no fue la razón por la cual se introdujeron las pruebas unitarias, y no proporciona los principales beneficios de las pruebas unitarias.

bikeman868
fuente
Lucho con esto porque me burlo de las dependencias de modo que si alguna dependencia cambia su comportamiento, mi prueba no fallará. ¿Se supone que esta falla ocurre en una prueba de integración, no en la prueba unitaria?
Todd
Se supone que el simulacro se comporta de manera idéntica a la implementación real, pero no tiene dependencias. Si la implementación real cambia para que ahora sean diferentes, esto se mostraría como una falla de prueba de integración. Personalmente, considero que el desarrollo de simulacros y la implementación real se deben realizar como una tarea. No construyo simulacros como parte de las pruebas unitarias de escritura. De esta manera, cuando cambio el comportamiento de mis clases y cambio el simulacro para que coincida, al ejecutar las pruebas unitarias se identificarán todas las otras clases que se rompieron por este cambio.
bikeman868
1

Antes de responder a esa pregunta, debe decidir qué es lo que realmente quiere lograr.

Tú escribes el código. Espera que cumpla con su contrato (en otras palabras, hace lo que se supone que debe hacer. Escribir lo que se supone que debe hacer es un gran paso adelante para algunas personas).

Para estar razonablemente convencido de que el código hace lo que se supone que debe hacer, puede mirarlo lo suficiente o escribir un código de prueba que pruebe suficientes casos para convencerlo de "si el código pasa todas estas pruebas, entonces es correcto".

A menudo solo le interesa la interfaz definida públicamente de algún código. Si uso la biblioteca, no me importa cómo lo hizo funcionar correctamente, sólo que hace el trabajo correctamente. Verifico que su biblioteca es correcta realizando pruebas unitarias.

Pero estás creando la biblioteca. Hacer que funcione correctamente puede ser difícil de lograr. Digamos que solo me importa que la biblioteca haga la operación X correctamente, así que tengo una prueba unitaria para X. Usted, el desarrollador responsable de crear la biblioteca, implementa X combinando los pasos A, B y C, que son totalmente no triviales. Para que su biblioteca funcione, agregue pruebas para verificar que A, B y C funcionen correctamente. Quieres estas pruebas. Decir "no deberías tener pruebas unitarias para métodos privados" no tiene sentido. Usted desea que las pruebas para estos métodos privados. Tal vez alguien le diga que la unidad que prueba métodos privados está mal. Pero eso solo significa que no puede llamarlos "pruebas unitarias" sino "pruebas privadas" o como quiera llamarlas.

El lenguaje Swift resuelve el problema de que no desea exponer A, B, C como métodos públicos simplemente porque desea probarlo dando a las funciones un atributo "comprobable". El compilador permite que se invoquen métodos privados comprobables desde pruebas unitarias, pero no desde código que no sea de prueba.

gnasher729
fuente
0

Sí, estás loco ... ¡COMO UN ZORRO!

Hay un par de formas de probar métodos privados, algunos de los cuales dependen del idioma.

  • ¡reflexión! ¡las reglas estan hechas para romperse!
  • hacerlos protegidos, heredar y anular
  • amigo / InternalsVisibleTo clases

En general, si desea probar métodos privados, probablemente quiera moverlos a métodos públicos en una dependencia y probar / inyectar eso.

Ewan
fuente
No sé, en mi opinión, usted explicó que no es una buena idea, pero continuó respondiendo la pregunta
Liath
Pensé que tenía todas las bases cubiertas :(
Ewan
3
Voté, porque la idea de exponer sus métodos de ayuda privada a la API pública solo para probarlos es una locura, y siempre lo he pensado.
Robert Harvey
0

Mantente pragmático. Probar casos especiales en métodos privados mediante la configuración del estado de la instancia y los parámetros de un método público para que esos casos ocurran allí, a menudo es demasiado complicado.

Agrego un descriptor de internalacceso adicional (junto con el indicador InternalsVisibleTodel ensamblaje de prueba), claramente nombrado DoSomethingForTesting(parameters), para probar esos métodos "privados".

Por supuesto, la implementación puede cambiar en algún momento, y esas pruebas, incluidos los accesores de prueba, se vuelven obsoletas. Eso sigue siendo mejor que los casos no probados o las pruebas ilegibles.

Bernhard Hiller
fuente