¿Por qué las pruebas unitarias de métodos privados se consideran una mala práctica?

16

Contexto:

Actualmente estoy trabajando en un pequeño proyecto en Python. Comúnmente estructuro mis clases con algunos métodos públicos que están documentados pero que tratan principalmente con los conceptos de alto nivel (lo que un usuario de la clase debe saber y usar), y un montón de métodos ocultos (comenzando con guiones bajos) que están a cargo de procesamiento complejo o de bajo nivel.

Sé que las pruebas son esenciales para dar confianza en el código y para garantizar que cualquier modificación posterior no haya roto el comportamiento anterior.

Problema:

Para construir los métodos públicos de nivel superior sobre una base confiable, generalmente pruebo los métodos privados. Me resulta más fácil encontrar si una modificación de código ha introducido regresiones y dónde. Significa que esas pruebas internas pueden infringir revisiones menores y deberán ser reparadas / reemplazadas

Pero también sé que el método privado de pruebas unitarias es al menos un concepto en disputa o más a menudo considerado como una mala práctica. La razón es: solo se debe probar el comportamiento público ( ref. )

Pregunta:

Me importa seguir las mejores prácticas y me gustaría entender:

  • ¿por qué es malo usar pruebas unitarias en métodos privados / ocultos (cuál es el riesgo)?
  • ¿Cuáles son las mejores prácticas cuando los métodos públicos pueden usar procesamiento de bajo nivel y / o complejo?

Precisiones

  • no es una forma de pregunta. Python no tiene un verdadero concepto de privacidad y los métodos ocultos simplemente no se enumeran, pero se pueden usar cuando se conoce su nombre
  • Nunca me han enseñado reglas y patrones de programación: mis últimas clases son de los años 80 ... Aprendí idiomas principalmente por prueba y fracaso y referencias en Internet (Stack Exchange fue mi favorito durante años)
Serge Ballesta
fuente
2
Posible duplicado de Prueba de métodos privados como protegidos
Greg Burghardt
2
OP, ¿dónde escuchó o leyó que las pruebas de métodos privados se consideraban "malas"? Hay diferentes formas de realizar pruebas unitarias. Consulte Prueba de unidad, Prueba de caja negra y Prueba de caja blanca .
John Wu
@ JohnWu: Sé la diferencia entre las pruebas de caja blanca y caja negra. Pero incluso en las pruebas de caja blanca parece que la necesidad de probar métodos privados es una pista para un problema de diseño. Mi pregunta es un intento de entender cuáles son los mejores caminos cuando caigo allí ...
Serge Ballesta
2
Nuevamente, ¿dónde escuchó o leyó que incluso en las pruebas de White-box la necesidad de probar métodos privados es una pista para un problema de diseño? Me gustaría entender el razonamiento detrás de esa creencia antes de intentar una respuesta.
John Wu
@SergeBallesta en otras palabras, ponga algunas referencias a esos artículos que le hicieron creer que probar métodos privados es una mala práctica. Luego explícanos por qué les creíste.
Laiv

Respuestas:

17

Un par de razones:

  1. Por lo general, cuando se siente tentado a probar el método privado de una clase, es un olor a diseño (clase iceberg, no hay suficientes componentes públicos reutilizables, etc.). Casi siempre hay algún problema "mayor" en juego.

  2. Puede probarlos a través de la interfaz pública (que es cómo desea probarlos, porque así es como los llamará / usará el cliente). Puede obtener una falsa sensación de seguridad al ver la luz verde en todas las pruebas de aprobación de sus métodos privados. Es mucho mejor / más seguro probar casos extremos en sus funciones privadas a través de su interfaz pública.

  3. Corre el riesgo de una duplicación de prueba severa (pruebas que se ven / se sienten muy similares) al probar métodos privados. Esto tiene consecuencias importantes cuando cambian los requisitos, ya que se romperán muchas más pruebas de las necesarias. También puede ponerlo en una posición en la que es difícil refactorizar debido a su conjunto de pruebas ... lo cual es la ironía final, ¡porque el conjunto de pruebas está ahí para ayudarlo a rediseñar y refactorizar de manera segura!

Un consejo si todavía está tentado a probar las partes privadas (no lo use si le molesta a usted y a YMMV, pero me ha funcionado bien en el pasado): a veces escribir pruebas unitarias para funciones privadas solo para asegurarse están trabajando exactamente como crees que pueden ser valiosos (especialmente si eres nuevo en un idioma). Sin embargo, una vez que esté seguro de que funcionan, elimine las pruebas y asegúrese siempre de que las pruebas públicas sean sólidas y se detendrán si alguien realiza un cambio atroz en dicha función privada.

