¿Cómo puedo defender las pruebas unitarias en código privado?

15

Estoy tratando de defender las pruebas unitarias en mi grupo de trabajo, pero una objeción que a menudo recibo es que debe usarse solo para API exportadas externamente (que es solo una parte mínima y no crítica de nuestro sistema), y no en interna y privada código (que ahora solo tiene pruebas funcionales).

Si bien creo que esa prueba unitaria puede y debe aplicarse a todo el código, ¿cómo puedo persuadir a mis compañeros de trabajo?

Wizard79
fuente
3
Si tiene métodos privados que siente la necesidad de probar, a menudo es una señal de que su código está violando el SRP y hay otra clase allí que clama por ser extraída y probada por derecho propio.
Paddyslacker
@Paddyslacker: Siento que todo el código necesita ser probado. No veo por qué una unidad de código que sigue el principio de responsabilidad única no debería ser sometida a pruebas de unidad ...
Wizard79
44
@lorenzo, te perdiste mi punto; tal vez no lo hice muy bien. Si extrae estos métodos privados a otra clase, ahora deberá acceder a ellos desde su clase original. Debido a que los métodos ahora son públicos, deberán probarse. No estaba insinuando que no deberían ser probados, estaba insinuando que si sientes la necesidad de probar directamente los métodos, es probable que no sean privados.
Paddyslacker
@Paddyslacker: Siento la necesidad de probar directamente también métodos privados. ¿Por qué crees que no deberían ser privados?
Wizard79
66
Al probar métodos privados, está rompiendo la abstracción. Debe probar el estado y / o el comportamiento, no la implementación, en las pruebas unitarias. Sus ejemplos / escenarios deberían poder verificar cuál es el resultado del código privado: si le resulta difícil, entonces, como dice Paddyslacker, podría significar que está violando SRP. Sin embargo, también podría significar que no ha destilado sus ejemplos para ser realmente representativo de lo que está haciendo su código.
FinnNk

Respuestas:

9

Sus compañeros de trabajo pueden estar confundiendo verdaderas pruebas unitarias con pruebas de integración. Si su producto es (o tiene) una API, las pruebas de integración se pueden programar como casos de prueba NUnit. Algunas personas creen erróneamente que esas son pruebas unitarias.

Puede intentar convencer a sus compañeros de trabajo con lo siguiente (estoy seguro de que ya sabe esto, todo lo que digo es que señalarlo a sus compañeros de trabajo podría ayudar):

  • Prueba de cobertura . Mida el porcentaje de cobertura de prueba real de esas pruebas de integración. Esta es una verificación de la realidad para aquellos que nunca han realizado una cobertura de prueba. Debido a que es difícil ejercer todas las rutas lógicas cuando la entrada está a varias capas de distancia, la cobertura de prueba alcanza un máximo entre 20% y 50%. Para obtener más cobertura, sus compañeros de trabajo deben escribir pruebas unitarias reales y aisladas.
  • Configuracion . Implemente el mismo software bajo prueba y quizás pueda demostrar a sus compañeros de trabajo lo difícil que es ejecutar sus pruebas en un entorno diferente. Rutas a varios archivos, cadenas de conexión de base de datos, URL de servicios remotos, etc., todo se suma.
  • Tiempo de ejecución . A menos que las pruebas sean verdaderas pruebas unitarias y puedan ejecutarse en la memoria, tardarán mucho tiempo en ejecutarse.
azheglov
fuente
12

Las razones para usar pruebas unitarias en código interno / privado son exactamente las mismas que para las API con soporte externo:

  • Evitan que los errores se repitan (las pruebas unitarias forman parte de su conjunto de pruebas de regresión).
  • Documentan (en un formato ejecutable) que el código funciona.
  • Proporcionan una definición ejecutable de lo que significa "el código funciona".
  • Proporcionan un medio automatizado para demostrar que el código realmente coincide con las especificaciones (como se define en el punto anterior).
  • Muestran cómo la unidad / clase / módulo / función / método falla en presencia de entradas inesperadas.
  • Proporcionan ejemplos sobre cómo usar la unidad, que es una excelente documentación para los nuevos miembros del equipo.
