¿Debo probar métodos privados o solo públicos? [cerrado]


348

He leído esta publicación sobre cómo probar métodos privados. Por lo general, no los pruebo, porque siempre pensé que es más rápido probar solo los métodos públicos que se llamarán desde fuera del objeto. ¿Pruebas métodos privados? ¿Debería probarlos siempre?



"¿Debería evaluar a los ayudantes privados?" Si. "¿Debo probar a los ayudantes privados directamente?" Depende, en general, si puede probarlos fácilmente a través de la interfaz pública, ¿por qué probarlos directamente? Si se vuelve complejo probar todos los aspectos de los ayudantes a través de una interfaz pública, ¿el componente ha sobrevivido a su existencia como una sola unidad?
Mihai Danila

Respuestas:


328

No hago pruebas unitarias de métodos privados. Un método privado es un detalle de implementación que debe ocultarse a los usuarios de la clase. Probar métodos privados rompe la encapsulación.

Si encuentro que el método privado es enorme o complejo o lo suficientemente importante como para requerir sus propias pruebas, simplemente lo pongo en otra clase y lo hago público allí ( Object Object ). Entonces puedo probar fácilmente el método anteriormente privado, pero ahora público, que ahora vive en su propia clase.


88
Estoy en desacuerdo. Idealmente, debe escribir una prueba rápida antes de comenzar a codificar una función. Piense en la entrada típica y cuál será la salida. Escriba la prueba (que no debería llevarle más de unos segundos) y codifique hasta que obtenga la prueba correcta. No hay razón para abandonar ese estilo de trabajo por métodos privados.
Frank

254
Decir que los métodos privados no necesitan pruebas es como decir que un automóvil está bien siempre que conduzca bien, y no importa lo que haya debajo del capó. Pero, ¿no sería bueno saber que parte del cable interno se está soltando, incluso si el usuario no nota nada? Claro, puedes hacer que todo sea público, pero ¿qué sentido tiene? Siempre querrás algunos métodos privados.
Frank

37
"Un método privado es un detalle de implementación que debe ocultarse a los usuarios de la clase". ¿pero las pruebas están realmente en el mismo lado de la interfaz de la clase que los usuarios "regulares" (tiempo de ejecución)? ;)
mlvljr

34
El peligro de extraer cualquier cosa que desee probar en otra clase es que puede terminar con la sobrecarga de diseñar demasiado su producto y tener un millón de componentes reutilizables que nunca se reutilizarán.
oculto

44
Comparar un código con un automóvil está mal; el código no se " daña " con el tiempo, es eterno . Si su prueba de la interfaz pública solo llega a determinar que 'se ve bien ', entonces su prueba del código público es insuficiente. En este caso, probar métodos privados por separado no completará la prueba general, sin importar cuánto lo intente. Concéntrese en probar exhaustivamente su código público en su conjunto, utilizando el conocimiento del funcionamiento interno del código para crear los escenarios correctos.
rustyx

293

¿Cuál es el propósito de la prueba?

La mayoría de las respuestas hasta ahora dicen que los métodos privados son detalles de implementación que no (o al menos no deberían) importar siempre que la interfaz pública esté bien probada y funcione. Eso es absolutamente correcto si su único propósito para las pruebas es garantizar que la interfaz pública funcione .

Personalmente, mi uso principal para las pruebas de código es asegurar que los cambios futuros en el código no causen problemas y ayudar a mis esfuerzos de depuración si lo hacen. Me parece que probar los métodos privados tan a fondo como la interfaz pública (¡si no más!) Promueve ese propósito.

Considere: Tiene un método público A que llama al método privado B. A y B utilizan el método C. C es cambiado (quizás por usted, quizás por un proveedor), lo que hace que A comience a fallar sus pruebas. ¿No sería útil tener pruebas para B también, a pesar de que es privado, para saber si el problema está en el uso de C por parte de A, el uso de C por parte de B o ambos?

Probar métodos privados también agrega valor en casos donde la cobertura de prueba de la interfaz pública es incompleta. Si bien esta es una situación que generalmente queremos evitar, la prueba de la unidad de eficiencia depende tanto de las pruebas para encontrar errores como de los costos asociados de desarrollo y mantenimiento de esas pruebas. En algunos casos, los beneficios de una cobertura de prueba del 100% pueden considerarse insuficientes para garantizar los costos de esas pruebas, lo que genera brechas en la cobertura de prueba de la interfaz pública. En tales casos, una prueba bien dirigida de un método privado puede ser una adición muy efectiva a la base del código.


72
El problema aquí es que esos "cambios futuros de código" significan invariablemente refactorizar el funcionamiento interno de alguna clase. Esto sucede con tanta frecuencia que escribir pruebas crea una barrera para la refactorización.
Outlaw Programmer

40
Además, si está cambiando continuamente sus pruebas unitarias, entonces ha perdido toda la consistencia en sus pruebas e incluso podría estar creando errores en las propias pruebas unitarias.
17 de 26

66
@ 17 Si las pruebas y la implementación se modifican sincrónicamente (como parece, debería ser así), habrá muchos menos problemas.
mlvljr

11
@Sauronlord, la razón por la que prueba los métodos privados es porque si solo prueba los métodos públicos, cuando la prueba falla no sabemos directamente cuál es la causa raíz de la falla. Podría estar en cualquiera testDoSomething()o testDoSomethingPrivate(). Esto hace que la prueba sea menos valiosa. . Aquí hay más razones para probar private stackoverflow.com/questions/34571/… :
Pacerier

3
@Pacerier También hay una diferencia entre probar su código y tener un proceso de prueba automatizado continuo. Obviamente, debe asegurarse de que su método privado funcione, pero no debe tener pruebas que lo acoplen al método privado, porque no es parte del caso de uso del software.
Didier A.

150

Tiendo a seguir los consejos de Dave Thomas y Andy Hunt en su libro Pragmatic Unit Testing :

En general, no querrás romper ninguna encapsulación por el simple hecho de probar (o como mamá solía decir, "¡no expongas tus partes privadas!"). La mayoría de las veces, debería poder evaluar una clase ejerciendo sus métodos públicos. Si hay una funcionalidad significativa que está oculta detrás del acceso privado o protegido, esa podría ser una señal de advertencia de que hay otra clase luchando por salir.

