¿Los métodos privados / protegidos deben someterse a prueba unitaria?

82

En el desarrollo de TDD, lo primero que suele hacer es crear su interfaz y luego comenzar a escribir sus pruebas unitarias en esa interfaz. A medida que avanza en el proceso TDD, terminaría creando una clase que implementa la interfaz y luego, en algún momento, su prueba unitaria pasaría.

Ahora mi pregunta es sobre los métodos privados y protegidos que podría tener que escribir en mi clase en apoyo de los métodos / propiedades expuestos por la interfaz:

  • ¿Deberían los métodos privados de la clase tener sus propias pruebas unitarias?

  • ¿Los métodos protegidos de la clase deberían tener sus propias pruebas unitarias?

Mis pensamientos:

  • Especialmente porque estoy codificando interfaces, no debería preocuparme por métodos protegidos / privados, ya que son cajas negras.

  • Debido a que estoy usando interfaces, estoy escribiendo pruebas unitarias para validar que el contrato definido está implementado correctamente por las diferentes clases que implementan la interfaz, por lo que nuevamente no debería preocuparme por los métodos privados / protegidos y deben ejercerse a través de pruebas unitarias que llaman al métodos / propiedades definidos por la interfaz.

  • Si mi cobertura de código no muestra que los métodos protegidos / privados están siendo afectados, entonces no tengo las pruebas unitarias correctas o tengo un código que no se está usando y debería eliminarse.

Raj Rao
fuente
1
Si no ejercita sus métodos protegidos de sus pruebas, ya sea anulándolos o llamándolos, ¿por qué están protegidos en lugar de privados? Al protegerlos, está tomando una decisión consciente de exponer el punto de extensión / funcionalidad. Para mí, si está siguiendo TDD, esta decisión debe basarse en las pruebas que está escribiendo.
forsvarir
2
Debería poner la parte sobre sus propios pensamientos en una respuesta separada. Avísame cuando lo hagas y votaré a favor.
Keith Pinson
Lo mismo solo para privados: stackoverflow.com/questions/105007/…
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
Tiene razón sobre las pruebas unitarias activas, es decir, aquellas que están configuradas para ejecutarse continuamente. Para estos, solo desea que se prueben las interfaces públicas y protegidas. Puede y podría beneficiarse también de escribir pruebas para métodos privados. Esas pruebas no deberían ser parte de su conjunto continuo, pero como una única vez para verificar que su implementación es buena, puede ser una herramienta muy valiosa.
Didier A.

Respuestas:

108

No, no pienso en probar métodos privados o protegidos. Los métodos privados y protegidos de una clase no forman parte de la interfaz pública, por lo que no exponen el comportamiento público. Generalmente, estos métodos se crean mediante refactorizaciones que aplica después de haber hecho que su prueba se vuelva verde.

Entonces estos métodos privados se prueban implícitamente mediante las pruebas que afirman el comportamiento de su interfaz pública.

En una nota más filosófica, recuerde que está probando el comportamiento, no los métodos. Entonces, si piensa en el conjunto de cosas que la clase bajo prueba puede hacer, siempre que pueda probar y afirmar que la clase se comporta como se espera, si hay métodos privados (y protegidos) que la clase usa internamente para implementar ese comportamiento es irrelevante. Esos métodos son detalles de implementación del comportamiento público.


fuente
23
Me gusta el hecho de que dijiste que las pruebas unitarias, prueban el comportamiento y no los métodos. Eso aclara mucho las cosas.
Raj Rao
1
Estoy de acuerdo con @rajah. Esa debería ser la primera declaración en cada tutorial. Me he estado preguntando cómo probar mis métodos, ahora sé que no es necesario. +1
frostymarvelous
3
¿Diría que esto todavía se aplica en los casos en que las clases base implementan un comportamiento protegido que se espera que el público herede y use? Entonces, los métodos protegidos siguen siendo parte de la interfaz pública, ¿no es así?
Nick Udell
1
En términos generales, los patrones que favorecen la separación de preocupaciones son más adecuados para las pruebas unitarias aisladas, mientras que los patrones que favorecen la encapsulación tienen API más fáciles de usar.
尤川豪
3
Esto no aclara el caso de la visibilidad protegida. Parece que un método protegido también es parte de una interfaz, a menudo, es un punto de extensión, hecho a propósito protegido para serlo. Yo diría que en esos casos, también debería realizar una prueba unitaria. No quiere que nadie cambie las cosas en el futuro y rompa las clases que dependían de esos puntos de extensión para el comportamiento.
Didier A.
45

No estoy de acuerdo con la mayoría de los carteles.