Frank Shearar
fuente
8

Si te refieres a privado de la manera en que creo que lo dices en serio, entonces no, no deberías probarlo unitario. Solo debe realizar pruebas unitarias de comportamiento / estado observable. Puede estar perdiendo el punto detrás del ciclo "rojo-verde-refactor" de TDD (y si no está haciendo la prueba primero, entonces se aplica el mismo principio). Una vez que las pruebas se escriben y pasan, no desea que cambien mientras realiza la refactorización. Si se ve obligado a probar la funcionalidad privada de la unidad, entonces probablemente significa que las pruebas unitarias en torno a la funcionalidad pública son defectuosas. Si es difícil y complejo escribir pruebas alrededor del código público, entonces tal vez su clase está haciendo demasiado o su problema no está claramente definido.

Peor aún, con el tiempo sus pruebas unitarias se convertirán en una bola y una cadena que lo ralentizarán sin agregar ningún valor (cambiar la implementación, por ejemplo, la optimización o la eliminación de la duplicación, no debería tener efecto en las pruebas unitarias). Sin embargo, el código interno debe ser probado en la unidad ya que el comportamiento / estado es observable (solo de manera restringida).

Cuando hice la prueba de la unidad por primera vez, hice todo tipo de trucos para probar cosas privadas de la unidad, pero ahora, con algunos años en mi haber, lo veo peor que una pérdida de tiempo.

Aquí hay un ejemplo un poco tonto, por supuesto en la vida real tendrías más pruebas que estas:

Digamos que tiene una clase que devuelve una lista ordenada de cadenas: debe verificar que el resultado esté ordenado, no cómo realmente clasifica esa lista. Puede comenzar su implementación con un algoritmo único que simplemente clasifique la lista. Una vez hecho esto, su prueba no necesita cambiar si luego cambia su algoritmo de clasificación. En este punto, tiene una única prueba (suponiendo que la clasificación esté integrada en su clase):

  1. ¿Está mi resultado ordenado?

Ahora digamos que desea dos algoritmos (tal vez uno sea más eficiente en algunas circunstancias pero no en otras), entonces cada algoritmo podría (y generalmente debería) ser proporcionado por una clase diferente y su clase elige de ellos; puede verificar que esto esté sucediendo para sus escenarios elegidos utilizando simulacros, pero su prueba original sigue siendo válida y, como solo estamos verificando el comportamiento / estado observable, no necesita cambiar. Terminas con 3 pruebas:

  1. ¿Está mi resultado ordenado?
  2. Dado un escenario (digamos que la lista inicial está casi ordenada para empezar), ¿se realiza una llamada a la clase que ordena cadenas usando el algoritmo X?
  3. Dado un escenario (la lista inicial está en un orden aleatorio), ¿se realiza una llamada a la clase que ordena las cadenas usando el algoritmo Y?

La alternativa hubiera sido comenzar a probar el código privado dentro de su clase (no obtiene nada de esto), las pruebas anteriores me dicen todo lo que necesito saber en lo que respecta a las pruebas unitarias. Al agregar pruebas privadas, está construyendo una camisa de fuerza, ¿cuánto más trabajo sería si no solo verificara que el resultado se clasificó sino también cómo se clasifica?

Las pruebas (de este tipo) solo deberían cambiar cuando el comportamiento cambia, comenzar a escribir pruebas contra el código privado y eso desaparece.

FinnNk
fuente
1
Tal vez hay un malentendido sobre el significado de "privado". En nuestro sistema, el 99% del código es "privado", entonces tenemos una pequeña API para la automatización / control remoto de uno de los componentes del sistema. Me refiero a la unidad que prueba el código de todos los demás módulos.
Wizard79
4

Aquí hay otra razón: en el caso hipotético, tendría que elegir entre la unidad que prueba la API externa y las partes privadas, elegiría las partes privadas.

Si cada parte privada está cubierta por una prueba, la API que consta de estas partes privadas también debería estar cubierta por casi el 100%, excluyendo solo la capa superior. Pero es probable que sea una capa delgada.

