Hacer público un método privado para probarlo ... ¿buena idea?

301

Nota para el moderador: ya hay 39 respuestas publicadas aquí (algunas se han eliminado). Antes de publicar su respuesta, considere si puede agregar algo significativo a la discusión. Es más que probable que solo estés repitiendo lo que otra persona ya ha dicho.


Ocasionalmente me encuentro necesitando hacer un método privado en una clase pública solo para escribir algunas pruebas unitarias para él.

Por lo general, esto se debe a que el método contiene lógica compartida entre otros métodos en la clase y es más ordenado probar la lógica por sí mismo, o podría ser posible otra razón si quiero probar la lógica utilizada en subprocesos sincrónicos sin tener que preocuparme por problemas de subprocesos .

¿Se encuentran otras personas haciendo esto, porque realmente no me gusta hacerlo? Personalmente, creo que los bonos superan los problemas de hacer público un método que realmente no proporciona ningún servicio fuera de la clase ...

ACTUALIZAR

Gracias por las respuestas a todos, parece haber despertado el interés de la gente. Creo que el consenso general es que las pruebas deberían realizarse a través de la API pública, ya que esta es la única forma en que se utilizará una clase, y estoy de acuerdo con esto. Los dos casos que mencioné anteriormente donde haría esto anteriormente fueron casos poco comunes y pensé que los beneficios de hacerlo valieron la pena.

Sin embargo, puedo ver que todos señalan que nunca debería suceder realmente. Y cuando lo pienso un poco más, creo que cambiar su código para acomodar las pruebas es una mala idea, después de todo, supongo que las pruebas son una herramienta de soporte de alguna manera y cambiar un sistema para 'soportar una herramienta de soporte' si lo desea, es descarado mala práctica.

jcvandan
fuente
2
"cambiar un sistema para 'soportar una herramienta de soporte' si lo desea, es una mala práctica descarada". Bueno, sí, más o menos. Pero OTOH si su sistema no funciona bien con las herramientas establecidas, tal vez algo esté mal con el sistema.
Thilo
1
¿Cómo no es este duplicado de stackoverflow.com/questions/105007/do-you-test-private-method ?
Sanghyun Lee
1
No es una respuesta, pero siempre puedes acceder a ellos por reflexión.
Zano
33
No, no debes exponerlos. En cambio, debe probar que su clase se comporta como debería hacerlo, a través de su API pública. Y si exponer las partes internas es realmente la única opción (que dudo), entonces al menos debe proteger el paquete de accesores, de modo que solo las clases en el mismo paquete (como debería ser su prueba) puedan acceder a ellos.
JB Nizet
44
Sí lo es. Es perfectamente aceptable dar visibilidad a un método de paquete privado (predeterminado) para que sea accesible desde las pruebas unitarias. Las bibliotecas como Guava incluso proporcionan una @VisibileForTestinganotación para hacer tales métodos; le recomiendo que haga esto para que la razón por la cual el método no privateesté debidamente documentado.
Boris the Spider

Respuestas:

186

Nota:
Esta respuesta se publicó originalmente para la pregunta ¿Las pruebas unitarias solas alguna vez son una buena razón para exponer variables de instancia privadas a través de getters? que se fusionó con este, por lo que puede ser un poco específico para el caso de uso presentado allí.

Como una declaración general, generalmente estoy a favor de refactorizar el código de "producción" para que sea más fácil de probar. Sin embargo, no creo que sea una buena decisión aquí. Una buena prueba unitaria (por lo general) no debería preocuparse por los detalles de implementación de la clase, solo por su comportamiento visible. En lugar de exponer las pilas internas a la prueba, puede probar que la clase devuelve las páginas en el orden que espera después de llamar first()o last().

Por ejemplo, considere este pseudocódigo:

public class NavigationTest {
    private Navigation nav;

    @Before
    public void setUp() {
        // Set up nav so the order is page1->page2->page3 and
        // we've moved back to page2
        nav = ...;
    }

    @Test
    public void testFirst() {
        nav.first();

        assertEquals("page1", nav.getPage());

        nav.next();
        assertEquals("page2", nav.getPage());

        nav.next();
        assertEquals("page3", nav.getPage());
    }

