¿Cómo pruebo una función privada o una clase que tiene métodos privados, campos o clases internas?

2729

¿Cómo pruebo la unidad (usando xUnit) una clase que tiene métodos privados internos, campos o clases anidadas? ¿O una función que se hace privada al tener un enlace interno ( staticen C / C ++) o está en un espacio de nombres privado ( anónimo )?

Parece malo cambiar el modificador de acceso para un método o función solo para poder ejecutar una prueba.

Raedwald
fuente
257
La mejor manera de probar un método privado no es probarlo directamente
Surya
26
Consulte el artículo Prueba de métodos privados con JUnit y SuiteRunner .
Mouna Cheikhna
383
Estoy en desacuerdo. Un método (público) que es largo o difícil de comprender tiene que ser refactorizado. Sería una locura no probar los métodos pequeños (privados) que obtienes en lugar de solo los públicos.
Michael Piefel
181
No estoy probando ningún método solo porque su visibilidad es estúpida. Incluso la prueba unitaria debe ser sobre el código más pequeño, y si prueba solo métodos públicos, ahora nunca estará seguro de dónde se produce el error: ese método u otro.
Dainius
126
Debe probar la funcionalidad de la clase , no su implementación . ¿Quieres probar los métodos privados? Prueba los métodos públicos que los llaman. Si la funcionalidad que ofrece la clase se prueba a fondo, sus partes internas han demostrado ser correctas y confiables; No necesita probar las condiciones internas. Las pruebas deben mantener el desacoplamiento de las clases probadas.
Calimar41

Respuestas:

1640

Actualizar:

Unos 10 años más tarde, tal vez la mejor manera de probar un método privado, o cualquier miembro inaccesible, es a través @Jailbreakdel marco de Manifold .

@Jailbreak Foo foo = new Foo();
// Direct, *type-safe* access to *all* foo's members
foo.privateMethod(x, y, z);
foo.privateField = value;

De esta manera, su código sigue siendo seguro para escribir y legible. Sin compromisos de diseño, sin métodos de sobreexposición y campos por el bien de las pruebas.

Si tienes algo de un legado aplicación Java y no puede cambiar la visibilidad de sus métodos, la mejor manera de probar métodos privados es usar la reflexión .

Internamente estamos usando ayudantes para obtener / establecer privatey private staticvariables, así como invocarprivate y private staticmétodos. Los siguientes patrones le permitirán hacer prácticamente cualquier cosa relacionada con los métodos y campos privados. Por supuesto, no puede cambiar las private static finalvariables a través de la reflexión.

Method method = TargetClass.getDeclaredMethod(methodName, argClasses);
method.setAccessible(true);
return method.invoke(targetObject, argObjects);

Y para campos:

Field field = TargetClass.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(object, value);

Notas:
1. le TargetClass.getDeclaredMethod(methodName, argClasses)permite buscar privatemétodos. Lo mismo se aplica getDeclaredField.
2. Se setAccessible(true)requiere jugar con los soldados.

Cem Catikkas
fuente
350
Útil si quizás no conozca la API, pero si tiene que probar métodos privados de esta manera, hay algo en su diseño. Como dice otro afiche, las pruebas unitarias deberían probar el contrato de la clase: si el contrato es demasiado amplio e instancia demasiado del sistema, entonces el diseño debe ser abordado.
andygavin
21
Muy útil. Al usar esto, es importante tener en cuenta que fallaría gravemente si las pruebas se ejecutaran después de la ofuscación.
Rick Minerich
20
El código de ejemplo no funcionó para mí, pero esto dejó las cosas más claras: java2s.com/Tutorial/Java/0125__Reflection/…
Rob
35
Mucho mejor que usar la reflexión directamente sería usar alguna biblioteca para ello, como Powermock .
Michael Piefel
25
Es por eso que Test Driven Design es útil. Le ayuda a descubrir qué debe exponerse para validar el comportamiento.
Thorbjørn Ravn Andersen
621

La mejor manera de probar un método privado es a través de otro método público. Si esto no se puede hacer, entonces una de las siguientes condiciones es verdadera:

  1. El método privado es código muerto
  2. Hay un olor a diseño cerca de la clase que está probando
  3. El método que está intentando probar no debe ser privado.