Pero a veces no puedo evitar probar métodos privados porque me da la sensación de que estoy construyendo un programa completamente robusto.


99
Recomendaría deshabilitar las pruebas unitarias que apuntan a métodos privados. Son un acoplamiento de código y supondrán una carga para el futuro trabajo de refactorización, o incluso a veces obstaculizarán la adición o modificación de funciones. Es bueno escribir una prueba para ellos mientras los implementa, como una forma automatizada de afirmar que su implementación funciona, pero no es beneficioso mantener las pruebas como regresión.
Didier A.

61

Me siento obligado a probar funciones privadas ya que sigo cada vez más una de nuestras últimas recomendaciones de control de calidad en nuestro proyecto:

No más de 10 en complejidad ciclomática por función.

Ahora el efecto secundario de la aplicación de esta política es que muchas de mis funciones públicas muy grandes se dividen en muchas funciones privadas más enfocadas y mejor nombradas .
La función pública todavía está allí (por supuesto), pero se reduce esencialmente a todas esas "subfunciones" privadas.

Eso es realmente genial, porque la pila de llamadas ahora es mucho más fácil de leer (en lugar de un error dentro de una función grande, tengo un error en una subfunción secundaria con el nombre de las funciones anteriores en la pila de llamadas para ayudarme a comprender 'cómo llegué allí')

Sin embargo, ahora parece más fácil probar directamente esas funciones privadas y dejar la prueba de la gran función pública a algún tipo de prueba de 'integración' donde un escenario necesita ser abordado.

Solo mis 2 centavos.


2
para reaccionar a @jop, no siento la necesidad de exportar esas funciones privadas (creadas debido a la división de una función pública compleja demasiado grande y ciclomática) a otra clase. Me gusta tenerlos aún estrechamente unidos a la función pública, en la misma clase. Pero sigue siendo probado por la unidad.
VonC

2
Mi experiencia es que esos métodos privados son solo métodos de utilidad que están siendo reutilizados por esos métodos públicos. A veces es más conveniente dividir la clase original en dos (o tres) clases más coherentes, haciendo públicos esos métodos privados en sus propias clases y, por lo tanto, comprobables.
jop

77
No, en mi caso, esas nuevas funciones privadas son realmente parte del algoritmo más grande representado por la función pública. Esa función se divide en partes más pequeñas, que no son de utilidad, sino pasos de un proceso más grande. De ahí la necesidad de hacer una prueba unitaria de ellos (en lugar de una prueba unitaria de todo el algo a la vez)
VonC

Para aquellos interesados ​​en la complejidad ciclomática, agregué una pregunta sobre el tema: stackoverflow.com/questions/105852/…
VonC

¡Vaya, la url de la pregunta cambió debido a un error tipográfico en el título! stackoverflow.com/questions/105852/…
VonC

51

Sí, pruebo funciones privadas, porque aunque se prueban con sus métodos públicos, es bueno en TDD (Test Driven Design) probar la parte más pequeña de la aplicación. Pero las funciones privadas no son accesibles cuando está en su clase de unidad de prueba. Esto es lo que hacemos para probar nuestros métodos privados.

¿Por qué tenemos métodos privados?

Las funciones privadas existen principalmente en nuestra clase porque queremos crear código legible en nuestros métodos públicos. No queremos que el usuario de esta clase llame a estos métodos directamente, sino a través de nuestros métodos públicos. Además, no queremos cambiar su comportamiento al extender la clase (en caso de estar protegido), por lo tanto, es privado.

Cuando codificamos, utilizamos el diseño controlado por prueba (TDD). Esto significa que a veces nos topamos con una funcionalidad que es privada y queremos probar. Las funciones privadas no son verificables en phpUnit, porque no podemos acceder a ellas en la clase Test (son privadas).

Creemos que aquí hay 3 soluciones:

1. Puede probar sus partes privadas a través de sus métodos públicos

Ventajas

  • Pruebas unitarias directas (no se necesitan 'hacks')

Desventajas

  • El programador necesita comprender el método público, mientras que solo quiere probar el método privado.
  • No está probando la parte comprobable más pequeña de la aplicación

2. Si lo privado es tan importante, entonces quizás sea un código para crear una nueva clase separada para él

Ventajas

  • Puede refactorizar esto a una nueva clase, porque si es tan importante, otras clases también pueden necesitarlo
  • La unidad comprobable ahora es un método público, por lo que es comprobable

Desventajas

  • No desea crear una clase si no es necesaria, y solo la usa la clase de donde proviene el método
  • Pérdida potencial de rendimiento debido a sobrecarga adicional

3. Cambie el modificador de acceso a (final) protegido

Ventajas

  • Está probando la parte comprobable más pequeña de la aplicación. Cuando se utiliza la protección final, la función no será reemplazable (como un privado)
  • Sin pérdida de rendimiento
  • Sin gastos adicionales

Desventajas

  • Está cambiando un acceso privado a protegido, lo que significa que es accesible para sus hijos.
  • Todavía necesita una clase simulada en su clase de prueba para usarla

Ejemplo

class Detective {
  public function investigate() {}
  private function sleepWithSuspect($suspect) {}
}
Altered version:
class Detective {
  public function investigate() {}
  final protected function sleepWithSuspect($suspect) {}
}
In Test class:
class Mock_Detective extends Detective {

  public test_sleepWithSuspect($suspect) 
  {
    //this is now accessible, but still not overridable!
    $this->sleepWithSuspect($suspect);
  }
}

Entonces nuestra unidad de prueba ahora puede llamar a test_sleepWithSuspect para probar nuestra función privada anterior.


eddy147, me gusta mucho el concepto de probar métodos protegidos a través de simulacros. ¡¡¡¡Gracias!!!!
Theodore R. Smith

15
Solo quiero señalar que en la descripción original de TDD, en las pruebas unitarias, la unidad es la clase , no un método / función. Entonces, cuando mencionas "probar la parte más pequeña de la aplicación", es incorrecto referir la parte más pequeña que se puede probar como método. Si usa esa lógica, también podría estar hablando una sola línea de código en lugar de un bloque de código completo.
Matt Quigley

