unit test telecomunicaciones programming español ejemplo driven development acceptance unit-testing tdd

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



test driven development español (30)

¿Cómo realizo la prueba unitaria (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 ( static en C / C ++) o está en un espacio de nombres privado ( anonymous )?

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


Como han dicho otros ... 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. Utilice herramientas de cobertura de código. Me gusta Cobertura (¡oh feliz día, parece que ha salido una nueva versión!)

Ejecutar el código de cobertura en las pruebas unitarias. Si ve que los métodos no están totalmente probados, agregue a las pruebas para obtener la cobertura. Trate de obtener una cobertura de código del 100%, pero tenga en cuenta que probablemente no lo obtendrá.


Cuando tengo métodos privados en una clase que son lo suficientemente complicados que siento la necesidad de probar los métodos privados directamente, eso es un olor de código: mi clase es demasiado complicada.

Mi enfoque habitual para abordar estos problemas es descubrir una nueva clase que contiene los bits interesantes. A menudo, este método y los campos con los que interactúa, y tal vez otro método o dos pueden extraerse en una nueva clase.

La nueva clase expone estos métodos como ''públicos'', por lo que son accesibles para pruebas de unidad. 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 usar las fuerzas de las pruebas unitarias para ayudarlo a encontrar buenas clases nuevas.



En Spring Framework puedes probar métodos privados usando este método:

ReflectionTestUtils.invokeMethod()

Por ejemplo:

ReflectionTestUtils.invokeMethod(TestClazz, "createTest", "input data");


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.


Habiendo probado la solución de Cem Catikkas utilizando la reflexión para Java, debo decir que la suya fue una solución más elegante de lo 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 será una opción.

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

La creación de una prueba con acceso a miembros y métodos privados puede probar áreas de código que son difíciles de identificar 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 se pueden probar 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, 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


He usado la reflection para hacer esto con 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 debe 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 desea realizar un pequeño cambio interno en la clase, que no afecta el contrato público de las clases, entonces él / ella debe modificar su prueba basada en la reflexión para asegurarse de que funciona. Si lo hace repetidamente a lo largo de un proyecto, las pruebas unitarias dejan de ser una medida útil de la salud del código y comienzan a convertirse en un obstáculo para el desarrollo y una molestia para el equipo de desarrollo.

En su lugar, lo que recomiendo es usar 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 hacen los métodos privados y mantiene un mayor nivel de agilidad.


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ás probando
  3. El método que está intentando probar no debe ser privado.

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 de PrivilegedAccessor . Para 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 reflexión en sus métodos de prueba. Para detalles sobre cómo usarlo, vea este script de prueba .

PS Soy el principal contribuyente a Dp4j , pregúntame si necesitas ayuda. :)


Le sugiero que refactorice su código un poco. Cuando tienes que empezar a pensar en usar la reflexión u otro tipo de cosas, solo para probar tu código, algo va mal con tu código.

Usted mencionó diferentes tipos de problemas. Empecemos por los campos privados. En el caso de los 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<>(); ... }

Yo habría 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 antiguo usará un constructor vacío, y si me lo 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 los métodos privados. En mi experiencia personal, cuando tienes que aplastar un método privado para la prueba, ese método no tiene nada que hacer en esa clase. Un patrón común, en ese caso, sería envolverlo 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; }

Sobre todo 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 más fácil de mantener. Yo diría lo mismo de las clases anidadas. Si una clase anidada contiene lógica pesada, sería mejor si la moviera como una clase privada de paquete y la inyectara 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 muy lento (es simplemente molesto y no me gusta ese tipo de cosas).

También hay una inyección de setter, pero no recomendaría su uso. Será mejor que me quede con un constructor e inicie todo cuando sea realmente necesario, dejando la posibilidad de inyectar las dependencias necesarias.


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

Así que no pruebes métodos privados.


Los métodos privados son consumidos por los públicos. De lo contrario, son código muerto. Por eso 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.

Los métodos privados de prueba deben probarse mediante la depuración antes de ejecutar sus pruebas de unidad en métodos públicos.

También pueden ser depurados usando un desarrollo guiado por pruebas, depurando sus pruebas unitarias hasta que se cumplan todas sus afirmaciones.

Personalmente creo que es mejor crear clases usando TDD; creando los apéndices de métodos públicos, luego generando 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 se toma el camino equivocado para hacer que las aserciones de prueba de la unidad se ajusten a los resultados. Su clase es robusta y cumple con los requisitos cuando pasan todas las pruebas unitarias.


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 un método público falla, eso podría ser un error en el método privado.


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 de Google Guava.