Trumpi
fuente
66
Además, nunca obtenemos una cobertura de código del 100% de todos modos, entonces, ¿por qué no enfocar su tiempo haciendo pruebas de calidad en los métodos que los clientes realmente usarán directamente?
grinch
30
@grinch Spot on. Lo único que obtendría al probar métodos privados es depurar información, y para eso están los depuradores. Si sus pruebas del contrato de la clase tienen cobertura total, entonces tiene toda la información que necesita. Los métodos privados son un detalle de implementación . Si los prueba, tendrá que cambiar sus pruebas cada vez que cambie su implementación, incluso si el contrato no lo hace. En el ciclo de vida completo del software, es probable que esto cueste mucho más que el beneficio que brinda.
Erik Madsen
99
@AlexWien tienes razón. La cobertura no era el término correcto. Lo que debería haber dicho fue "si sus pruebas del contrato de la clase cubren todas las entradas significativas y todos los estados significativos". Esto, usted señala correctamente, puede resultar inviable para probarlo a través de la interfaz pública del código, o indirectamente como se refiere a él. Si ese es el caso de algún código que está probando, me inclinaría a pensar: (1) El contrato es demasiado generoso (por ejemplo, demasiados parámetros en el método) o (2) El contrato es demasiado vago (por ejemplo, el comportamiento del método varía mucho con el estado de la clase). Consideraría ambos olores de diseño.
Erik Madsen
16
Casi todos los métodos privados no deben probarse directamente (para reducir los costos de mantenimiento). Excepción: los métodos científicos pueden tener formas como f (a (b (c (x), d (y))), a (e (x, z)), b (f (x, y, z), z)) donde a, b, c, d, e y f son expresiones horriblemente complicadas pero de otra manera son inútiles fuera de esta fórmula única. Algunas funciones (piense en MD5) son difíciles de invertir, por lo que puede ser difícil (NP-Hard) elegir parámetros que cubran completamente el espacio de comportamiento. Es más fácil probar a, b, c, d, e, f independientemente. Haciéndolos mentiras públicas o privadas de paquetes que son reutilizables. Solución: hacerlos privados, pero probarlos.
Epónimo
44
@Eponymous Estoy un poco desgarrado en este caso. El purista orientado a objetos en mí diría que debe usar un enfoque orientado a objetos en lugar de métodos privados estáticos, lo que le permite probar a través de métodos de instancia pública. Pero, de nuevo, en un marco científico, la sobrecarga de memoria es muy probable que sea una preocupación que, según creo, defiende el enfoque estático.
Erik Madsen
323

Cuando tengo métodos privados en una clase que son lo suficientemente complicados como para sentir la necesidad de probarlos directamente, es un olor a código: mi clase es demasiado complicada.

Mi enfoque habitual para abordar estos problemas es descifrar una nueva clase que contenga los bits interesantes. A menudo, este método y los campos con los que interactúa, y quizás otro método o dos se pueden extraer en una nueva clase.

La nueva clase expone estos métodos como 'públicos', por lo que son accesibles para pruebas unitarias. Las clases nuevas y antiguas ahora son más simples que la clase original, lo cual es genial para mí (¡necesito mantener las cosas simples o me pierdo!).

¡Tenga en cuenta que no estoy sugiriendo que las personas creen clases sin usar su cerebro! El punto aquí es utilizar las fuerzas de las pruebas unitarias para ayudarlo a encontrar buenas clases nuevas.

Jay Bazuzi
fuente
24
La pregunta era cómo probar métodos privados. Dices que harías una nueva clase para eso (y agregarías mucha más complejidad) y luego sugieres no crear una nueva clase. Entonces, ¿cómo probar los métodos privados?
Dainius
27
@Dainius: no sugiero crear una nueva clase únicamente para que pueda probar ese método. Sugiero que escribir pruebas puede ayudarlo a mejorar su diseño: los buenos diseños son fáciles de probar.
Jay Bazuzi
13
¿Pero está de acuerdo en que un buen OOD expondría (haría público) solo los métodos que son necesarios para que esa clase / objeto funcione correctamente? todos los demás deben ser privados / protegidos. Por lo tanto, en algunos métodos privados habrá algo de lógica, y las pruebas de la OMI con estos métodos solo mejorarán la calidad del software. Por supuesto, estoy de acuerdo en que si algún fragmento de código es complejo, debe dividirse en métodos / clases separados.
Dainius
66
@Danius: al agregar una nueva clase, en la mayoría de los casos se reduce la complejidad. Solo mídelo. La capacidad de prueba en algunos casos lucha contra OOD. El enfoque moderno es la comprobabilidad a favor.
AlexWien 01 de
55
¡Así es exactamente cómo usar los comentarios de sus pruebas para conducir y mejorar su diseño! Escucha tus pruebas. si son difíciles de escribir, ¡eso te está diciendo algo importante sobre tu código!
pnschofield
289

He utilizado la reflexión para hacer esto para Java en el pasado, y en mi opinión fue un gran error.

Estrictamente hablando, no debe escribir pruebas unitarias que prueben directamente métodos privados. Lo que deberias probar es el contrato público que la clase tiene con otros objetos; nunca debes probar directamente las partes internas de un objeto. Si otro desarrollador quiere hacer un pequeño cambio interno en la clase, lo que no afecta el contrato público de las clases, entonces él / ella tiene que modificar su prueba basada en la reflexión para asegurarse de que funcione. Si hace esto repetidamente a lo largo de un proyecto, las pruebas unitarias dejan de ser una medida útil del estado del código y comienzan a convertirse en un obstáculo para el desarrollo y una molestia para el equipo de desarrollo.