@Matt Una unidad de trabajo puede apuntar a una clase, pero también a un único método.
eddy147

44
@ eddy147 Las pruebas unitarias vienen con el desarrollo impulsado por pruebas, donde la unidad se definió como una clase. Como sucede con The Internets, la semántica se ha expandido para significar muchas cosas (es decir, pregunte a 2 personas cuál es la diferencia entre las pruebas unitarias y de integración, y obtendrá 7 respuestas). TDD fue pensado como una forma de escribir software con principios SÓLIDOS, incluida la responsabilidad individual, donde una clase tenía una responsabilidad única y no debería tener una alta complejidad cíclica. En TDD, usted escribe su clase y prueba juntos, tanto la unidad. Los métodos privados están encapsulados no tienen una prueba de unidad correspondiente.
Matt Quigley

"Cuando codificamos, utilizamos el diseño controlado por prueba (TDD). Esto significa que a veces nos topamos con una funcionalidad que es privada y queremos probar". Estoy totalmente en desacuerdo con esta declaración, consulte mi respuesta a continuación para obtener más detalles. TDD no significa que esté obligado a probar métodos privados. Puedes elegir probar métodos privados: y esa es tu elección, pero no es TDD lo que te hace hacer tal cosa.
Matt Messersmith el

41

No me gusta probar la funcionalidad privada por un par de razones. Son los siguientes (estos son los puntos principales para las personas TLDR):

  1. Por lo general, cuando estás tentado a probar el método privado de una clase, es un olor a diseño.
  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 severa de la prueba (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!

Explicaré cada uno de estos con un ejemplo concreto. Resulta que 2) y 3) están algo intrincadamente conectados, por lo que su ejemplo es similar, aunque considero que son razones separadas por las que no debe probar métodos privados.

Hay momentos en los que es apropiado probar métodos privados, solo es importante tener en cuenta los inconvenientes mencionados anteriormente. Voy a repasarlo con más detalle más adelante.

También repaso por qué TDD no es una excusa válida para probar métodos privados al final.

Refactorizando su salida de un mal diseño

Uno de los patrones (anti) más comunes que veo es lo que Michael Feathers llama una clase "Iceberg" (si no sabe quién es Michael Feathers, vaya a comprar / lea su libro "Trabajando eficazmente con el código heredado". Él es una persona que vale la pena conocer si es un ingeniero / desarrollador de software profesional). Hay otros patrones (anti) que hacen que este problema surja, pero este es, con mucho, el más común con el que me he encontrado. Las clases "Iceberg" tienen un método público, y el resto son privadas (por eso es tentador probar los métodos privados). Se llama clase "Iceberg" porque generalmente hay un método público solitario que aparece, pero el resto de la funcionalidad está oculta bajo el agua en forma de métodos privados.

Evaluador de reglas

Por ejemplo, es posible que desee probar GetNextToken()llamándolo sucesivamente a una cadena y observando que devuelve el resultado esperado. Una función como esta garantiza una prueba: ese comportamiento no es trivial, especialmente si sus reglas de tokenización son complejas. Supongamos que no es tan complejo, y solo queremos enredar en tokens delimitados por el espacio. Entonces escribes una prueba, tal vez se ve más o menos así (algún código psuedo agnóstico de lenguaje, espero que la idea sea clara)

TEST_THAT(RuleEvaluator, canParseSpaceDelimtedTokens)
{
    input_string = "1 2 test bar"
    re = RuleEvaluator(input_string);

    ASSERT re.GetNextToken() IS "1";
    ASSERT re.GetNextToken() IS "2";
    ASSERT re.GetNextToken() IS "test";
    ASSERT re.GetNextToken() IS "bar";
    ASSERT re.HasMoreTokens() IS FALSE;
}

Bueno, eso en realidad se ve muy bien. Queremos asegurarnos de mantener este comportamiento a medida que hacemos cambios. ¡Pero GetNextToken()es una función privada ! Por lo tanto, no podemos probarlo de esta manera, ya que ni siquiera se compilará (suponiendo que estemos usando algún lenguaje que realmente aplique lo público / privado, a diferencia de algunos lenguajes de script como Python). Pero, ¿qué hay de cambiar la RuleEvaluatorclase para seguir el Principio de responsabilidad única (Principio de responsabilidad única)? Por ejemplo, parece que tenemos un analizador, un tokenizador y un evaluador agrupados en una clase. ¿No sería mejor separar esas responsabilidades? Además de eso, si crea una Tokenizerclase, entonces sus métodos públicos serían HasMoreTokens()yGetNextTokens() . losRuleEvaluator clase podría tener unTokenizerobjeto como miembro. Ahora, podemos mantener la misma prueba anterior, excepto que estamos probando la Tokenizerclase en lugar de la RuleEvaluatorclase.

Así es como se vería en UML:

Evaluador de reglas refactorizado

Tenga en cuenta que este nuevo diseño aumenta la modularidad, por lo que podría reutilizar estas clases en otras partes de su sistema (antes de que no pudiera, los métodos privados no son reutilizables por definición). Esta es la principal ventaja de romper el RuleEvaluator, junto con una mayor comprensión / localidad.

La prueba se vería extremadamente similar, excepto que en realidad se compilaría esta vez ya que el GetNextToken()método ahora es público en la Tokenizerclase:

TEST_THAT(Tokenizer, canParseSpaceDelimtedTokens)
{
    input_string = "1 2 test bar"
    tokenizer = Tokenizer(input_string);

    ASSERT tokenizer.GetNextToken() IS "1";
    ASSERT tokenizer.GetNextToken() IS "2";
    ASSERT tokenizer.GetNextToken() IS "test";
    ASSERT tokenizer.GetNextToken() IS "bar";
    ASSERT tokenizer.HasMoreTokens() IS FALSE;
}

Probar componentes privados a través de una interfaz pública y evitar la duplicación de pruebas