Cuándo probar métodos privados: dado que esta respuesta se ha vuelto (algo) popular, me siento obligado a mencionar que una "mejor práctica" es siempre eso: una "mejor práctica". No significa que debas hacerlo de manera dogmática o ciega. Si cree que debe probar sus métodos privados y tiene una razón legítima (como si estuviera escribiendo pruebas de caracterización para una aplicación heredada), pruebe sus métodos privados . Las circunstancias específicas siempre prevalecen sobre cualquier regla general o práctica recomendada. Solo tenga en cuenta algunas de las cosas que pueden salir mal (ver arriba).

Tengo una respuesta que detalla esto en SO que no repetiré aquí: /programming/105007/should-i-test-private-methods-or-only-public-ones/ 47401015 # 47401015

Matt Messersmith
fuente
44
Razón 1: Nebuloso. Razón 2: ¿Qué sucede si su método de ayuda privada no debe ser parte de la API pública? Razón 3: no si diseñas tu clase correctamente. Su último consejo: ¿por qué eliminaría una prueba perfectamente buena que pruebe que un método que escribí funciona?
Robert Harvey
44
@RobertHarvey Razón 2: ser accesible indirectamente a través de la API pública! = Ser parte de la API pública. Si su función privada no es comprobable a través de la API pública, entonces ¿tal vez es un código muerto y debería eliminarse? O su clase es de hecho un iceberg (razón 1) y debe ser refactorizada.
Frax
55
@RobertHarvey si no puede probar una función privada a través de una API pública, elimínela ya que no sirve para nada.
David Arno
1
@RobertHarvey 1: los olores de diseño siempre son algo subjetivos / nebulosos, así que seguro. Pero he enumerado algunos ejemplos concretos de antipatrones, y hay más detalles en mi respuesta SO. 2: Los métodos privados no pueden ser parte de la API pública (por definición: son privados) ... así que no creo que su pregunta tenga mucho sentido. Estoy tratando de llegar al punto de que si tienes algo como bin_search(arr, item)(público) y bin_search(arr, item, low, hi)(privado, hay muchas formas de hacer una búsqueda de bin), entonces todo lo que necesitas probar es el público que se enfrenta a uno ( bin_search(arr, item))
Matt Messersmith
1
@RobertHarvey 3: Primero, dije riesgo , no garantía . En segundo lugar, afirmar que "funciona si lo haces correctamente" es autocumplido. Por ejemplo, "Puede escribir un sistema operativo en una sola función si lo hace correctamente ". Esto no es falso: pero tampoco es útil. Acerca del consejo: lo eliminaría porque si su implementación cambia (es decir, si desea cambiar un impl privado), entonces su conjunto de pruebas se interpondrá en su camino (tendrá una prueba fallida donde no debería hacerlo).
Matt Messersmith
13

Dado que uno de los propósitos principales de las pruebas unitarias es que puede refactorizar las partes internas de su programa y luego poder verificar que no ha roto su funcionalidad, es contraproducente si sus pruebas unitarias operan con un nivel de granularidad tan fino que Cualquier cambio en el código del programa requiere que reescriba sus pruebas.

Pete
fuente
No estoy seguro de por qué su respuesta ha sido rechazada. Es breve, al punto y obtiene la respuesta 100% correcta.
David Arno
3
@DavidArno: Tal vez porque probar métodos privados realmente no tiene mucho que ver con la granularidad de prueba. Tiene todo que ver con el acoplamiento a los detalles de implementación.
Robert Harvey
10

Escribir pruebas unitarias para métodos privados vincula sus pruebas unitarias a los detalles de implementación.

Las pruebas unitarias deben probar el comportamiento de una clase en la superficie externa de la clase (es API pública). Las pruebas unitarias no deberían tener que saber nada sobre las entrañas de una clase. Escribir pruebas unitarias contra los detalles de implementación de una clase ata tus manos cuando llega el momento de refactorizar. Refactorizar seguramente romperá esas pruebas, porque no son parte de su API estable.

Dicho esto, ¿por qué puede ser que usted desee escribir pruebas unitarias para sus métodos privados?