Lo que recomiendo hacer en su lugar es utilizar una herramienta de cobertura de código como Cobertura, para garantizar que las pruebas unitarias que escriba proporcionen una cobertura decente del código en métodos privados. De esa manera, usted prueba indirectamente lo que están haciendo los métodos privados y mantiene un mayor nivel de agilidad.

Jon
fuente
76
+1 a esto. En mi opinión, es la mejor respuesta a la pregunta. Al probar métodos privados, está probando la implementación. Esto anula el propósito de las pruebas unitarias, que es probar las entradas / salidas del contrato de una clase. Una prueba solo debe saber lo suficiente sobre la implementación para burlarse de los métodos que llama en sus dependencias. Nada mas. Si no puede cambiar su implementación sin tener que cambiar una prueba, es probable que su estrategia de prueba sea deficiente.
Colin M
10
@Colin M Sin embargo, eso no es realmente lo que está pidiendo;) Déjelo decidir, no conoce el proyecto.
Aaron Marcus
2
No es realmente cierto. Podría tomar mucho esfuerzo probar una pequeña parte del método privado a través del método público que lo usa. El método público podría requerir una configuración significativa antes de llegar a la línea que llama a su método privado
ACV
1
Estoy de acuerdo. Debe probar, si se cumple el contrato. No debe probar cómo se hace esto. Si el contacto se cumple sin alcanzar el 100% de cobertura del código (en métodos privados), puede ser un código muerto o inútil.
wuppi
207

De este artículo: Prueba de métodos privados con JUnit y SuiteRunner (Bill Venners), básicamente tiene 4 opciones:

  1. No pruebes métodos privados.
  2. Dar acceso al paquete de métodos.
  3. Use una clase de prueba anidada.
  4. Usa la reflexión.
Iker Jiménez
fuente
@JimmyT., Depende de para quién es el "código de producción". Llamaría código producido para aplicaciones estacionadas dentro de VPN cuyos usuarios objetivo son administradores de sistemas para ser código de producción.
Pacerier
@Pacerier ¿Qué quieres decir?
Jimmy T.
1
La quinta opción, como se mencionó anteriormente, es probar el método público que llama al método privado. ¿Correcto?
user2441441
1
@LorenzoLerate He estado luchando con esto porque estoy haciendo un desarrollo integrado y me gustaría que mi software sea lo más pequeño posible. Las limitaciones de tamaño y el rendimiento son la única razón por la que puedo pensar.
Realmente me gusta la prueba usando la reflexión: aquí el método Método método = targetClass.getDeclaredMethod (methodName, argClasses); method.setAccessible (verdadero); return method.invoke (targetObject, argObjects);
Aguid
127

Generalmente, una prueba de unidad está destinada a ejercer la interfaz pública de una clase o unidad. Por lo tanto, los métodos privados son detalles de implementación que no esperaría probar explícitamente.

John Channing
fuente
16
Esa es la mejor respuesta de la OMI, o como se suele decir, probar el comportamiento, no los métodos. Las pruebas unitarias no reemplazan las métricas del código fuente, las herramientas de análisis de código estático y las revisiones de código. Si los métodos privados son tan complejos que necesitan pruebas separadas, entonces probablemente sea necesario refactorizarlos, no más pruebas.
Dan Haynes
14
así que no escribas métodos privados, ¿solo creas otras 10 clases pequeñas?
maquinilla de afeitar
1
No, básicamente significa que puede probar la funcionalidad de los métodos privados utilizando el método público que los invoca.
Akshat Sharda
67

Solo dos ejemplos de dónde me gustaría probar un método privado:

  1. Rutinas de descifrado : no quisiera hacerlas visibles para que cualquiera las vea solo por el simple hecho de probarlas, de lo contrario, cualquiera puede usarlas para descifrarlas. Pero son intrínsecos al código, complicados y deben funcionar siempre (la excepción obvia es la reflexión que se puede usar para ver incluso métodos privados en la mayoría de los casos, cuando SecurityManagerno está configurado para evitar esto).
  2. Creación de un SDK para consumo comunitario. Aquí el público adquiere un significado completamente diferente, ya que este es un código que todo el mundo puede ver (no solo interno a mi aplicación). Pongo código en métodos privados si no quiero que los usuarios del SDK lo vean. No veo esto como un olor a código, sino simplemente cómo funciona la programación del SDK. Pero, por supuesto, todavía necesito probar mis métodos privados, y es donde realmente vive la funcionalidad de mi SDK.

Entiendo la idea de probar solo el "contrato". Pero no veo que uno pueda abogar por no probar el código; su millaje puede variar.

Entonces, mi compensación implica complicar los JUnits con la reflexión, en lugar de comprometer mi seguridad y SDK.