Incluso si no cree que puede dividir su problema en menos componentes modulares (que puede hacer el 95% del tiempo si solo intenta hacerlo), simplemente puede probar las funciones privadas a través de una interfaz pública. Muchas veces no vale la pena probar a los miembros privados porque serán probados a través de la interfaz pública. Muchas veces lo que veo son pruebas que se ven muy , pero prueban dos funciones / métodos diferentes. Lo que termina sucediendo es que cuando los requisitos cambian (y siempre lo hacen), ahora tiene 2 pruebas rotas en lugar de 1. Y si realmente probó todos sus métodos privados, podría tener más de 10 pruebas rotas en lugar de 1. o hacer ellos públicos o usando la reflexión) que de otra manera podrían ser probados a través de una interfaz pública pueden causar duplicación de prueba En resumen , probar funciones privadas (medianteFRIEND_TEST. Realmente no quieres esto, porque nada duele más que tu conjunto de pruebas que te frena. ¡Se supone que disminuye el tiempo de desarrollo y los costos de mantenimiento! Si prueba métodos privados que de otro modo se prueban a través de una interfaz pública, el conjunto de pruebas puede hacer lo contrario y aumentar activamente los costos de mantenimiento y el tiempo de desarrollo. Cuando haces pública una función privada, o si usas algo comoFRIEND_TEST y / o reflexión, generalmente terminará arrepintiéndose a la larga.

Considere la siguiente implementación posible de la Tokenizerclase:

ingrese la descripción de la imagen aquí

Digamos que SplitUpByDelimiter()es responsable de devolver una matriz de manera que cada elemento de la matriz sea un token. Además, digamos que GetNextToken()es simplemente un iterador sobre este vector. Entonces su prueba pública podría verse así:

TEST_THAT(Tokenizer, canParseSpaceDelimtedTokens)
{
    input_string = "1 2 test bar"
    tokenizer = Tokenizer(input_string);

    ASSERT tokenizer.GetNextToken() IS "1";
    ASSERT tokenizer.GetNextToken() IS "2";
    ASSERT tokenizer.GetNextToken() IS "test";
    ASSERT tokenizer.GetNextToken() IS "bar";
    ASSERT tokenizer.HasMoreTokens() IS false;
}

Supongamos que tenemos lo que Michael Feather llama una herramienta a tientas . Esta es una herramienta que te permite tocar las partes privadas de otras personas. Un ejemplo es FRIEND_TESTde googletest, o reflexión si el idioma lo admite.

TEST_THAT(TokenizerTest, canGenerateSpaceDelimtedTokens)
{
    input_string = "1 2 test bar"
    tokenizer = Tokenizer(input_string);
    result_array = tokenizer.SplitUpByDelimiter(" ");

    ASSERT result.size() IS 4;
    ASSERT result[0] IS "1";
    ASSERT result[1] IS "2";
    ASSERT result[2] IS "test";
    ASSERT result[3] IS "bar";
}

Bueno, ahora digamos que los requisitos cambian, y la tokenización se vuelve mucho más compleja. Decide que un delimitador de cadena simple no será suficiente y necesita una Delimiterclase para manejar el trabajo. Naturalmente, esperará que se rompa una prueba, pero ese dolor aumenta cuando prueba funciones privadas.

¿Cuándo pueden ser apropiados los métodos privados de prueba?

No hay una "talla única para todos" en el software. A veces está bien (y en realidad es ideal) "romper las reglas". Recomiendo no probar la funcionalidad privada cuando pueda. Hay dos situaciones principales cuando creo que está bien:

  1. He trabajado mucho con sistemas heredados (por eso soy tan fanático de Michael Feathers), y puedo decir con seguridad que a veces es más seguro simplemente probar la funcionalidad privada. Puede ser especialmente útil para obtener "pruebas de caracterización" en la línea de base.

  2. Tienes prisa y tienes que hacer lo más rápido posible aquí y ahora. A la larga, no desea probar métodos privados. Pero diré que generalmente toma algún tiempo refactorizar para abordar los problemas de diseño. Y a veces tienes que enviar en una semana. Eso está bien: haz lo rápido y sucio y prueba los métodos privados usando una herramienta de búsqueda a tientas si eso es lo que crees que es la forma más rápida y confiable de hacer el trabajo. Pero comprenda que lo que hizo fue subóptimo a largo plazo, y considere volver a hacerlo (o, si se olvidó pero lo ve más tarde, corríjalo).

Probablemente hay otras situaciones en las que está bien. Si crees que está bien y tienes una buena justificación, entonces hazlo. Nadie te está deteniendo. Solo tenga en cuenta los costos potenciales.

La excusa TDD

Por otro lado, realmente no me gusta la gente que usa TDD como excusa para probar métodos privados. Practico TDD, y no creo que TDD te obligue a hacerlo. Puede escribir su prueba (para su interfaz pública) primero, y luego escribir código para satisfacer esa interfaz. A veces escribo una prueba para una interfaz pública, y la satisfaré escribiendo uno o dos métodos privados más pequeños (pero no pruebo los métodos privados directamente, pero sé que funcionan o mi prueba pública no funcionará) ) Si necesito probar casos extremos de ese método privado, escribiré un montón de pruebas que los afectarán a través de mi interfaz pública.Si no puede descubrir cómo llegar a los casos límite, esta es una buena señal de que necesita refactorizar en componentes pequeños, cada uno con sus propios métodos públicos. Es una señal de que sus funciones privadas están haciendo demasiado, y fuera del alcance de la clase .

Además, a veces encuentro que escribo una prueba que es demasiado grande para masticar en este momento, por lo que pienso "eh volveré a esa prueba más tarde cuando tenga más API para trabajar" (I Lo comentaré y lo mantendré en el fondo de mi mente). Aquí es donde muchos desarrolladores que he conocido comenzarán a escribir pruebas para su funcionalidad privada, utilizando TDD como chivo expiatorio. Dicen "oh, bueno, necesito otra prueba, pero para escribir esa prueba, necesitaré estos métodos privados. Por lo tanto, como no puedo escribir ningún código de producción sin escribir una prueba, necesito escribir una prueba por un método privado ". Pero lo que realmente necesitan hacer es refactorizar en componentes más pequeños y reutilizables en lugar de agregar / probar un montón de métodos privados a su clase actual.

Nota:

Respondí una pregunta similar acerca de probar métodos privados usando GoogleTest hace un tiempo. Principalmente modifiqué esa respuesta para que sea más independiente del lenguaje aquí.

PD: Aquí está la conferencia relevante sobre las clases de iceberg y las herramientas a tientas de Michael Feathers: https://www.youtube.com/watch?v=4cVZvoFGJTU