La regla más importante es: EL CÓDIGO DE TRABAJO SUPERA LAS REGLAS TEÓRICAS sobre público / protegido / privado.

Su código debe probarse a fondo. Si puede llegar allí escribiendo pruebas para los métodos públicos, que ejerciten suficientemente los métodos protegidos / privados, eso es genial.

Si no puede, refactorice para que pueda, o doble las reglas protegidas / privadas.

Hay una gran historia sobre un psicólogo que les hizo una prueba a los niños. Le dio a cada niño dos tablas de madera con una cuerda atada a cada extremo y les pidió que cruzaran una habitación sin tocar el suelo con los pies, lo más rápido posible. Todos los niños usaban las tablas como pequeños esquís, con un pie en cada tabla, sujetándolas por las cuerdas y deslizándose por el suelo. Luego les dio la misma tarea, pero usando solo UNA tabla. Giraron / "caminaron" por el suelo, con un pie en cada extremo de la única tabla, ¡y fueron MÁS RÁPIDOS!

El hecho de que Java (o cualquier idioma) tenga una función (privada / protegida / pública) no significa necesariamente que esté escribiendo un código mejor porque lo usa.

Ahora, siempre habrá formas de optimizar / minimizar este conflicto. En la mayoría de los lenguajes, puede hacer que un método esté protegido (en lugar de público) y poner la clase de prueba en el mismo paquete (o lo que sea), y el método estará disponible para la prueba. Hay anotaciones que pueden ayudar, como se describe en otros carteles. Puedes usar la reflexión para llegar a los métodos privados (puaj).

El contexto también importa. Si está escribiendo una API para que la utilicen personas externas, lo público / privado es más importante. Si es un proyecto interno, ¿a quién le importa?

Pero al final del día, piense en cuántos errores han sido causados ​​por la falta de pruebas. Luego compare con cuántos errores han sido causados ​​por métodos "demasiado visibles". Esa respuesta debería impulsar tu decisión.

Charles Roth
fuente
3
Si un método es crítico y tiene una lógica complicada, afirmar su comportamiento es muy útil para prevenir errores. Escribir una prueba unitaria para un método de este tipo puede ayudar incluso a implementar el método de una manera exploratoria. Entonces, incluso si es privado, diría que vale la pena realizar pruebas unitarias. PERO, y hay un gran pero, debes recordar que las pruebas son de acoplamiento de código. Si escribe prueba en un método, está evitando la refactorización.
Didier A.
6
Entonces, antes de comenzar a escribir pruebas para métodos privados, diría que siempre reconsidere su diseño. Vea si las cosas se pueden generalizar y convertir en métodos funcionales puros. Si es así, puede extraerlos en su propia construcción. Esta construcción puede entonces tener su propia interfaz pública y ser probada por unidad. Recuerde, muchas veces, el comportamiento complicado en métodos privados puede ser una señal de que una clase tiene más de una sola responsabilidad. Entonces, por favor, reconsidere su diseño primero.
Didier A.
Sí, pero ¿qué es "código de trabajo"? Probar un método privado no dice nada sobre si su objeto tiene el comportamiento correcto o no. Ese es el punto principal de por qué solo probamos los métodos públicos. Solo los métodos públicos exhiben un comportamiento que le interesa al usuario de un fragmento de código.
Sammi
1
"Código de trabajo" es un código que funciona. Si hay un error en su método privado (o cuasi-privado), que no es detectado por las pruebas de sus métodos públicos, entonces algo anda mal. Tal vez su diseño sea incorrecto, bastante justo: estoy de acuerdo en que la mejor solución son las pruebas que llaman a los métodos públicos. Pero eso no siempre es posible, especialmente si está agregando o arreglando código heredado. (Hablo por experiencia, en un proyecto con 1 millón de líneas de código). El código probado siempre es mejor que el código no probado, punto. ¡Incluso si hemos roto buenas reglas sobre solo probar métodos públicos!
Charles Roth
El bit (en la parte superior) sobre "las pruebas son acoplamiento de código ... evitando la refactorización" es 100% incorrecto. En la metáfora arquitectónica, las pruebas son andamios, no hormigón. Las cosas cambian, las pruebas cambian, se descartan, se escriben nuevas pruebas. Estoy de acuerdo en que un buen diseño minimiza las reescrituras de pruebas. Pero el cambio ocurre, incluso en los mejores diseños.
Charles Roth
34

Tu escribiste:

En el desarrollo de TDD, lo primero que suele hacer es crear su interfaz y luego comenzar a escribir sus pruebas unitarias en esa interfaz. A medida que avanza en el proceso TDD, terminaría creando una clase que implementa la interfaz y luego, en algún momento, su prueba unitaria pasaría.