Richard Le Mesurier
fuente
55
Si bien nunca debe hacer público un método solo para probarlo, privateno es la única alternativa. Ningún modificador de acceso es package privatey significa que puede realizar una prueba unitaria mientras su prueba unitaria viva en el mismo paquete.
ngreen
1
Estaba comentando su respuesta, el punto 1 en particular, no el OP. No es necesario que un método sea privado solo porque no desea que sea público.
ngreen
1
@ngreen true thx - Fui flojo con la palabra "public". He actualizado la respuesta para incluir público, protegido, predeterminado (y para mencionar la reflexión). El punto que estaba tratando de aclarar es que hay una buena razón para que algún código sea secreto, sin embargo, eso no debería evitar que lo probemos.
Richard Le Mesurier
2
Gracias por dar ejemplos reales a todos. La mayoría de las respuestas son buenas, pero desde un punto de vista teórico. En el mundo real, necesitamos ocultar la implementación del contrato, y aún tenemos que probarlo. Al leer todo esto, creo que quizás este debería ser un nivel completamente nuevo de prueba, con un nombre diferente
Z. Khullah,
1
Un ejemplo más: los analizadores HTML de rastreadores . Tener que analizar una página html completa en objetos de dominio requiere un montón de lógica esencial para validar pequeñas partes de la estructura. Tiene sentido dividir eso en métodos y llamarlo desde un método público, pero no tiene sentido que todos los métodos más pequeños que lo componen sean públicos, ni tiene mucho sentido crear 10 clases por página HTML.
Nether
59

Los métodos privados son llamados por un método público, por lo que las entradas a sus métodos públicos también deben probar los métodos privados que son llamados por esos métodos públicos. Cuando falla un método público, eso podría ser un error en el método privado.

Thomas Owens
fuente
Hecho real. El problema es cuando la implementación es más complicada, como si un método público llama a varios métodos privados. ¿Cuál falló? O si es un método privado complicado, que no se puede exponer ni transferir a una nueva clase
Z. Khullah,
Ya mencionaste por qué el método privado debería ser probado. Dijiste "entonces eso podría ser", así que no estás seguro. OMI, si un método debe ser probado es ortogonal a su nivel de acceso.
Wei Qiu
39

Otro enfoque que he usado es cambiar un método privado para empaquetar privado o protegido y luego complementarlo con la anotación @VisibleForTesting de la biblioteca Google Guava.

Esto le indicará a cualquiera que use este método que tenga cuidado y no acceda directamente, incluso en un paquete. Además, una clase de prueba no necesita estar físicamente en el mismo paquete , sino en el mismo paquete debajo de la carpeta de prueba .

Por ejemplo, si hay un método para probar, src/main/java/mypackage/MyClass.javaentonces su llamada de prueba debe realizarse src/test/java/mypackage/MyClassTest.java. De esa manera, tienes acceso al método de prueba en tu clase de prueba.

Peter Mortensen
fuente
1
No sabía sobre esto, es interesante, todavía creo que si necesita ese tipo de anotación, tiene problemas de diseño.
Seb
2
Para mí, esto es como decir que, en lugar de darle una llave única a su director de bomberos para que pruebe el simulacro de incendio, deja su puerta delantera abierta con un letrero que dice: "desbloqueado para la prueba, si no es de nuestra empresa, por favor no ingrese ".
P. Jeremy Krieg
@FrJeremyKrieg: ¿qué significa eso?
MasterJoe2
1
@ MasterJoe2: el propósito de una prueba de fuego es mejorar la seguridad de su edificio contra el riesgo de incendio. Pero tienes que darle acceso al alcaide a tu edificio. Si deja permanentemente la puerta delantera desbloqueada, aumenta el riesgo de acceso no autorizado y lo hace menos seguro. Lo mismo ocurre con el patrón VisibleForTesting: aumenta el riesgo de acceso no autorizado para reducir el riesgo de "incendio". Es mejor otorgar acceso como prueba única para las pruebas solo cuando lo necesite (p. Ej., Usando la reflexión) en lugar de dejarlo permanentemente desbloqueado (no privado).
P. Jeremy Krieg
34

Para probar el código heredado con clases grandes y extravagantes, a menudo es muy útil poder probar el método privado (o público) que estoy escribiendo en este momento .

Yo uso el paquete junitx.util.PrivateAccessor para Java. Un montón de frases útiles para acceder a métodos privados y campos privados.

import junitx.util.PrivateAccessor;

PrivateAccessor.setField(myObjectReference, "myCrucialButHardToReachPrivateField", myNewValue);
PrivateAccessor.invoke(myObjectReference, "privateMethodName", java.lang.Class[] parameterTypes, java.lang.Object[] args);
phareim
fuente
2
Asegúrese de descargar todo el paquete JUnit-addons ( sourceforge.net/projects/junit-addons ), no el proyecto PrivateAccessor recomendado por Source Forge.
skia.heliou
2
Y asegúrese de que, cuando esté usando a Classcomo su primer parámetro en estos métodos, esté accediendo solo a staticmiembros.
skia.heliou
31

En Spring Framework , puede probar métodos privados utilizando este método:

ReflectionTestUtils.invokeMethod()