El problema que tengo al enumerar "podría potencialmente reutilizar estas clases en otras partes de su sistema" como una ventaja, es que a veces la razón por la que marco una función es privada es porque no quiero que otras partes de la utilicen el sistema. Este es un problema específico del lenguaje: idealmente, esto sería privado para un "módulo", pero si el lenguaje no lo admite (por ejemplo, PHP), mi clase representa el módulo, no la unidad: los métodos privados son código reutilizable con sus propios contratos, pero solo deben reutilizarse dentro de esa clase.
IMSoP

Entiendo lo que estás diciendo, pero me gusta la forma en que la comunidad Python maneja ese problema. Si nombra al miembro "privado" en cuestión con un encabezado _, indica "oye, esto es 'privado'. Puede usarlo, pero la divulgación completa, no fue diseñado para su reutilización y solo debe usarlo si realmente saber lo que estás haciendo ". Puede adoptar el mismo enfoque en cualquier idioma: haga públicos esos miembros, pero márquelos con un encabezado _. O tal vez esas funciones realmente deberían ser privadas y solo probadas a través de una interfaz pública (consulte la respuesta para obtener más detalles). Es caso por caso, no hay regla general
Matt Messersmith

26

Creo que es mejor probar la interfaz pública de un objeto. Desde el punto de vista del mundo exterior, solo importa el comportamiento de la interfaz pública y esto es a lo que deben dirigirse las pruebas de su unidad.

Una vez que haya escrito algunas pruebas unitarias sólidas para un objeto, no querrá tener que volver atrás y cambiar esas pruebas solo porque la implementación detrás de la interfaz cambió. En esta situación, ha arruinado la consistencia de sus pruebas unitarias.


21

Si su método privado no se prueba llamando a sus métodos públicos, ¿qué está haciendo? Estoy hablando privado no protegido o amigo.


3
Gracias. Este es un comentario sorprendentemente subestimado y especialmente relevante, incluso después de casi 8 años desde que fue escrito.
Sauronlord

1
Con el mismo razonamiento, uno podría argumentar que solo prueba el software desde la interfaz de usuario (prueba de nivel del sistema), porque de alguna manera todas las funciones en el software se ejecutarían de alguna manera desde allí.
Dirk Herrmann

18

Si el método privado está bien definido (es decir, tiene una función que es comprobable y no está destinada a cambiar con el tiempo), entonces sí. Pruebo todo lo que es comprobable donde tiene sentido.

Por ejemplo, una biblioteca de cifrado puede ocultar el hecho de que realiza un cifrado en bloque con un método privado que cifra solo 8 bytes a la vez. Escribiría una prueba unitaria para eso: no está destinado a cambiar, aunque esté oculto, y si se rompe (debido a futuras mejoras de rendimiento, por ejemplo), quiero saber que es la función privada la que se rompió, no solo que una de las funciones públicas se rompió.

Acelera la depuración más tarde.

-Adán


1
En este caso, ¿no tendría sentido mover ese método privado a otra clase y luego hacerlo público o público estático?
Outlaw Programmer

+1 Si no prueba sus funciones de miembro privado y su prueba de la interfaz pública falla, todo lo que obtendrá será un resultado equivalente a algo roto sin ninguna idea de qué es ese algo.
Olumide

12

Si está desarrollando una prueba de manejo (TDD), probará sus métodos privados.



44
No es cierto, prueba sus métodos públicos y una vez que pasan las pruebas, extrae el código de sus métodos públicos en métodos privados durante el paso de "limpieza". Probar métodos privados es una mala idea porque hace que cambiar la implementación sea mucho más difícil (¿qué pasa si algún día quieres cambiar la forma en que haces algo? Deberías poder cambiarlo y ejecutar todas tus pruebas y si tu nueva forma de hacer Lo correcto es que deberían pasar, no me gustaría tener que cambiar todas mis pruebas privadas para esto).
Tesseract el

1
@Tesseract, si pudiera votar tu comentario más de una vez, lo haría. "... deberías poder cambiarlo y ejecutar todas tus pruebas y si tu nueva forma de hacer las cosas es correcta, deberían pasar" ESO es uno de los principales beneficios de las pruebas unitarias. Le permiten refactorizar con confianza. Puede cambiar completamente el funcionamiento privado interno de su clase y (sin reescribir todas sus pruebas unitarias) puede confiar en que no rompió nada porque todas sus pruebas unitarias (existentes) (en su interfaz pública) aún pasan.
Lee

No estoy de acuerdo, mira mi respuesta a continuación
Matt Messersmith

11

No soy un experto en este campo, pero las pruebas unitarias deberían probar el comportamiento, no la implementación. Los métodos privados son estrictamente parte de la implementación, por lo que en mi humilde opinión no se debe probar.


¿Dónde se prueba la implementación? Si alguna funcionalidad utiliza el almacenamiento en caché, ¿se trata de un detalle de implementación y no se prueba el almacenamiento en caché?
Dirk Herrmann

11

Probamos métodos privados por inferencia, con lo que quiero decir que buscamos una cobertura de prueba de clase total de al menos el 95%, pero solo hacemos que nuestras pruebas recurran a métodos públicos o internos. Para obtener la cobertura, necesitamos hacer múltiples llamadas al público / internos basados ​​en los diferentes escenarios que pueden ocurrir. Esto hace que nuestras pruebas sean más intensas en torno al propósito del código que están probando.

La respuesta de Trumpi a la publicación que ha vinculado es la mejor.


9

Las pruebas unitarias, creo, son para probar métodos públicos. Sus métodos públicos usan sus métodos privados, por lo que indirectamente también se están probando.


7

He estado lidiando con este problema durante un tiempo, especialmente al intentar suerte en TDD.

Me he encontrado con dos publicaciones que creo que abordan este problema con suficiente profundidad en el caso de TDD.

  1. Prueba de métodos privados, TDD y refactorización basada en pruebas
  2. El desarrollo basado en pruebas no está probando

