unit-testing - telecomunicaciones - unit testing tdd bdd




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

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á.

¿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.


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.


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.

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 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.


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 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 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).


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

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.







tdd