Por ejemplo:

ReflectionTestUtils.invokeMethod(TestClazz, "createTest", "input data");
Mikhail
fuente
La solución más limpia y concisa hasta ahora, pero solo si está utilizando Spring.
Denis Nikolaenko
28

Después de haber probado la solución de Cem Catikkas usando la reflexión para Java, debo decir que fue una solución más elegante que la que he descrito aquí. Sin embargo, si está buscando una alternativa al uso de la reflexión y tiene acceso a la fuente que está probando, esta seguirá siendo una opción.

Existe la posibilidad de probar métodos privados de una clase, particularmente con el desarrollo basado en pruebas , en el que le gustaría diseñar pequeñas pruebas antes de escribir cualquier código.

Crear una prueba con acceso a miembros y métodos privados puede probar áreas de código que son difíciles de enfocar específicamente con acceso solo a métodos públicos. Si un método público tiene varios pasos involucrados, puede consistir en varios métodos privados, que luego pueden probarse individualmente.

Ventajas:

  • Puede probar a una granularidad más fina

Desventajas

  • El código de prueba debe residir en el mismo archivo que el código fuente, lo que puede ser más difícil de mantener
  • De manera similar con los archivos de salida .class, deben permanecer dentro del mismo paquete que el declarado en el código fuente

Sin embargo, si las pruebas continuas requieren este método, puede ser una señal de que deben extraerse los métodos privados, que podrían probarse de la manera tradicional y pública.

Aquí hay un ejemplo complicado de cómo funcionaría esto:

// Import statements and package declarations

public class ClassToTest
{
    private int decrement(int toDecrement) {
        toDecrement--;
        return toDecrement;
    }

    // Constructor and the rest of the class

    public static class StaticInnerTest extends TestCase
    {
        public StaticInnerTest(){
            super();
        }

        public void testDecrement(){
            int number = 10;
            ClassToTest toTest= new ClassToTest();
            int decremented = toTest.decrement(number);
            assertEquals(9, decremented);
        }

        public static void main(String[] args) {
            junit.textui.TestRunner.run(StaticInnerTest.class);
        }
    }
}

La clase interna se compilaría a ClassToTest$StaticInnerTest.

Ver también: Java Tip 106: Clases internas estáticas para diversión y ganancias

Grundlefleck
fuente
26

Como otros han dicho ... no pruebes métodos privados directamente. Aquí hay algunos pensamientos:

  1. Mantenga todos los métodos pequeños y enfocados (fácil de probar, fácil de encontrar lo que está mal)
  2. Use herramientas de cobertura de código. Me gusta Cobertura (¡feliz día, parece que ya salió una nueva versión!)

Ejecute la cobertura del código en las pruebas unitarias. Si ve que los métodos no se prueban completamente, agréguelos a las pruebas para aumentar la cobertura. Apunte a una cobertura de código del 100%, pero tenga en cuenta que probablemente no la obtendrá.

TofuBeer
fuente
Para la cobertura del código. No importa qué tipo de lógica haya en el método privado, todavía está invocando esa lógica a través de un método público. Una herramienta de cobertura de código puede mostrarle qué partes están cubiertas por la prueba, por lo tanto, puede ver si se prueba su método privado.
coolcfan
He visto clases donde el único método público es main [], y aparecen una GUI emergente, además de conectar la base de datos y un par de servidores web en todo el mundo. Fácil de decir "no
pruebes
26

Los métodos privados son consumidos por los públicos. De lo contrario, son código muerto. Es por eso que prueba el método público, afirmando los resultados esperados del método público y, por lo tanto, los métodos privados que consume.

La prueba de los métodos privados debe probarse mediante depuración antes de ejecutar las pruebas unitarias en los métodos públicos.

También se pueden depurar mediante el desarrollo basado en pruebas, depurando las pruebas unitarias hasta que se cumplan todas sus afirmaciones.

Personalmente, creo que es mejor crear clases con TDD; crear los resguardos de métodos públicos, luego generar pruebas unitarias con todas las aserciones definidas de antemano, de modo que el resultado esperado del método se determine antes de codificarlo. De esta manera, no va por el camino equivocado de hacer que las afirmaciones de la prueba unitaria se ajusten a los resultados. Su clase es robusta y cumple con los requisitos cuando pasan todas las pruebas de su unidad.

Antony Booth
fuente
2
Aunque esto es cierto, puede llevar a algunas pruebas muy complicadas: es un mejor patrón para probar un solo método a la vez (prueba de unidad) en lugar de un grupo completo de ellos. No es una sugerencia terrible, pero creo que hay mejores respuestas aquí, este debería ser el último recurso.
Bill K
24

Si usa Spring, ReflectionTestUtils proporciona algunas herramientas útiles que ayudan aquí con un mínimo esfuerzo. Por ejemplo, para configurar un simulacro en un miembro privado sin verse obligado a agregar un setter público indeseable:

ReflectionTestUtils.setField(theClass, "theUnsettableField", theMockObject);
Steve Chambers
fuente
24

Si intenta probar el código existente que es reacio o no puede cambiar, la reflexión es una buena opción.

Si el diseño de la clase sigue siendo flexible, y tiene un método privado complicado que le gustaría probar por separado, le sugiero que lo saque a una clase separada y pruebe esa clase por separado. Esto no tiene que cambiar la interfaz pública de la clase original; internamente puede crear una instancia de la clase auxiliar y llamar al método auxiliar.

Si desea probar condiciones de error difíciles provenientes del método auxiliar, puede ir un paso más allá. Extraiga una interfaz de la clase auxiliar, agregue un captador y definidor público a la clase original para inyectar la clase auxiliar (utilizada a través de su interfaz), y luego inyecte una versión simulada de la clase auxiliar en la clase original para probar cómo funciona la clase original responde a excepciones del ayudante. Este enfoque también es útil si desea probar la clase original sin probar también la clase auxiliar.

Don Kirkby
fuente
19

Probar métodos privados rompe la encapsulación de su clase porque cada vez que cambia la implementación interna se rompe el código del cliente (en este caso, las pruebas).

Así que no pruebes métodos privados.

Peter Mortensen
fuente
44
la prueba unitaria y el código src son un par. Si cambia el src, tal vez tenga que cambiar la prueba de la unidad. Ese es el sentido de la prueba junit. Deberán garantizar que todo funciona como antes. y está bien si se rompen, si cambias el código.
AlexWien 01 de
1
No acepte que la prueba unitaria debería cambiar si el código cambia. Si se le ordena agregar funcionalidad a una clase sin cambiar la funcionalidad existente de la clase, entonces las pruebas unitarias existentes deben pasar incluso después de haber cambiado su código. Como Peter menciona, las pruebas unitarias deberían probar la interfaz, no cómo se hace internamente. En Test Driven Development, las pruebas unitarias se crean antes de escribir el código, para centrarse en la interfaz de la clase, no en cómo se resuelve internamente.
Harald Coppoolse
16

La respuesta de la página de preguntas frecuentes de JUnit.org :

Pero si debes ...

Si está utilizando JDK 1.3 o superior, puede usar la reflexión para subvertir el mecanismo de control de acceso con la ayuda del PrivilegedAccessor . Para obtener detalles sobre cómo usarlo, lea este artículo .

Si está utilizando JDK 1.6 o superior y anota sus pruebas con @Test, puede usar Dp4j para inyectar reflejo en sus métodos de prueba. Para obtener detalles sobre cómo usarlo, consulte este script de prueba .

PS Soy el principal contribuyente a Dp4j , pregunto yo si necesita ayuda. :)

simpatico
fuente
16

Si desea probar métodos privados de una aplicación heredada donde no puede cambiar el código, una opción para Java es jMockit , que le permitirá crear simulacros para un objeto incluso cuando son privados para la clase.

Will Sargent
fuente
44
Como parte de la biblioteca jmockit, tiene acceso a la clase Deencapsulation que facilita la prueba de métodos privados: Deencapsulation.invoke(instance, "privateMethod", param1, param2);
Domenic D.
Yo uso este enfoque todo el tiempo. Muy útil
MedicineMan
14

Tiendo a no probar métodos privados. Ahí yace la locura. Personalmente, creo que solo debe probar sus interfaces expuestas públicamente (y eso incluye métodos protegidos e internos).

bmurphy1976
fuente
14

Si está usando JUnit, eche un vistazo a junit-addons . Tiene la capacidad de ignorar el modelo de seguridad de Java y acceder a métodos y atributos privados.

Joe
fuente
13

Te sugiero que refactorices un poco tu código. Cuando tiene que comenzar a pensar en usar la reflexión u otro tipo de cosas, solo para probar su código, algo va mal con su código.

Mencionaste diferentes tipos de problemas. Comencemos con los campos privados. En el caso de campos privados, habría agregado un nuevo constructor e inyectado campos en eso. En lugar de esto:

public class ClassToTest {

    private final String first = "first";
    private final List<String> second = new ArrayList<>();
    ...
}

Hubiera usado esto:

public class ClassToTest {

    private final String first;
    private final List<String> second;

    public ClassToTest() {
        this("first", new ArrayList<>());
    }

    public ClassToTest(final String first, final List<String> second) {
        this.first = first;
        this.second = second;
    }
    ...
}

Esto no será un problema incluso con algún código heredado. El código anterior usará un constructor vacío, y si me preguntas, el código refactorizado se verá más limpio y podrás inyectar los valores necesarios en la prueba sin reflexión.

Ahora sobre métodos privados. En mi experiencia personal, cuando tienes que colocar un método privado para probar, ese método no tiene nada que ver en esa clase. Un patrón común, en ese caso, sería envolver dentro de una interfaz, como Callabley luego pasar esa interfaz también en el constructor (con ese truco de constructor múltiple):

public ClassToTest() {
    this(...);
}