En resumen:

  • Cuando se utilizan técnicas de desarrollo (diseño) basadas en pruebas, los métodos privados deben surgir solo durante el proceso de refactorización de código ya en funcionamiento y probado.

  • Por la naturaleza misma del proceso, cualquier parte de la funcionalidad de implementación simple extraída de una función completamente probada será autoevaluada (es decir, cobertura de prueba indirecta).

Para mí, parece bastante claro que en la parte inicial de la codificación, la mayoría de los métodos serán funciones de nivel superior porque están encapsulando / describiendo el diseño.

Por lo tanto, estos métodos serán públicos y probarlos será bastante fácil.

Los métodos privados vendrán más tarde una vez que todo esté funcionando bien y tengamos en cuenta la legibilidad y la limpieza .


6

Como se citó anteriormente, "Si no prueba sus métodos privados, ¿cómo sabe que no se romperán?"

Este es un problema importante. Uno de los grandes puntos de las pruebas unitarias es saber dónde, cuándo y cómo algo se rompió lo antes posible. Disminuyendo así una cantidad significativa de desarrollo y esfuerzo de control de calidad. Si todo lo que se prueba es el público, entonces no tiene una cobertura honesta y una delineación de los elementos internos de la clase.

He descubierto que una de las mejores formas de hacerlo es simplemente agregar la referencia de prueba al proyecto y poner las pruebas en una clase paralela a los métodos privados. Ponga la lógica de compilación adecuada para que las pruebas no se incorporen al proyecto final.

Entonces tiene todos los beneficios de probar estos métodos y puede encontrar problemas en segundos frente a minutos u horas.

Entonces, en resumen, sí, pruebe sus métodos privados.


2
Estoy en desacuerdo. "Si no prueba sus métodos privados, ¿cómo sabe que no se romperán?" : Sé esto porque si mis métodos privados están rotos, entonces las pruebas que prueban mis métodos públicos que se basan en esos métodos privados fallarán. No quiero tener que cambiar mis pruebas cada vez que cambio de opinión sobre cómo implementar los métodos públicos. También creo que el interés principal de las pruebas unitarias es no saber específicamente qué línea de código es defectuosa, sino que le permite estar más o menos seguro de que no rompió nada al hacer cambios (a los métodos privados).
Tesseract el

6

No deberías . Si sus métodos privados tienen suficiente complejidad que deben probarse, debe colocarlos en otra clase. Mantenga una alta cohesión , una clase debe tener un solo propósito. La interfaz pública de clase debería ser suficiente.


3

Si no prueba sus métodos privados, ¿cómo sabe que no se romperán?


19
Al escribir a través de pruebas de sus métodos públicos.
scubabbl

3
Esos métodos privados son supuestamente llamados por los métodos públicos de la clase. Tan solo pruebe los métodos públicos que llaman a los métodos privados.
Jop

1
Si sus métodos públicos funcionan correctamente, entonces obviamente los métodos privados a los que acceden funcionan correctamente.
17 de 26

Si las pruebas de sus métodos públicos fallan, sabrá instantáneamente que algo no es correcto en un nivel inferior en su objeto / componente / etc.
Rob

3
Sin embargo, es realmente bueno saber que es una función interna y no solo las funciones externas que se rompieron (o, por el contrario, que las funciones internas están bien y puede concentrarse en lo externo).
Adam Davis

2

Obviamente depende del idioma. En el pasado con c ++, he declarado que la clase de prueba es una clase amiga. Desafortunadamente, esto requiere su código de producción para saber acerca de la clase de prueba.


55
La palabra clave amigo me pone triste.
Rob

Esto no es un problema si la clase de prueba se implementa en otro proyecto. Lo importante es que el código de producción no hace referencia a la clase de prueba.
Olumide

2

Entiendo el punto de vista donde los métodos privados se consideran detalles de implementación y luego no tienen que ser probados. Y me apegaría a esta regla si tuviéramos que desarrollarnos solo fuera del objeto. Pero nosotros, ¿somos algún tipo de desarrolladores restringidos que están desarrollando solo fuera de los objetos, llamando solo a sus métodos públicos? ¿O en realidad también estamos desarrollando ese objeto? Como no estamos obligados a programar objetos externos, probablemente tendremos que llamar a esos métodos privados a los nuevos públicos que estamos desarrollando. ¿No sería genial saber que el método privado resiste contra viento y marea?

Sé que algunas personas podrían responder que si estamos desarrollando otro método público en ese objeto, entonces este debería probarse y eso es todo (el método privado podría seguir viviendo sin prueba). Pero esto también es cierto para cualquier método público de un objeto: al desarrollar una aplicación web, todos los métodos públicos de un objeto se invocan desde los métodos de los controladores y, por lo tanto, podrían considerarse como detalles de implementación para los controladores.

Entonces, ¿por qué estamos uniendo objetos de prueba? Porque es realmente difícil, por no decir imposible, estar seguros de que estamos probando los métodos de los controladores con la entrada adecuada que activará todas las ramas del código subyacente. En otras palabras, cuanto más alto estamos en la pila, más difícil es probar todo el comportamiento. Y lo mismo ocurre con los métodos privados.

Para mí, la frontera entre los métodos públicos y privados es un criterio psicológico cuando se trata de pruebas. Los criterios que más me importan son:

  • ¿Se llama el método más de una vez desde diferentes lugares?
  • ¿Es el método lo suficientemente sofisticado como para requerir pruebas?

1

Si encuentro que el método privado es enorme o complejo o lo suficientemente importante como para requerir sus propias pruebas, simplemente lo pongo en otra clase y lo hago público allí (Object Object). Entonces puedo probar fácilmente el método anteriormente privado pero ahora público que ahora vive en su propia clase.


1

Nunca entiendo el concepto de Prueba de Unidad, pero ahora sé cuál es el objetivo.

Una prueba unitaria no es una prueba completa . Por lo tanto, no es un reemplazo para el control de calidad y la prueba manual. El concepto de TDD en este aspecto es incorrecto, ya que no se puede probar todo, incluidos los métodos privados, sino también los métodos que utilizan recursos (especialmente recursos que no tenemos control). TDD basa toda su calidad en algo que no se pudo lograr.

Una prueba de unidad es más una prueba de pivote . Marca un pivote arbitrario y el resultado del pivote debe permanecer igual.


1