Esto le dirá a cualquier persona que use este método que tome precauciones y no acceda a él 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 en la carpeta de prueba .

Por ejemplo, si un método que se va a probar está en src/main/java/mypackage/MyClass.java , su llamada de prueba se debe colocar en src/test/java/mypackage/MyClassTest.java . De esa manera, tienes acceso al método de prueba en tu clase de prueba.


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

Utilizo el paquete junitx.util.PrivateAccessor para Java. Un montón de útiles de una sola línea 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);


Si desea probar métodos privados de una aplicación heredada en la que 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.


Si está tratando de probar el código existente que no quiere 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 por separado y que la evalúe por separado. Esto no tiene que cambiar la interfaz pública de la clase original; Puede crear internamente una instancia de la clase auxiliar y llamar al método auxiliar.

Si desea probar condiciones de error difíciles que provienen 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 las excepciones del ayudante. Este enfoque también es útil si desea probar la clase original sin probar también la clase auxiliar.


Si está utilizando 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.


Si tiene una aplicación Java heredada y no tiene permiso para cambiar la visibilidad de sus métodos, la mejor manera de probar métodos privados es usar la reflection .

Internamente estamos utilizando ayudantes para obtener / establecer variables private static private y private static , así como invocar métodos private static private y private static . Los siguientes patrones le permitirán hacer prácticamente todo lo relacionado con los métodos y campos privados. Por supuesto, no se pueden cambiar private static final variables private static final través de la reflexión.

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

Y para los campos:

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

Notas:
1. targetClass.getDeclaredMethod(methodName, argClasses) permite buscar métodos private . Lo mismo se aplica para getDeclaredField .
2. Se setAccessible(true) el setAccessible(true) para jugar con personas privadas.


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 tener que agregar un configurador público no deseado:

ReflectionTestUtils.setField(theClass, "theUnsettableField", theMockObject);


Solo dos ejemplos de donde me gustaría probar un método privado:

  1. Rutinas de descifrado : no querría que sean visibles para que cualquiera las vea solo para probarlas, de lo contrario, cualquiera puede usarlas para descifrarlas. Pero son intrínsecos al código, son complicados y necesitan funcionar siempre (la excepción obvia es la reflexión que se puede usar para ver incluso los métodos privados en la mayoría de los casos, cuando SecurityManager no está configurado para evitar esto).
  2. Creación de un SDK para el consumo de la comunidad. 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 el código en métodos privados si no quiero que los usuarios del SDK lo vean. No veo esto como un olor de código, simplemente como la programación de 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 solo probar el "contrato". Pero no veo que uno pueda recomendar que realmente no esté probando el código; su millaje puede variar.

Así que mi compromiso implica complicar a los JUnits con la reflexión, en lugar de comprometer mi seguridad y SDK.


Solo se puede acceder a un método privado dentro de la misma clase. Por lo tanto, no hay manera de probar un método "privado" de una clase objetivo desde cualquier clase de prueba. Una salida es que puede realizar pruebas de unidad 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.


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


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); }


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

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


Por favor, vea abajo para 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");


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

Si tienes algún código con métodos privados o campos o constructores, puedes usar BoundBox . Hace exactamente lo que buscas. 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. Le dará acceso a todo eso a través de la reflexión, PERO todo se verifica en el momento de la compilación.

Es ideal para probar algún código heredado. Úsalo con cuidado. ;)

BoundBox


Para Java, uso la reflection , ya que no me gusta la idea de cambiar el acceso a un paquete en el método declarado solo por el hecho de realizar pruebas. Sin embargo, normalmente solo pruebo los métodos públicos, lo que también debería asegurar que los métodos privados funcionen correctamente.

no se puede usar la reflexión para obtener métodos privados desde fuera de la clase propietaria, el modificador privado también afecta la reflexión

Esto no es verdad. Seguramente puedes, como se menciona en la respuesta de Cem Catikkas .


Primero, haré esta pregunta: ¿por qué sus miembros privados necesitan pruebas aisladas? ¿Son tan complejos, que proporcionan comportamientos tan complicados que requieren pruebas aparte de la superficie pública? Es una prueba de unidad, 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" grande en complejidad, considere refactorizar a dichos miembros privados fuera de esta clase.

Si la refactorización es inapropiada o no es factible, ¿puede usar el patrón de estrategia para reemplazar el acceso a estas funciones / clases de miembro privadas cuando está por debajo de la prueba de unidad? Bajo la prueba de la unidad, la estrategia proporcionaría una validación adicional, pero en las versiones de lanzamiento sería un paso simple.


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");

Campos de configuración, por ejemplo private BigInteger amount;, por reflexión de Java

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

Campos de ajuste, 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));