public ClassToTest(final Callable<T> privateMethodLogic) {
    this.privateMethodLogic = privateMethodLogic;
}

Casi todo lo que escribí parece que es un patrón de inyección de dependencia. En mi experiencia personal, es realmente útil durante las pruebas, y creo que este tipo de código es más limpio y será más fácil de mantener. Yo diría lo mismo sobre las clases anidadas. Si una clase anidada contiene una lógica pesada, sería mejor si la hubiera movido como una clase privada de paquete y la hubiera inyectado en una clase que la necesita.

También hay varios otros patrones de diseño que he usado al refactorizar y mantener el código heredado, pero todo depende de los casos de su código para probar. El uso de la reflexión en su mayoría no es un problema, pero cuando tienes una aplicación empresarial que está muy probada y las pruebas se ejecutan antes de cada implementación, todo se vuelve realmente lento (es molesto y no me gusta ese tipo de cosas).

También hay una inyección de setter, pero no recomendaría usarla. Mejor me quedo con un constructor e inicializo todo cuando sea realmente necesario, dejando la posibilidad de inyectar las dependencias necesarias.

GROX13
fuente
1
No estoy de acuerdo con la ClassToTest(Callable)inyección. Eso lo hace ClassToTestmás complicado. Mantenlo simple. Además, eso requiere que alguien más diga ClassToTestalgo de lo que ClassToTestdebería ser el jefe. Hay un lugar para inyectar lógica, pero este no lo es. Acabas de hacer que la clase sea más difícil de mantener, no más fácil.
Loduwijk
Además, es preferible que su método de prueba Xno aumente la Xcomplejidad hasta el punto en que pueda causar más problemas ... y, por lo tanto, requiera pruebas adicionales ... que si lo ha implementado de una manera que puede causar más problemas ... . (esto no es un bucle infinito; cada iteración es probablemente más pequeña que la anterior, pero sigue siendo molesta)
Loduwijk
2
¿Podría explicar por qué haría que la clase fuera ClassToTestmás difícil de mantener? En realidad, hace que su aplicación sea más fácil de mantener, ¿qué sugiere hacer una nueva clase para cada valor diferente que necesitará firsty las 'segundas' variables?
GROX13
1
El método de prueba no aumenta la complejidad. Es solo tu clase la que está mal escrita, tan mal que no se puede probar.
GROX13
Más difícil de mantener porque la clase es más complicada. class A{ A(){} f(){} }es más simple que class A { Logic f; A(logic_f) } class B { g() { new A( logic_f) } }. Si eso no fuera cierto, y si fuera cierto que el suministro de la lógica de una clase como argumentos de constructor era más fácil de mantener, entonces pasaríamos toda la lógica como argumentos de constructor. Simplemente no veo cómo puede reclamar class B{ g() { new A( you_dont_know_your_own_logic_but_I_do ) } }alguna vez hace que A sea más fácil de mantener. Hay casos en los que inyectar así tiene sentido, pero no veo su ejemplo como "más fácil de mantener"
Loduwijk
12

Solo se puede acceder a un método privado dentro de la misma clase. Por lo tanto, no hay forma de probar un método "privado" de una clase objetivo desde ninguna clase de prueba. Una salida es que puede realizar pruebas unitarias manualmente o puede cambiar su método de "privado" a "protegido".

Y luego solo se puede acceder a un método protegido dentro del mismo paquete donde se define la clase. Por lo tanto, probar un método protegido de una clase objetivo significa que necesitamos definir su clase de prueba en el mismo paquete que la clase objetivo.

Si todo lo anterior no cumple con sus requisitos, use la forma de reflexión para acceder al método privado.

loknath
fuente
Estás mezclando "protegido" con "amigable". Solo se puede acceder a un método protegido mediante una clase cuyos objetos sean asignables a la clase de destino (es decir, subclases).
Sayo Oladeji
1
En realidad no hay "Amistoso" en Java, el término es "Paquete" y se indica por la falta de un modificador privado / público / protegido. Hubiera corregido esta respuesta, pero hay una muy buena que ya dice esto, así que solo recomendaría eliminarla.
Bill K
Casi voté en contra, pensando todo el tiempo "Esta respuesta es simplemente errónea". hasta que llegué a la última oración, lo que contradice el resto de la respuesta. La última oración debería haber sido la primera.
Loduwijk
11

Como muchos han sugerido anteriormente, una buena manera es probarlos a través de sus interfaces públicas.

Si hace esto, es una buena idea usar una herramienta de cobertura de código (como Emma) para ver si sus métodos privados se están ejecutando de sus pruebas.

Grebas de Darren
fuente
¡No debes probar indirectamente! No solo tocar a través de la cobertura; ¡prueba que se entregue el resultado esperado!
AlexWien 01 de
11

Aquí está mi función genérica para probar campos privados:

protected <F> F getPrivateField(String fieldName, Object obj)
    throws NoSuchFieldException, IllegalAccessException {
    Field field =
        obj.getClass().getDeclaredField(fieldName);

    field.setAccessible(true);
    return (F)field.get(obj);
}
Yuli Reiri
fuente
10

Consulte a continuación para ver un ejemplo;

Se debe agregar la siguiente declaración de importación:

import org.powermock.reflect.Whitebox;

Ahora puede pasar directamente el objeto que tiene el método privado, el nombre del método que se va a llamar y los parámetros adicionales como se muestra a continuación.

Whitebox.invokeMethod(obj, "privateMethod", "param1");
Olcay Tarazan
fuente
10

Hoy, introduje una biblioteca Java para ayudar a probar métodos y campos privados. Ha sido diseñado con Android en mente, pero realmente se puede usar para cualquier proyecto Java.

Si tiene algún código con métodos privados o campos o constructores, puede usar BoundBox . Hace exactamente lo que estás buscando. A continuación se muestra un ejemplo de una prueba que accede a dos campos privados de una actividad de Android para probarla:

@UiThreadTest
public void testCompute() {

    // Given
    boundBoxOfMainActivity = new BoundBoxOfMainActivity(getActivity());

    // When
    boundBoxOfMainActivity.boundBox_getButtonMain().performClick();

    // Then
    assertEquals("42", boundBoxOfMainActivity.boundBox_getTextViewMain().getText());
}

BoundBox facilita la prueba de campos, métodos y constructores privados / protegidos. Incluso puedes acceder a cosas que están ocultas por herencia. De hecho, BoundBox rompe la encapsulación. Te dará acceso a todo eso a través de la reflexión, PERO todo se verifica en tiempo de compilación.

Es ideal para probar algunos códigos heredados. Úselo con cuidado. ;)

https://github.com/stephanenicolas/boundbox

Snicolas
fuente
1
¡Acabo de probarlo, BoundBox es una solución simple y elegante!
Forhas
9

Primero, haré esta pregunta: ¿Por qué sus miembros privados necesitan pruebas aisladas? ¿Son tan complejos y proporcionan comportamientos tan complicados que requieren pruebas aparte de la superficie pública? Es una prueba unitaria, no una prueba de "línea de código". No te preocupes por las cosas pequeñas.

Si son tan grandes, lo suficientemente grandes como para que estos miembros privados sean cada uno una 'unidad' de gran complejidad, considere refactorizar a tales miembros privados fuera de esta clase.

Si la refactorización es inapropiada o inviable, ¿puede usar el patrón de estrategia para reemplazar el acceso a estas funciones / clases de miembros privados cuando se realiza una prueba unitaria? Según la prueba unitaria, la estrategia proporcionaría una validación adicional, pero en las versiones de lanzamiento sería un paso simple.

Aaron
fuente
3
Debido a que a menudo una pieza particular de código de un método público se refactoriza en un método privado interno y es realmente la pieza crítica de la lógica que podría haberse equivocado. Desea probar esto independientemente del método público
oxbow_lakes
Incluso el código más corto a veces sin prueba de unidad no es correcto. Solo trata de calcular la diferencia entre 2 ángulos geográficos. 4 líneas de código, y la mayoría no lo hará correctamente en el primer intento. Dichos métodos necesitan una prueba unitaria, porque forman la base de un código confiable. (Resp. Tal código útil también puede ser público; menos útil protegido
AlexWien
8

Recientemente tuve este problema y escribí una pequeña herramienta, llamada Picklock , que evita los problemas de usar explícitamente la API de reflexión de Java, dos ejemplos:

Métodos de llamada, por ejemplo private void method(String s), por reflexión de Java

Method method = targetClass.getDeclaredMethod("method", String.class);
method.setAccessible(true);
return method.invoke(targetObject, "mystring");

Métodos de llamada, por ejemplo private void method(String s), por Picklock

interface Accessible {
  void method(String s);
}

...
Accessible a = ObjectAccess.unlock(targetObject).features(Accessible.class);
a.method("mystring");

Configuración de campos, p private BigInteger amount;. Ej ., Por reflexión Java

Field field = targetClass.getDeclaredField("amount");
field.setAccessible(true);
field.set(object, BigInteger.valueOf(42));

Configuración de campos, por ejemplo private BigInteger amount;, por Picklock

interface Accessible {
  void setAmount(BigInteger amount);
}

...
Accessible a = ObjectAccess.unlock(targetObject).features(Accessible.class);
a.setAmount(BigInteger.valueOf(42));
CoronA
fuente
7

PowerMockito está hecho para esto. Usar dependencia maven

<dependency>
    <groupId>org.powermock</groupId>
    <artifactId>powermock-mockito-release-full</artifactId>
    <version>1.6.4</version>
</dependency>

Entonces puedes hacer

import org.powermock.reflect.Whitebox;
...
MyClass sut = new MyClass();
SomeType rval = Whitebox.invokeMethod(sut, "myPrivateMethod", params, moreParams);
Victor Grazi
fuente