Público versus privado no es una distinción útil para qué apis llamar de sus pruebas, ni método vs. clase. La mayoría de las unidades comprobables son visibles en un contexto, pero están ocultas en otros.

Lo que importa es la cobertura y los costos. Debe minimizar los costos mientras logra los objetivos de cobertura de su proyecto (línea, rama, ruta, bloque, método, clase, clase de equivalencia, caso de uso ... lo que decida el equipo).

Por lo tanto, use herramientas para garantizar la cobertura y diseñe sus pruebas para causar los menores costos (a corto y largo plazo ).

No haga que las pruebas sean más caras de lo necesario. Si es más barato probar solo los puntos de entrada públicos, hazlo. Si es más barato probar métodos privados, hazlo.

A medida que tenga más experiencia, será mejor para predecir cuándo vale la pena refactorizar para evitar los costos a largo plazo del mantenimiento de la prueba.


0

Si el método es lo suficientemente significativo / complejo, generalmente lo haré "protegido" y lo probaré. Algunos métodos se dejarán privados y se probarán implícitamente como parte de las pruebas unitarias para los métodos públicos / protegidos.


1
@VisibleForTesting es una anotación para eso. No relajaría la encapsulación para las pruebas, prefiero usar dp4j.com
simpatico

0

Veo que muchas personas están en la misma línea de pensamiento: prueba a nivel público. ¿Pero no es eso lo que hace nuestro equipo de control de calidad? Prueban la entrada y la salida esperada. Si como desarrolladores solo probamos los métodos públicos, simplemente estamos rehaciendo el trabajo de QA y no agregamos ningún valor mediante "pruebas unitarias".


La tendencia actual es reducir o no tener un equipo de control de calidad. Estas pruebas unitarias se convierten en pruebas automatizadas que se ejecutan cada vez que un ingeniero inserta el código en la rama maestra. Incluso con QA, no hay forma de que puedan probar toda la aplicación tan rápido como las pruebas automatizadas.
Patrick Desjardins

0

La respuesta a "¿Debo probar métodos privados?" es a veces". Por lo general, deberías probar con la interfaz de tus clases.

  • Una de las razones es porque no necesita una cobertura doble para una función.
  • Otra razón es que si cambia los métodos privados, tendrá que actualizar cada prueba para ellos, incluso si la interfaz de su objeto no ha cambiado en absoluto.

Aquí hay un ejemplo:

class Thing
  def some_string
    one + two
  end

  private 

  def one
    'aaaa'
  end

  def two
    'bbbb'
  end

end


class RefactoredThing
def some_string
    one + one_a + two + two_b
  end

  private 

  def one
    'aa'
  end

  def one_a
    'aa'
  end

  def two
    'bb'
  end

  def two_b
    'bb'
  end
end

En RefactoredThingahora tiene 5 pruebas, 2 de las cuales era necesario actualizar para refactorización, pero la funcionalidad de su objeto no ha cambiado realmente. Entonces, digamos que las cosas son más complejas que eso y tiene algún método que define el orden de la salida, como:

def some_string_positioner
  if some case
  elsif other case
  elsif other case
  elsif other case
  else one more case
  end
end

Esto no debe ser ejecutado por un usuario externo, pero su clase de encapsulación puede ser demasiado pesada para ejecutar tanta lógica una y otra vez. En este caso, tal vez prefiera extraer esto en una clase separada, darle a esa clase una interfaz y probarla.

Y finalmente, digamos que su objeto principal es súper pesado, y el método es bastante pequeño y realmente necesita asegurarse de que la salida sea correcta. Estás pensando: "Tengo que probar este método privado". ¿Puede que tal vez pueda hacer que su objeto sea más ligero al pasar parte del trabajo pesado como parámetro de inicialización? Entonces puedes pasar algo más ligero y probar contra eso.


0

No No deberías probar los métodos privados ¿por qué? y, además, el marco burlón popular como Mockito no proporciona soporte para probar métodos privados.


0

Un punto principal es

Si probamos para asegurar la corrección de la lógica, y un método privado lleva una lógica, deberíamos probarlo. ¿No es así? Entonces, ¿por qué vamos a saltar eso?

Escribir pruebas basadas en la visibilidad de los métodos es una idea completamente irrelevante.

Por el contrario

Por otro lado, llamar a un método privado fuera de la clase original es un problema principal. Y también hay limitaciones para burlarse de un método privado en algunas herramientas de burla. (Ej: Mockito )

Aunque hay algunas herramientas como Power Mock que lo soporta, es una operación peligrosa. La razón es que necesita hackear la JVM para lograr eso.

Una solución que se puede hacer es (si desea escribir casos de prueba para métodos privados)

Declare esos métodos privados como protegidos . Pero puede no ser conveniente para varias situaciones.


0

No se trata solo de métodos o funciones públicas o privadas, se trata de detalles de implementación. Las funciones privadas son solo un aspecto de los detalles de implementación.

La prueba unitaria, después de todo, es un enfoque de prueba de caja blanca. Por ejemplo, quien usa el análisis de cobertura para identificar partes del código que se han descuidado en las pruebas hasta ahora, entra en los detalles de implementación.

A) Sí, debería probar los detalles de implementación:

Piense en una función de clasificación que, por razones de rendimiento, utiliza una implementación privada de BubbleSort si hay hasta 10 elementos, y una implementación privada de un enfoque de clasificación diferente (digamos, montón) si hay más de 10 elementos. La API pública es la de una función de clasificación. Sin embargo, su conjunto de pruebas utiliza mejor el conocimiento de que en realidad se utilizan dos algoritmos de clasificación.

En este ejemplo, seguramente, podría realizar las pruebas en la API pública. Sin embargo, esto requeriría tener una serie de casos de prueba que ejecuten la función de clasificación con más de 10 elementos, de modo que el algoritmo de ordenación esté suficientemente probado. La existencia de tales casos de prueba solo es una indicación de que el conjunto de pruebas está conectado a los detalles de implementación de la función.

Si los detalles de implementación de la función de clasificación cambian, tal vez en la forma en que el límite entre los dos algoritmos de clasificación se cambia o que el montón se reemplaza por mergesort o lo que sea: las pruebas existentes continuarán funcionando. Sin embargo, su valor es cuestionable, y es probable que necesiten ser reelaborados para probar mejor la función de clasificación modificada. En otras palabras, habrá un esfuerzo de mantenimiento a pesar del hecho de que las pruebas se realizaron en la API pública.