    @Test
    public void testLast() {
        nav.last();

        assertEquals("page3", nav.getPage());

        nav.previous();
        assertEquals("page2", nav.getPage());

        nav.previous();
        assertEquals("page1", nav.getPage());
    }
}
Mureinik
fuente
8
Estoy completamente de acuerdo con tu respuesta. Muchos desarrolladores se verían tentados a usar el modificador de acceso predeterminado y justificar que los buenos marcos de código abierto usan esta estrategia, por lo que debe ser correcta. Solo porque puedas no significa que debas. +1
CKing
66
Aunque otros han proporcionado ideas útiles, debo decir que esta es la solución que considero más adecuada para este problema. Mantiene las pruebas completamente independientes de cómo se logra internamente el comportamiento previsto. +10!
Gitahi Ng'ang'a
Por cierto, ¿está bien que en el ejemplo proporcionado por @Mureinik aquí, los métodos de prueba terminen cubriendo más que el método real bajo prueba? Por ejemplo, testFirst () llama a next (), que no es realmente el método que se prueba aquí, primero es (). ¿No sería más y más claro tener solo 1 método de prueba que cubra los 4 métodos de navegación?
Gitahi Ng'ang'a
1
Si bien este es el ideal, hay momentos en los que debe verificar que un componente hizo algo de la manera correcta, no solo que tuvo éxito. Esto puede ser más difícil para la prueba de caja negra. En algún momento tienes que probar un componente en una caja blanca.
Peter Lawrey
1
¿Crear captadores solo por probar las unidades? Esto definitivamente va en contra del pensamiento de OOP / Object en que la interfaz debe exponer el comportamiento, no los datos.
IntelliData
151

Personalmente, prefiero realizar una prueba de unidad con la API pública y, desde luego, nunca haría público el método privado solo para facilitar la prueba.

Si realmente desea probar el método privado de forma aislada, en Java podría usar Easymock / Powermock para permitirle hacer esto.

Debe ser pragmático al respecto y también debe ser consciente de las razones por las que las cosas son difíciles de probar.

' Escucha las pruebas ': si es difícil de probar, ¿te está diciendo algo sobre tu diseño? ¿Podría refactorizar a dónde una prueba para este método sería trivial y fácilmente cubierta mediante pruebas a través de la API pública?

Esto es lo que Michael Feathers tiene que decir en ' Trabajar eficazmente con código heredado "

"Muchas personas pasan mucho tiempo tratando de descubrir cómo solucionar este problema ... la verdadera respuesta es que si tiene la necesidad de probar un método privado, el método no debería ser privado; si hace público el método te molesta, lo más probable es que sea porque es parte de una responsabilidad por separado; debería estar en otra clase ". [ Trabajando efectivamente con el código heredado (2005) por M. Feathers]

blanco
fuente
55
+1 para Easymock / Powermock y nunca haga público un método privado
Leonard Brünings
51
+1 Para "Escuchar las pruebas". Si siente la necesidad de probar un método privado, es muy probable que la lógica en ese método sea tan compleja que debería estar en una clase auxiliar separada. Entonces puedes probar la clase auxiliar.
Phil
2
'Escuchar las pruebas' parece estar inactivo. Aquí hay un enlace en caché: webcache.googleusercontent.com/…
Jess Telford
1
Estoy de acuerdo con @Phil (especialmente la referencia del libro), pero a menudo me encuentro en una situación de huevo / gallina con código heredado. Sé que este método pertenece a otra clase, sin embargo, no estoy 100% cómodo refactorizando sin pruebas en primer lugar.
Kevin
Estoy de acuerdo doctor. Si necesita probar un método privado, probablemente debería estar en otra clase. Puede notar que la otra clase / clase auxiliar que el método privado es probable que se vuelva pública / protegida.
rupweb
67

Como han dicho otros, es algo sospechoso que la unidad pruebe métodos privados; la unidad prueba la interfaz pública, no los detalles de implementación privados.

Dicho esto, la técnica que uso cuando quiero probar la unidad algo que es privado en C # es degradar la protección de accesibilidad de privado a interno, y luego marcar el ensamblaje de prueba de la unidad como un ensamblaje amigo usando InternalsVisibleTo . El conjunto de prueba de la unidad podrá tratar las partes internas como públicas, pero no tiene que preocuparse por agregar accidentalmente a su área de superficie pública.