Por otro lado, cuando solo se prueba la API, puede ser realmente difícil cubrir completamente todas las rutas de código posibles.

stijn
fuente
+1 "por otro lado ..." Pero si nada más, agregue pruebas en las que un fallo sea el más perjudicial.
Tony Ennis
2

Es difícil lograr que la gente acepte las pruebas unitarias porque parece una pérdida de tiempo ("¡podríamos estar codificando otro proyecto para ganar dinero!") O recursivo ("¡Y luego tenemos que escribir casos de prueba para los casos de prueba!") Soy culpable de decir las dos cosas.

La primera vez que encuentras un error, tienes que enfrentar la verdad de que no eres perfecto (¡qué rápido olvidamos los programadores!) Y dices "Hmmm".


Otro aspecto de las pruebas unitarias es que el código debe escribirse para que sea comprobable. Al darse cuenta de que Some Code es fácilmente comprobable y Some Code no lo hace, un buen programador dice "Hmmm".


¿Le preguntó a su compañero de trabajo por qué las pruebas unitarias solo eran útiles para las API externas?


Una forma de mostrar el valor de las pruebas unitarias es esperar a que ocurra un error desagradable y luego mostrar cómo las pruebas unitarias podrían haberlo evitado. Eso no es para frotarlos en sus caras, eso es, en sus mentes, mover las pruebas unitarias de una Teoría de la Torre de Marfil a una realidad en las trincheras.

Otra forma es esperar hasta que el mismo error ocurra dos veces . "Uhhh, bueno Jefe, agregamos código para probar un valor nulo después del problema de la semana pasada, ¡pero el usuario ingresó una cosa vacía esta vez!"


Predicar con el ejemplo. Escriba pruebas unitarias para SU código, luego muéstrele a su jefe el valor. Luego, vea si el jefe llamará pizza para el almuerzo un día y hará una presentación.


Finalmente, no puedo decirte el alivio que siento cuando estamos a punto de presionar para obtener un empujón y obtengo una barra verde de las pruebas unitarias.

Tony Ennis
fuente
2

Hay dos tipos de código privado: código privado que es llamada por código público (o código privado que es llamada por código privado que es llamada por código público (o ...)) y el código privado que no finalmente no serán llamadas por el público código.

El primero ya se prueba a través de las pruebas del código público. No se puede llamar a este último y, por lo tanto, se debe eliminar, no probar.

Tenga en cuenta que cuando hace TDD es imposible que exista código privado no probado.

Jörg W Mittag
fuente
En nuestro sistema, el 99% del código es del tercer tipo : privado, no llamado por código público, y esencial para el sistema (solo una parte mínima de nuestro sistema tiene una API pública externa).
Wizard79
1
"Tenga en cuenta que cuando hace TDD es imposible que exista código privado no probado". <- eliminar un caso de prueba, sin saber que la prueba es la única prueba que cubre una rama en particular. OK, ese es un código más "actualmente no probado", pero es bastante fácil ver una refactorización trivial posterior cambiando ese código ... solo su conjunto de pruebas ya no lo cubre.
Frank Shearar
2

La prueba de unidad se trata de probar unidades de su código. Depende de usted definir qué es una unidad. Sus compañeros de trabajo definen unidades como elementos API.

De todos modos, las pruebas de API también deberían resultar en el ejercicio de código privado. Si define la cobertura del código como un indicador del progreso de las pruebas unitarias, terminará probando todo su código. Si no se ha alcanzado alguna parte del código, brinde a sus compañeros de trabajo tres opciones:

  • definir otro caso de prueba para cubrir esa parte,
  • analizar el código para justificar por qué no se puede cubrir en el contexto de las pruebas unitarias, pero se debería cubrir en otras situaciones,
  • eliminar el código muerto que no ha sido cubierto ni justificado.
Mouviciel
fuente
En nuestro sistema, la API es solo una parte mínima, que permite la automatización / control remoto para una aplicación de terceros. Probar solo las cuentas API para una cobertura de código del 1% ...
Wizard79