Permítanme reformular esto en lenguaje BDD :

Al describir por qué una clase es valiosa y cómo se comporta, lo primero que suele hacer es crear un ejemplo de cómo usar la clase, a menudo a través de su interfaz *. A medida que agrega el comportamiento deseado, termina creando una clase que proporciona ese valor, y luego, en algún momento, su ejemplo funciona.

* Puede ser una InterfaceAPI real o simplemente accesible de la clase, por ejemplo: Ruby no tiene interfaces.

Esta es la razón por la que no prueba métodos privados, porque una prueba es un ejemplo de cómo usar la clase, y en realidad no puede usarlos. Algo que puede hacer si lo desea es delegar las responsabilidades en los métodos privados a una clase colaboradora, luego simular / eliminar ese ayudante.

Con métodos protegidos, estás diciendo que una clase que amplía tu clase debería tener un comportamiento particular y proporcionar algún valor. Luego, podría usar extensiones de su clase para demostrar ese comportamiento. Por ejemplo, si estuviera escribiendo una clase de colección ordenada, es posible que desee demostrar que dos extensiones con el mismo contenido demostraron igualdad.

¡Espero que esto ayude!

Lunívoro
fuente
1
Publicación brillante. Aclara mucho.
frostymarvelous
17

Cuando escribe las pruebas unitarias para su clase, no debe preocuparse necesariamente si la funcionalidad de la clase se implementa directamente en el método en la interfaz pública o si se implementa en una serie de métodos privados. Entonces, sí, debería probar sus métodos privados, pero no debería necesitar llamarlos directamente desde su código de prueba para hacerlo (probar directamente los métodos privados combina estrechamente su implementación con sus pruebas y hace que la refactorización sea innecesariamente difícil).

Los métodos protegidos forman un contrato diferente entre su clase y sus futuros hijos, por lo que realmente debería probarlos de manera similar a su interfaz pública para asegurarse de que el contrato esté bien definido y ejecutado.

forsvarir
fuente
13

¡No! Solo prueba interfaces.

Uno de los grandes beneficios de TDD es garantizar que la interfaz funcione sin importar cómo haya elegido implementar los métodos privados.

S. Lot
fuente
11

Completando lo que otros dijeron anteriormente, diría que los métodos protegidos son parte de una interfaz de algún tipo: simplemente resulta ser el que está expuesto a la herencia en lugar de la composición, que es en lo que todos tienden a pensar cuando se consideran interfaces.

Marcar un método como protegido en lugar de privado significa que se espera que sea utilizado por un código de terceros, por lo que es necesario definir y probar algún tipo de contrato, como sucede con las interfaces normales definidas por métodos públicos, que están abiertos tanto para herencia como para composición. .

MGF
fuente
9

Hay dos razones para escribir pruebas:

  1. Afirmar el comportamiento esperado
  2. Prevenir la regresión del comportamiento

La toma de (1) Afirmar el comportamiento esperado:

Cuando afirma el comportamiento esperado, quiere asegurarse de que el código funcione como cree que debería. Esta es efectivamente una forma automatizada de realizar su verificación manual de rutina que cualquier desarrollador realizaría al implementar cualquier tipo de código:

  • ¿Lo que acabo de escribir funciona?
  • ¿Este ciclo realmente termina?
  • ¿Se repite en el orden que creo que está?
  • ¿Funcionaría esto para una entrada nula?

Esas son preguntas que todos respondemos en nuestras cabezas y, normalmente, intentamos ejecutar el código en nuestras cabezas también, asegurarnos de que parezca que funciona. Para estos casos, a menudo es útil que la computadora los responda de manera definitiva. Entonces escribimos una prueba unitaria que afirma que sí. Esto nos da confianza en nuestro código, nos ayuda a encontrar defectos temprano e incluso puede ayudarnos a implementar el código.

Es una buena idea hacer esto donde lo crea necesario. Cualquier código que sea un poco complicado de entender o que no sea trivial. Incluso el código trivial podría beneficiarse de ello. Se trata de tu propia confianza. La frecuencia con que lo haga y hasta dónde llegar dependerá de su propia satisfacción. Deténgase cuando pueda responder con seguridad Sí a: ¿Está seguro de que esto funciona?

Para este tipo de pruebas, a usted no le importa la visibilidad, las interfaces ni nada de eso, solo le importa tener un código que funcione. Entonces, sí, probaría los métodos privados y protegidos si cree que deben probarse para responder Sí a la pregunta.

La toma de (2) Prevención de la regresión del comportamiento:

Una vez que tenga el código que funcione, debe tener un mecanismo para proteger este código de daños futuros. Si nadie volviera a tocar su fuente y su configuración nunca más, no necesitaría esto, pero en la mayoría de los casos, usted u otros estarán tocando la fuente y las configuraciones de su software. Es muy probable que esta manipulación interna rompa su código de trabajo.

Ya existen mecanismos en la mayoría de los idiomas como una forma de protegerse contra este daño. Las características de visibilidad son un mecanismo. Un método privado está aislado y oculto. La encapsulación es otro mecanismo, en el que se compartimentan las cosas, para que cambiar otros compartimentos no afecte a los demás.

El mecanismo general para esto se llama: codificación hasta el límite. Al crear límites entre partes del código, protege todo lo que se encuentra dentro de un límite de las cosas que están fuera de él. Los límites se convierten en el punto de interacción y el contrato mediante el cual las cosas interactúan.

Esto significa que los cambios en un límite, ya sea rompiendo su interfaz o rompiendo su comportamiento esperado, dañarían y posiblemente romperían otros límites que dependían de él. Por eso es una buena idea tener una prueba unitaria, que se dirija a esos límites y afirme que no cambian en semántica ni en comportamiento.

Esta es su prueba unitaria típica, de la que casi todo el mundo habla cuando menciona TDD o BDD. El punto es endurecer los límites y protegerlos del cambio. No desea probar métodos privados para esto, porque un método privado no es un límite. Los métodos protegidos son un límite restringido y yo los protegería. No están expuestos al mundo, pero aún están expuestos a otros compartimentos o "Unidades".

¿Qué hacer con esto?

Como hemos visto, hay una buena razón para realizar pruebas unitarias de métodos públicos y protegidos, para afirmar que nuestras interfaces no cambian. Y también hay una buena razón para probar métodos privados, como para afirmar que nuestra implementación funciona. Entonces, ¿deberíamos probarlos todos por unidad?

Si y no.

En primer lugar : pruebe todos los métodos que crea que necesita una prueba definitiva de que funciona en la mayoría de los casos para poder estar seguro de que su código funciona, sin importar la visibilidad. Luego, desactive esas pruebas. Han hecho su trabajo.

Finalmente : escribe pruebas para tus límites. Realice una prueba unitaria para cada punto que utilizarán otras unidades de su sistema. Asegúrese de que esta prueba afirme el contrato semántico, el nombre del método, el número de argumentos, etc. Y también asegúrese de que la prueba afirme el comportamiento disponible de la unidad. Su prueba debe demostrar cómo usar la unidad y qué puede hacer la unidad. Mantenga estas pruebas habilitadas para que se ejecuten en cada inserción de código.

NOTA: La razón por la que desactivó el primer conjunto de pruebas es para permitir que se produzca el trabajo de refactorización. Una prueba activa es un código de acoplamiento. Evita modificaciones futuras del código que está probando. Solo desea esto para sus interfaces y contratos de interacción.

Didier A.
fuente
1
Hace que parezca que si no prueba explícitamente métodos privados de forma aislada, sus pruebas no los cubren y no puede confiar en que funcionen. Afirmo que esto es simplemente incorrecto. Un método privado (o cualquier ruta de código en él) que no se puede probar a través de un método público es un código muerto y debe eliminarse. El objetivo de TDD es obtener una cobertura completa solo probando métodos públicos, porque escribe 0 LoC que no existe para aprobar una prueba. Probar un método privado aislado SÓLO sirve para hacer que la refactorización sea más difícil, todo lo contrario de (uno de) los objetivos de TDD.
Sara
@kai Declaro explícitamente que no debe tener pruebas automatizadas para métodos privados, pero a veces es valioso tener pruebas aisladas para ayudarlo con la implementación. Esas pruebas no deben ser parte de su conjunto de pruebas, o deben estar deshabilitadas por la misma razón que mencionó: refactorización. Depende de su propio nivel de confianza decidir cuándo prefiere tener una prueba programática para implementar un método privado o no. ¿Quizás no leíste hasta el final de mi respuesta?
Didier A.
usted afirma que "también hay una buena razón para probar métodos privados, como para afirmar que nuestra implementación funciona". No veo ninguna base para esto en la publicación. No hay nada que una prueba de un método privado pueda decirle acerca de la implementación en funcionamiento que una prueba de un método público no pueda decirle también. El método privado funciona o no. Si no funciona, hará que una prueba de uno o más métodos públicos falle o su código esté muerto o no probado.
Sara
@kai Mencionas: "o es un código muerto y / o no probado". El código no probado es de lo que estoy hablando. Un método privado podría ocultar una gran cantidad de errores a los que no se están aplicando los casos extremos de los métodos públicos. Imagínese un error por un error. A veces, las invariantes de los métodos públicos hacen que este caso nunca suceda. En tal caso, consideraría que el método privado todavía tiene errores y tiene una implementación defectuosa, sin embargo, su integración evita que el error se encuentre y se detecte. En este caso, es posible que desee realizar algunas pruebas para probar casos extremos, de modo que pueda estar seguro de que su método está libre de errores.
Didier A.
@kai Pero, comprenda que esas pruebas de las que estoy hablando no son su conjunto de pruebas, esto no es TDD. Estoy diciendo que algunos métodos privados se hacen más fáciles de implementar si puede ejecutar rápidamente algunas pruebas en ellos. En los idiomas con REPL, no lo necesita tanto. Y puede intentar recorrer el método en su cabeza, pero recomiendo que la computadora ejecute pruebas solo para los métodos privados difíciles de implementar. Sugiero eliminar las pruebas después, o mantenerlas deshabilitadas o en su propio lugar especial que no se ejecuta en su compilación de CI.
Didier A.
4