Eric Lippert
fuente
También uso esta técnica, pero como último recurso para el código heredado por lo general. Si necesita probar el código privado, le falta una clase / la clase bajo prueba está haciendo demasiado. Extraiga dicho código en una nueva clase que puede probar de forma aislada. Trucos como este deberían usarse raramente.
Finglas
Pero extraer una clase y probar solo eso significa que sus pruebas pierden el conocimiento de que la clase original está usando el código extraído. ¿Cómo recomiendas llenar este vacío en tus pruebas? Y si la respuesta es simplemente probar la interfaz pública de la clase original de una manera que haga que se use la lógica extraída, ¿por qué molestarse en extraerla en primer lugar? (No pretendo sonar confrontacional aquí, por cierto, siempre me he preguntado cómo las personas equilibran tales compensaciones)
Mal Ross
@mal Me gustaría hacer una prueba de colaboración. En otras palabras, se invocó correctamente un simulacro para verificar la nueva clase. Vea mi respuesta a continuación.
Finglas
¿Podría uno escribir una clase de prueba que herede de la clase con la lógica interna que desea probar y proporcionar un método público para usar para probar la lógica interna?
sholsinger
1
@sholsinger: en C #, una clase pública no puede heredar de una clase interna.
Eric Lippert
45

Muchas respuestas sugieren solo probar la interfaz pública, pero en mi humilde opinión, esto no es realista: si un método hace algo que lleva 5 pasos, querrás probar esos cinco pasos por separado, no todos juntos. Esto requiere probar los cinco métodos, que (aparte de las pruebas) podrían serlo de otra manera private.

La forma habitual de probar métodos "privados" es dar a cada clase su propia interfaz y hacer los métodos "privados" public, pero no incluirlos en la interfaz. De esta manera, todavía se pueden probar, pero no hinchan la interfaz.

Sí, esto resultará en hinchazón de archivos y clases.

Sí, esto hace que los especificadores publicy sean privateredundantes.

Sí, esto es un dolor en el culo.