B) Cómo probar los detalles de implementación

Una razón por la cual muchas personas argumentan que uno no debería probar las funciones privadas o los detalles de implementación es que es más probable que los detalles de implementación cambien. Esta mayor probabilidad de cambio al menos es una de las razones para ocultar los detalles de implementación detrás de las interfaces.

Ahora, suponga que la implementación detrás de la interfaz contiene partes privadas más grandes para las cuales las pruebas individuales en la interfaz interna podrían ser una opción. Algunas personas argumentan que estas partes no deben probarse cuando son privadas, sino que deben convertirse en algo público. Una vez público, probar el código de la unidad estaría bien.

Esto es interesante: si bien la interfaz era interna, era probable que cambiara, siendo un detalle de implementación. Al tomar la misma interfaz, hacerla pública hace una transformación mágica, es decir, convertirla en una interfaz que es menos probable que cambie. Obviamente hay algún defecto en esta argumentación.

Sin embargo, hay algo de verdad detrás de esto: cuando se prueban los detalles de implementación, en particular el uso de interfaces internas, uno debe esforzarse por usar interfaces que probablemente se mantengan estables. Sin embargo, si es probable que alguna interfaz sea estable no es simplemente decidible en función de si es pública o privada. En los proyectos del mundo en los que he estado trabajando durante algún tiempo, las interfaces públicas también cambian con bastante frecuencia, y muchas interfaces privadas han permanecido intactas durante siglos.

Aún así, es una buena regla general usar la "puerta principal primero" (ver http://xunitpatterns.com/Principles%20of%20Test%20Automation.html ). Pero tenga en cuenta que se llama "puerta de entrada primero" y no "puerta de entrada solamente".

C) Resumen

Prueba también los detalles de implementación. Prefiere probar en interfaces estables (públicas o privadas). Si los detalles de implementación cambian, también se deben revisar las pruebas en la API pública. Convertir algo privado en público no cambia mágicamente su estabilidad.


0

Sí, debe probar métodos privados, siempre que sea posible. ¿Por qué? Para evitar una explosión innecesaria de espacio de estado de casos de prueba que finalmente terminan probando implícitamente las mismas funciones privadas repetidamente en las mismas entradas. Vamos a explicar por qué con un ejemplo.

Considere el siguiente ejemplo ligeramente artificial. Supongamos que queremos exponer públicamente una función que toma 3 enteros y devuelve verdadero si y solo si esos 3 enteros son primos. Podríamos implementarlo así:

public bool allPrime(int a, int b, int c)
{
  return andAll(isPrime(a), isPrime(b), isPrime(c))
}

private bool andAll(bool... boolArray)
{
  foreach (bool b in boolArray)
  {
    if(b == false) return false;
  }
  return true;
}

private bool isPrime(int x){
  //Implementation to go here. Sorry if you were expecting a prime sieve.
}

Ahora, si tuviéramos que adoptar el enfoque estricto de que solo se deberían probar las funciones públicas, solo se nos permitiría probar allPrimey no isPrimeo andAll.

Como probador, podemos estar interesados en cinco posibilidades para cada argumento: < 0, = 0, = 1, prime > 1, not prime > 1. Pero para ser exhaustivos, también tendríamos que ver cómo se combinan todas las combinaciones de los argumentos. Entonces eso es 5*5*5= 125 casos de prueba que necesitaríamos probar a fondo esta función, de acuerdo con nuestras intuiciones.

Por otro lado, si se nos permitiera probar las funciones privadas, podríamos cubrir tanto terreno con menos casos de prueba. Necesitaríamos solo 5 casos de prueba isPrimepara probar al mismo nivel que nuestra intuición anterior. Y según la hipótesis de pequeño alcance propuesta por Daniel Jackson, solo tendríamos que probar la andAllfunción hasta una longitud pequeña, por ejemplo, 3 o 4. Lo que sería como máximo 16 pruebas más. Entonces 21 pruebas en total. En lugar de 125. Por supuesto, probablemente querríamos ejecutar algunas pruebas enallPrime , pero no nos sentiríamos tan obligados a cubrir exhaustivamente las 125 combinaciones de escenarios de entrada que dijimos que nos preocupaban. Solo algunos senderos felices.

Un ejemplo artificial, seguro, pero era necesario para una demostración clara. Y el patrón se extiende al software real. Las funciones privadas son generalmente los bloques de construcción de nivel más bajo y, por lo tanto, a menudo se combinan para producir una lógica de nivel más alto. Es decir, en niveles superiores, tenemos más repeticiones de las cosas de nivel inferior debido a las diversas combinaciones.


Primero, no tienes que probar combinaciones como esa con funciones puras como las que has mostrado. Las llamadas a isPrimeson verdaderamente independientes, por lo que probar cada combinación a ciegas no tiene ningún propósito. En segundo lugar, marcar una función pura llamada isPrimeprivada viola tantas reglas de diseño que ni siquiera sé por dónde empezar. isPrimeclaramente debe ser una función pública. Dicho esto, entiendo lo que estás diciendo, independientemente de este ejemplo extremadamente pobre. Sin embargo, se basa en la premisa de que desearía realizar pruebas de combinación, cuando en sistemas de software reales rara vez es una buena idea.
Matt Messersmith

Matt, sí, el ejemplo no es ideal, te lo daré. Pero el principio debería ser obvio.
Colm Bhandal

La premisa no es exactamente que quieras hacer pruebas de combinación. Es necesario que lo haga si se limita a probar solo las piezas públicas del rompecabezas. Hay casos en los que desea que una función pura sea privada para cumplir con los principios de encapsulación adecuados. Y esta función privada pura podría ser utilizada por las públicas. De forma combinatoria, con otras funciones privadas puras quizás. En ese caso, siguiendo el dogma de que no probará privado, se vería obligado a hacer pruebas de combinación en la función pública en lugar de hacer pruebas modulares de los componentes privados.
Colm Bhandal

0

También puede hacer que su método sea privado de paquete, es decir, predeterminado y debería poder probarlo a menos que sea necesario que sea privado.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.