No, no deberías probar métodos privados (¿cómo lo harías de todos modos sin usar algo horrible como la reflexión?). Con los métodos protegidos, es un poco menos obvio en C # que puede hacer que las cosas estén protegidas como internas y creo que está bien hacerlo para probar clases derivadas que implementan toda su funcionalidad a través de métodos de patrón de plantilla.

Pero, en general, si cree que sus métodos públicos están haciendo demasiado, entonces es hora de refactorizar sus clases en más clases atómicas y luego probar esas clases.

satnhak
fuente
2

Yo también estoy de acuerdo con la respuesta de @ kwbeam sobre no probar métodos privados. Sin embargo, un punto importante que me gustaría resaltar: los métodos protegidos SON parte de la API exportada de una clase y, por lo tanto, DEBEN probarse.

Es posible que los métodos protegidos no sean de acceso público, pero definitivamente está proporcionando una forma para que las subclases los usen / anulen. Algo fuera de la clase puede acceder a ellos y, por lo tanto, debe asegurarse de que esos miembros protegidos se comporten de la manera esperada. Por lo tanto, no pruebe los métodos privados, pruebe los métodos públicos y protegidos.

Si cree que tiene un método privado que contiene lógica crítica, trataría de extraerlo en un objeto separado, aislarlo y proporcionar una forma de probar su comportamiento.

¡Espero eso ayude!

codematix
fuente
2

Si su objetivo es una alta cobertura de código (le sugiero que lo haga), debe probar todos sus métodos, independientemente de que sean privados o protegidos.

Protegido es una especie de punto de discusión diferente, pero en resumen, no debería estar allí en absoluto. O rompe la encapsulación en el código implementado, o lo obliga a heredar de esa clase, solo para probarlo unitariamente, incluso a veces no es necesario heredar.

El simple hecho de ocultar un método al cliente (convertirlo en privado) no le otorga el privilegio de no ser auditado. Por lo tanto, pueden probarse mediante métodos públicos como se mencionó anteriormente.

Teoman shipahi
fuente
1

Estoy de acuerdo con todos los demás: la respuesta a su pregunta es "no".

De hecho, está completamente en lo cierto con su enfoque y sus pensamientos, especialmente sobre la cobertura del código.

También agregaría que la pregunta (y la respuesta 'no') también se aplica a los métodos públicos que podría introducir en las clases.

  • Si agrega métodos (públicos / protegidos o privados) porque fallan en la prueba, entonces ha logrado más o menos el objetivo de TDD.
  • Si agrega métodos (públicos / protegidos o privados) porque simplemente decide hacerlo, violando TDD, entonces la cobertura de su código debería detectarlos y debería poder mejorar su proceso.

Además, para C ++ (y debería pensar solo para C ++) implemento interfaces usando solo métodos privados, para indicar que la clase solo debe usarse a través de la interfaz que implementa. Me detiene llamando por error a nuevos métodos agregados a mi implementación desde mis pruebas

quamrana
fuente
0

Un buen diseño significa dividir la aplicación en varias unidades probables. Después de hacer esto, algunas unidades quedan expuestas a la API pública, pero es posible que otras no. Además, los puntos de interacción entre las unidades expuestas y estas unidades "internas" tampoco forman parte de la API pública.

Creo que una vez que tengamos la unidad identificable, se beneficiaría de las pruebas unitarias, independientemente de si se expone a través de API pública o no.

Audrius Meskauskas
fuente