Este es, desafortunadamente, uno de los muchos sacrificios que hacemos para que el código sea comprobable . Quizás un lenguaje futuro (o incluso una versión futura de C # / Java) tendrá características para hacer que la capacidad de prueba de clase y módulo sea más conveniente; pero mientras tanto, tenemos que saltar a través de estos aros.


Hay quienes argumentan que cada uno de esos pasos debería ser su propia clase , pero no estoy de acuerdo: si todos comparten el estado, no hay razón para crear cinco clases separadas donde cinco métodos funcionarían. Peor aún, esto da como resultado una hinchazón de archivos y clases. Además , infecta la API pública de su módulo: todas esas clases deben ser necesariamente publicsi desea probarlas desde otro módulo (ya sea eso o incluir el código de prueba en el mismo módulo, lo que significa enviar el código de prueba con su producto) .

BlueRaja - Danny Pflughoeft
fuente
2
buena respuesta para la primera mitad
cmcginty
77
+1: La mejor respuesta para mí. Si un fabricante de automóviles no prueba una parte de su automóvil por la misma razón, no estoy seguro de que alguien lo compre. Una parte importante del código debe probarse incluso si debemos cambiarlo a público.
Tae-Sung Shin
1
Muy buena respuesta. Tienes mi cálido voto. Agregué una respuesta. Te puede interesar.
davidxxx
29

Una prueba unitaria debe probar el contrato público, la única forma en que una clase podría usarse en otras partes del código. Un método privado son los detalles de implementación, no debe probarlo, en la medida en que la API pública funcione correctamente, la implementación no importa y podría cambiarse sin cambios en los casos de prueba.

kan
fuente
+1, y una respuesta real
Raedwald
Afair, la rara situación en la que alguna vez decidí usar elementos privados en un caso de prueba fue cuando estaba verificando la corrección de un objeto, si ha cerrado todos los controladores JNI. Obviamente, no está expuesto como API pública, sin embargo, si no sucede, se produce una pérdida de memoria. Pero curioso, más tarde también lo eliminé después de haber introducido un detector de pérdida de memoria como parte de una API pública.
kan
11
Esta es una lógica defectuosa. El punto de la prueba unitaria es detectar errores más temprano que tarde. Al no probar métodos privados, está dejando vacíos en sus pruebas que pueden no ser descubiertos hasta mucho más tarde. En el caso de que el método privado sea complejo y no sea fácilmente ejercido por una interfaz pública, debe probarse de forma unitaria.
cmcginty
66
@Casey: si el método privado no es ejercido por una interfaz pública, ¿por qué está ahí?
Thilo
2
@DaSilva_Ireland: Será difícil mantener casos de prueba en el futuro. El único caso de uso de clase real es a través del método público. Es decir, todos los demás códigos podrían usar la clase solo a través de API pública. Por lo tanto, si desea cambiar la implementación y la falla del caso de prueba del método privado, no significa que esté haciendo algo mal, además, si está probando solo privado, pero no está probando público en su totalidad, no cubrirá algunos errores potenciales . Idealmente, un caso de prueba debería cubrir todos los casos posibles y solo los casos de uso real de la clase.
kan
22

En mi opinión, debe escribir sus pruebas sin hacer suposiciones profundas sobre cómo su clase se implementó en su interior. Probablemente desee refactorizarlo más tarde utilizando otro modelo interno pero aún haciendo las mismas garantías que brinda la implementación anterior.

Teniendo esto en cuenta, le sugiero que se concentre en probar que su contrato aún se mantiene sin importar la implementación interna que tenga actualmente su clase. Pruebas basadas en propiedades de sus API públicas.

SimY4
fuente
66
Estoy de acuerdo en que las pruebas unitarias que no necesitan ser reescritas después de una refactorización del código son mejores. Y no solo por el tiempo que pasaría reescribiendo las pruebas unitarias. Una razón mucho más importante es que considero que el propósito más importante de las pruebas unitarias es que su código aún se comporta correctamente después de una refactorización. Si tiene que reescribir todas sus pruebas unitarias después de una refactorización, entonces pierde ese beneficio de las pruebas unitarias.
kasperd
20

¿Qué tal hacer que el paquete sea privado? Entonces su código de prueba puede verlo (y también otras clases en su paquete), pero aún está oculto para sus usuarios.

Pero realmente, no deberías probar métodos privados. Esos son detalles de implementación y no forman parte del contrato. Todo lo que hacen debe cubrirse llamando a los métodos públicos (si tienen un código que no es ejercido por los métodos públicos, entonces eso debería desaparecer). Si el código privado es demasiado complejo, la clase probablemente está haciendo demasiadas cosas y necesita refactorizar.

Hacer público un método es un gran compromiso. Una vez que hagas eso, las personas podrán usarlo, y ya no podrás cambiarlos.

Thilo
fuente
+1 si necesita probar sus partes privadas probablemente se esté perdiendo una clase
jk.
+1 por clase faltante. Me alegra ver que otros no se suban al carro de "marca interna" de inmediato.
Finglas
Chico, tuve que recorrer un largo camino para encontrar la respuesta privada del paquete.
Bill K
17

Actualización : he agregado una respuesta más amplia y más completa a esta pregunta en muchos otros lugares. Esto se puede encontrar en mi blog .

Si alguna vez necesito hacer algo público para probarlo, esto generalmente sugiere que el sistema bajo prueba no está siguiendo el Principio de Responsabilidad Única . Por lo tanto, falta una clase que debería introducirse. Después de extraer el código en una nueva clase, hazlo público. Ahora puedes probar fácilmente y estás siguiendo SRP. Su otra clase simplemente tiene que invocar esta nueva clase a través de la composición.

Hacer públicos los métodos / usar trucos de lenguaje, como marcar el código como visible para los ensambles de prueba, siempre debe ser un último recurso.

Por ejemplo:

public class SystemUnderTest
{
   public void DoStuff()
   {
      // Blah
      // Call Validate()
   }

   private void Validate()
   {
      // Several lines of complex code...
   }
}

Refactorice esto introduciendo un objeto validador.

public class SystemUnderTest
{
    public void DoStuff()
    {
       // Blah
       validator.Invoke(..)
    }
}

Ahora todo lo que tenemos que hacer es probar que el validador se invoque correctamente. El proceso real de validación (la lógica previamente privada) se puede probar de forma aislada. No será necesario configurar pruebas complejas para garantizar que se apruebe esta validación.

Finglas
fuente
1
Personalmente, creo que SRP se puede llevar demasiado lejos, por ejemplo, si escribo un servicio de cuenta que se ocupa de actualizar / crear / eliminar cuentas en una aplicación, ¿me está diciendo que debe crear una clase separada para cada operación? ¿Y qué pasa con la validación, por ejemplo, realmente vale la pena extraer la lógica de validación a otra clase cuando nunca se usará en otro lugar? ¡Imo que solo hace que el código sea menos legible, menos conciso y empaqueta su aplicación con clases superfluas!
jcvandan
1
Todo depende del escenario. La definición de una persona de "hacer una cosa" puede ser diferente de otra. En este caso, el código privado que desea probar es complejo / difícil de configurar. El hecho mismo de que desee probarlo demuestra que está haciendo demasiado. Por lo tanto, sí, tener un nuevo objeto será ideal.
Finglas
1
En cuanto a hacer que su código sea menos legible, no estoy de acuerdo. Tener una clase para la validación es más fácil de leer que tener la validación incrustada en el otro objeto. Todavía tiene la misma cantidad de código, simplemente no está anidado en una clase grande. Prefiero tener varias clases que hagan una cosa muy bien, que una clase grande.
Finglas
44
@dormisher, con respecto al servicio de su cuenta, piense en el SRP como una "única razón para cambiar". Si sus operaciones CRUD naturalmente cambiarían juntas, entonces se adaptarían al SRP. Normalmente, los cambiaría porque el esquema de la base de datos podría cambiar, lo que naturalmente afectaría la inserción y actualización (aunque quizás no siempre se elimine).
Anthony Pegram
@finglas lo que piensas sobre esto: stackoverflow.com/questions/29354729/… . No tengo ganas de probar el método privado, por lo que tal vez no debería separarlo en una clase, pero se está utilizando en otros 2 métodos públicos, por lo que terminaría probando dos veces la misma lógica.
Rodrigo Ruiz
13

Algunas buenas respuestas. Algo que no vi mencionado es que con el desarrollo basado en pruebas (TDD), se crean métodos privados durante la fase de refactorización (consulte el Método de extracción para ver un ejemplo de un patrón de refactorización) y, por lo tanto, ya debería tener la cobertura de prueba necesaria . Si se hace correctamente (y, por supuesto, obtendrá una mezcla de opiniones en lo que respecta a la corrección), no debería tener que preocuparse por tener que hacer público un método privado para poder probarlo.

csano
fuente
11

¿Por qué no dividir el algoritmo de gestión de la pila en una clase de utilidad? La clase de utilidad puede administrar las pilas y proporcionar accesores públicos. Sus pruebas unitarias pueden centrarse en los detalles de implementación. Las pruebas profundas para clases algorítmicamente complicadas son muy útiles para eliminar casos extremos y garantizar la cobertura.

Entonces su clase actual puede delegar limpiamente a la clase de utilidad sin exponer ningún detalle de implementación. Sus pruebas se relacionarán con el requisito de paginación que otros han recomendado.

Alfred Armstrong
fuente
10

En Java, también existe la opción de hacer que el paquete sea privado (es decir, omitir el modificador de visibilidad). Si sus pruebas unitarias están en el mismo paquete que la clase que se está probando, entonces debería poder ver estos métodos, y es un poco más seguro que hacer que el método sea completamente público.

Tom Jefferys
fuente
10

Los métodos privados generalmente se usan como métodos "auxiliares". Por lo tanto, solo devuelven valores básicos y nunca operan en instancias específicas de objetos.

Tienes un par de opciones si quieres probarlas.

  • Usar reflejo
  • Dar acceso al paquete de métodos

Alternativamente, podría crear una nueva clase con el método auxiliar como método público si es un candidato suficientemente bueno para una nueva clase.

Hay un muy buen artículo aquí sobre esto.

adamjmarkham
fuente
1
Comentario justo Era solo una opción, quizás una mala.
adamjmarkham
@Finglas ¿Por qué probar código privado usando la reflexión es una mala idea?
Anshul
Los métodos privados de @Anshul son detalles de implementación y no comportamiento. En otras palabras, cambian. Los nombres cambian. Los parámetros cambian. Cambio de contenido. Esto significa que estas pruebas se romperán todo el tiempo. Los métodos públicos, por otro lado, son mucho más estables en términos de API, por lo que son más resistentes. Al proporcionar sus pruebas sobre métodos públicos, verifique el comportamiento y no los detalles de implementación, debería ser bueno.
Finglas
1
@Finglas ¿Por qué no quieres probar los detalles de implementación? Es un código que debe funcionar, y si cambia más a menudo, espera que se rompa con más frecuencia, lo cual es una razón más para probarlo más que la API pública. Después de que su base de código sea estable, supongamos que en algún momento en el futuro alguien viene y cambia un método privado que interrumpe el funcionamiento interno de su clase. Una prueba que pruebe las partes internas de su clase les dirá inmediatamente que rompieron algo. Sí, dificultaría el mantenimiento de sus pruebas, pero eso es algo que puede esperar de las pruebas de cobertura total.
Anshul
1
Los detalles de implementación de @Finglas Testing no están mal. Esa es su postura al respecto, pero este es un tema ampliamente debatido. Probar las partes internas le brinda algunas ventajas, aunque puede que no sean necesarias para una cobertura total. Sus pruebas están más enfocadas porque está probando los métodos privados directamente en lugar de pasar por la API pública, lo que puede confundir su prueba. Las pruebas negativas son más fáciles porque puede invalidar manualmente los miembros privados y asegurarse de que la API pública devuelva los errores apropiados en respuesta al estado interno inconsistente. Hay mas ejemplos.
Anshul
9

Si está utilizando C #, puede hacer que el método sea interno. De esa manera no contamina la API pública.

Luego agregue atributo a dll

[ensamblaje: InternalsVisibleTo ("MyTestAssembly")]

Ahora todos los métodos son visibles en su proyecto MyTestAssembly. Tal vez no sea perfecto, pero mejor que hacer público el método privado solo para probarlo.

Piotr Perak
fuente
7

Use la reflexión para acceder a las variables privadas si es necesario.

Pero realmente, no te importa el estado interno de la clase, solo quieres probar que los métodos públicos devuelven lo que esperas en las situaciones que puedes anticipar.

Daniel Alexiuc
fuente
¿Qué harías con un método vacío?
IntelliData
@IntelliData Use la reflexión para acceder a las variables privadas si es necesario.
Daniel Alexiuc
6

Diría que es una mala idea porque no estoy seguro de si obtiene algún beneficio y potencialmente problemas en el futuro. Si está cambiando el contrato de una llamada, solo para probar un método privado, no está probando la clase sobre cómo se usaría, sino creando un escenario artificial que nunca tuvo la intención de que suceda.

Además, al declarar el método como público, ¿qué decir que dentro de seis meses (después de olvidar que la única razón para hacer público un método es para probar) que usted (o si ha entregado el proyecto) alguien completamente diferente ganó No lo use, lo que puede tener consecuencias no deseadas y / o una pesadilla de mantenimiento.

beny23
fuente
1
¿Qué pasa si la clase es la implementación de un contrato de servicio y solo se usa a través de su interfaz? De esta manera, incluso si el método privado se ha hecho público, no se llamará de todos modos ya que no es visible
jcvandan
Todavía me gustaría probar la clase usando la API pública (interfaz), porque de lo contrario si refactoriza la clase para dividir el método interno, entonces tendría que cambiar las pruebas, lo que significa que tendría que gastar un poco esfuerzo para garantizar que las nuevas pruebas funcionen igual que las anteriores.
beny23
Además, si la única forma de garantizar que la cobertura de la prueba incluya todos los casos límite dentro de un método privado es llamarlo directamente, puede ser indicativo de que la complejidad de la clase justifica la refactorización para simplificarlo.
beny23
@dormisher: si la clase solo se usa a través de la interfaz, solo necesita probar que los métodos de la interfaz pública se comporten correctamente. Si los métodos públicos hacen lo que deben hacer, ¿por qué te interesan los privados?
Andrzej Doyle
@Andrzej: supongo que no debería, pero hay situaciones en las que creo que podría ser válido, por ejemplo, un método privado que valida el estado del modelo, puede valer la pena probar un método como este, pero un método como este debería ser comprobable a través de la interfaz pública de todos modos
jcvandan
6

en términos de pruebas unitarias, definitivamente no debe agregar más métodos; Creo que first()será mejor que haga un caso de prueba sobre su método, que se llamaría antes de cada prueba; entonces puede llamar varias veces al - next(), previous()y last()para ver si los resultados coinciden con sus expectativas. Supongo que si no agrega más métodos a su clase (solo para fines de prueba), se apegaría al principio de prueba de "recuadro negro";

Johny
fuente
5

Primero vea si el método debe extraerse en otra clase y hacerse público. Si ese no es el caso, proteja el paquete y anote en Java con @VisibleForTesting .

Noel Yap
fuente
5

En su actualización, dice que es bueno simplemente probar usando la API pública. En realidad hay dos escuelas aquí.

  1. Prueba de caja negra

    La escuela de caja negra dice que la clase debe considerarse como una caja negra para que nadie pueda ver la implementación dentro de ella. La única forma de probar esto es a través de la API pública, tal como lo usará el usuario de la clase.

  2. prueba de caja blanca.

    La escuela de caja blanca cree que es natural usar el conocimiento sobre la implementación de la clase y luego probar la clase para saber que funciona como debería.

Realmente no puedo tomar partido en la discusión. Simplemente pensé que sería interesante saber que hay dos formas distintas de probar una clase (o una biblioteca o lo que sea).

bengtb
fuente
4

En realidad, hay situaciones en las que debe hacer esto (por ejemplo, cuando implementa algunos algoritmos complejos). Solo hazlo paquete privado y esto será suficiente. Pero en la mayoría de los casos, probablemente tenga clases demasiado complejas, lo que requiere descomponer la lógica en otras clases.

Stanislav Bashkyrtsev
fuente
4

Los métodos privados que desea probar de forma aislada son una indicación de que hay otro "concepto" oculto en su clase. Extraiga ese "concepto" a su propia clase y pruébelo como una "unidad" separada.

Echa un vistazo a este video para una versión realmente interesante sobre el tema.

Jordão
fuente
4

Nunca debes dejar que tus pruebas dicten tu código. No estoy hablando de TDD u otros DD, quiero decir, exactamente lo que estás preguntando. ¿Su aplicación necesita esos métodos para ser pública? Si es así, pruébelos. Si no es así, entonces no los haga públicos solo para probarlos. Lo mismo con las variables y otras. Deje que las necesidades de su aplicación dicten el código, y deje que sus pruebas prueben que se satisface la necesidad. (Nuevamente, no me refiero a la prueba primero o no, me refiero a cambiar la estructura de una clase para cumplir con un objetivo de prueba).

En cambio, debe "probar más alto". Pruebe el método que llama al método privado. Pero sus pruebas deberían probar las necesidades de sus aplicaciones y no sus "decisiones de implementación".

Por ejemplo (pseudocódigo bod aquí);

   public int books(int a) {
     return add(a, 2);
   }
   private int add(int a, int b) {
     return a+b;
   } 

No hay razón para probar "agregar", puede probar "libros" en su lugar.

Nunca permita que sus pruebas tomen decisiones de diseño de código por usted. Comprueba que obtienes el resultado esperado, no cómo obtienes ese resultado.

coteyr
fuente
1
"Nunca dejes que tus pruebas tomen decisiones de diseño de código por ti" - Debo estar en desacuerdo. Ser difícil de probar es un olor a código. Si no puede probarlo, ¿cómo sabe que no está roto?
Alfred Armstrong
Para aclarar, acepto que no debe cambiar el diseño de API externo de una biblioteca, por ejemplo. Pero es posible que deba refactorizar para que sea más comprobable.
Alfred Armstrong
He descubierto que si necesita refactorizar para probar, ese no es el olor del código del que debe preocuparse. Tienes algo más mal. Por supuesto, esa es una experiencia personal, y ambos probablemente podríamos discutir con datos sólidos en ambos sentidos. Simplemente nunca he tenido "difícil de probar" que no era "diseño pobre" en primer lugar.
coteyr
Esta solución es buena para un método como libros que devuelve un valor: puede verificar el tipo de retorno en una afirmación ; pero, ¿cómo harías para verificar un método nulo ?
IntelliData
los métodos nulos hacen algo o no necesitan existir. Comprueba lo que hacen,
coteyr
2

Tiendo a estar de acuerdo en que las bonificaciones de tener su unidad probada superan los problemas de aumentar la visibilidad de algunos de los miembros. Una ligera mejora es hacerlo protegido y virtual, luego anularlo en una clase de prueba para exponerlo.

Alternativamente, si es la funcionalidad que desea probar por separado, ¿no sugiere un objeto faltante en su diseño? Tal vez podría ponerlo en una clase comprobable por separado ... luego su clase existente simplemente delega a una instancia de esta nueva clase.

IanR
fuente
2

Generalmente mantengo las clases de prueba en el mismo proyecto / ensamblaje que las clases bajo prueba.
De esta manera solo necesito internalvisibilidad para hacer que las funciones / clases sean comprobables.

Esto complica un poco su proceso de construcción, que necesita filtrar las clases de prueba. Lo logro nombrando todas mis clases de prueba TestedClassTesty usando una expresión regular para filtrar esas clases.

Por supuesto, esto solo se aplica a la parte C # / .NET de su pregunta

yas4891
fuente
2

Menudo voy a añadir un método llamado algo así como validate, verify, check, etc, a una clase de modo que pueda ser llamado para poner a prueba el estado interno de un objeto.

A veces, este método está envuelto en un bloque ifdef (escribo principalmente en C ++) para que no se compile para su lanzamiento. Pero a menudo es útil en el lanzamiento para proporcionar métodos de validación que recorren los árboles de objetos del programa comprobando cosas.

Zan Lynx
fuente
2

La guayaba tiene una anotación @VisibleForTesting para marcar métodos que tienen un alcance ampliado (paquete o público) que de lo contrario lo harían. Yo uso una anotación @Private para lo mismo.

Si bien la API pública debe probarse, a veces es conveniente y sensato llegar a cosas que normalmente no serían públicas.

Cuando:

  • una clase se vuelve significativamente menos legible, in toto, al dividirla en varias clases,
  • solo para hacerlo más comprobable,
  • y proporcionar algún acceso de prueba a las entrañas haría el truco

Parece que la religión está superando a la ingeniería.

Ed Staub
fuente
2

Por lo general, dejo esos métodos como protectedy coloco la prueba unitaria dentro del mismo paquete (pero en otro proyecto o carpeta de origen), donde pueden acceder a todos los métodos protegidos porque el cargador de clases los colocará dentro del mismo espacio de nombres.

hiergiltdiestfu
fuente
2

No, porque hay mejores formas de desollar a ese gato.

Algunos arneses de prueba unitaria se basan en macros en la definición de clase que se expanden automáticamente para crear ganchos cuando se construyen en modo de prueba. Muy estilo C, pero funciona.

Un modismo OO más fácil es hacer que todo lo que desee probar sea "protegido" en lugar de "privado". El arnés de prueba hereda de la clase bajo prueba y luego puede acceder a todos los miembros protegidos.

O opta por la opción "amigo". Personalmente, esta es la característica de C ++ que menos me gusta porque rompe las reglas de encapsulación, pero resulta necesario para la forma en que C ++ implementa algunas características, así que, hola.

De todos modos, si realiza pruebas unitarias, es probable que necesite inyectar valores en esos miembros. Los mensajes de texto de caja blanca son perfectamente válidos. Y eso realmente sería romper su encapsulación.

Graham
fuente
2

En .Net hay una clase especial llamada PrivateObjectdiseñada específicamente para permitirle acceder a métodos privados de una clase.

Vea más sobre esto en MSDN o aquí en Stack Overflow

(Me pregunto que nadie lo haya mencionado hasta ahora).

Sin embargo, hay situaciones en las que esto no es suficiente, en cuyo caso tendrá que usar la reflexión.

Aún así, me adheriría a la recomendación general de no probar métodos privados, sin embargo, como siempre, siempre hay excepciones.

Yoel Halb
fuente
1

Como se observa ampliamente en los comentarios de otros, las pruebas unitarias deben centrarse en la API pública. Sin embargo, dejando de lado los pros / contras y la justificación, puede llamar a métodos privados en una prueba unitaria utilizando la reflexión. Por supuesto, deberá asegurarse de que su seguridad JRE lo permita. Llamar a métodos privados es algo que Spring Framework emplea con sus ReflectionUtils (vea el makeAccessible(Method)método).

Aquí hay una pequeña clase de ejemplo con un método de instancia privada.

public class A {
    private void doSomething() {
        System.out.println("Doing something private.");
    }
}

Y una clase de ejemplo que ejecuta el método de instancia privada.

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class B {
    public static final void main(final String[] args) {
        try {
            Method doSomething = A.class.getDeclaredMethod("doSomething");
            A o = new A();
            //o.doSomething(); // Compile-time error!
            doSomething.setAccessible(true); // If this is not done, you get an IllegalAccessException!
            doSomething.invoke(o);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        }
    }
}

Al ejecutar B, se imprimirá. Doing something private. Si realmente lo necesita, la reflexión podría usarse en pruebas unitarias para acceder a métodos de instancias privadas.

Dan Cruz
fuente
0

Personalmente, tengo los mismos problemas al probar métodos privados y esto se debe a que algunas de las herramientas de prueba son limitadas. No es bueno que su diseño sea impulsado por herramientas limitadas si no responden a su necesidad de cambiar la herramienta, no el diseño. Debido a que solicita C #, no puedo proponer buenas herramientas de prueba, pero para Java hay dos herramientas poderosas: TestNG y PowerMock, y puede encontrar las herramientas de prueba correspondientes para la plataforma .NET

Xoke
fuente