Existe una tensión natural entre las pruebas unitarias y el desarrollo incremental. Los desarrolladores de software que usan un REPL (bucle read-eval-print) pueden dar fe de cuán productivo puede ser escribir y probar rápidamente pequeños bits de funcionalidad a medida que "crecen" una clase o función. La única buena manera de hacerlo en entornos que están basados ​​en pruebas unitarias es escribir pruebas unitarias para métodos privados, pero hay mucha fricción al hacerlo. Las pruebas unitarias tardan en redactarse, necesita un método real contra el cual realizar pruebas y su marco de pruebas debe admitir la capacidad de mantener el método privado para que no contamine su API externa.

Algunos ecosistemas como C # y .NET tienen formas de crear entornos similares a REPL (herramientas como Linqpad hacen esto), pero su utilidad es limitada porque no tiene acceso a su proyecto. La ventana inmediata en Visual Studio es inconveniente; todavía no tiene Intellisense completo, debe usar nombres completos y desencadena una compilación cada vez que lo usa.

Robert Harvey
fuente
44
@ user949300 Es un poco difícil debatir esto sin caer en la verdadera falacia de Scotsman , pero hay muchas pruebas mal escritas de todo tipo. Desde una perspectiva de prueba de unidad, debe probar el contrato público de su método sin conocer los detalles internos de implementación. No siempre es incorrecto afirmar que cierta dependencia se ha llamado X veces: hay situaciones en las que esto tiene sentido. Solo tiene que asegurarse de que esta es una información que realmente desea transmitir en el contrato de esa unidad bajo prueba.
Vincent Savard
3
@DavidArno: [encogiéndose de hombros] He estado haciendo esto por un tiempo ahora. Las pruebas unitarias para métodos privados siempre funcionaron bien para mí, hasta que Microsoft decidió dejar de admitir objetos proxy en su marco de prueba. Nada explotó como resultado. Nunca abrí un agujero en el universo escribiendo una prueba para un método privado.
Robert Harvey
2
@DavidArno: ¿Por qué dejaría de usar una técnica perfectamente buena que me proporciona beneficios, solo porque alguien en Internet dice que es una mala idea sin dar ninguna justificación?
Robert Harvey
2
El principal beneficio que obtengo de las pruebas unitarias es que me proporciona una "red de seguridad" que me permite modificar mi código y estar seguro de que mis cambios no están introduciendo regresiones. Con ese fin, probar métodos de ayuda privados hace que sea más fácil encontrar tales regresiones. Cuando refactorizo ​​un método auxiliar privado e introduzco un error lógico, rompo las pruebas específicas de ese método privado. Si las pruebas de mi unidad fueran más generales y solo probaran la interfaz de esa unidad de código, entonces el problema sería mucho más oscuro de encontrar.
Alexander - Restablece a Mónica el
2
@Frax Claro que podría, pero por esa lógica, debería renunciar a las pruebas unitarias a favor de las pruebas de integración de todo el sistema. Después de todo, "en la mayoría de los casos deberías poder modificar estas pruebas para probar el mismo comportamiento"
Alexander - Restablece a Monica el
6

Según mi experiencia, descubrí que la unidad que prueba las clases internas, los métodos generalmente significa que tengo que eliminar las funciones probadas, las clases. Para crear otro nivel de abstracción.

Esto conduce a una mejor adhesión del Principio de Responsabilidad Única.

Robert Andrzejuk
fuente
Esta debería ser la respuesta aceptada.
Jack Aidley
0

Creo que esta es una buena pregunta porque expone un problema común en las pruebas de cobertura. Pero una buena respuesta debe decirle que la cuestión no es exactamente correcto, porque, en teoría, debería no ser capaz de probar la unidad privadas métodos. Por eso son privados .

Tal vez una mejor pregunta sería "¿Qué debo hacer cuando quiero probar métodos privados?" , y la respuesta es bastante obvia: debe exponerlos de una manera que haga posible la prueba. Ahora, esto no significa necesariamente que debas hacer público el método y eso es todo. Lo más probable es que quieras hacer una mayor abstracción; muévase a una biblioteca o API diferente para que pueda hacer sus pruebas en esa biblioteca, sin exponer esa funcionalidad en su API principal.

Recuerde que hay una razón por la cual sus métodos tienen diferentes niveles de accesibilidad, y siempre debe pensar en cómo se utilizarán sus clases al final.

Juan Carlos Eduardo Romaina Ac
fuente
Intenté abordar esto en mi pregunta diciendo que Python no tiene un concepto verdadero de privacidad y que los métodos ocultos simplemente no están en la lista, pero se pueden usar cuando se conoce su nombre
Serge Ballesta