¿Cómo prueba los métodos privados?

184

Estoy trabajando en un proyecto de Java. Soy nuevo en pruebas unitarias. ¿Cuál es la mejor manera de probar métodos privados de prueba en clases Java?

Vinoth Kumar CM
fuente
1
Verifique esta pregunta en StackOverflow. Se mencionan y discuten un par de técnicas. ¿Cuál es la mejor manera de probar los métodos privados de la unidad?
Quirón
34
Mi opinión siempre ha sido que los métodos privados no necesitan pruebas, ya que debería probar lo que está disponible. Un método público Si no puede romper el método público, ¿realmente importa lo que estén haciendo los métodos privados?
Plataforma
1
Deben probarse los métodos públicos y privados. Por lo tanto, un controlador de prueba generalmente necesita estar dentro de la clase que prueba. como esta .
intercambio excesivo
2
@Rig - +1 - debería poder invocar todo el comportamiento requerido de un método privado desde sus métodos públicos; si no puede hacerlo, la funcionalidad nunca podrá invocarse de todos modos, por lo que no tiene sentido probarlo.
James Anderson
Úselo @Jailbreakdesde el marco del múltiple para acceder directamente a métodos privados. De esta manera, su código de prueba sigue siendo seguro y legible. Sobre todo, sin compromisos de diseño, sin métodos de sobreexposición y campos por el bien de las pruebas.
Scott

Respuestas:

239

Por lo general, no prueba los métodos privados directamente. Como son privados, considérelos un detalle de implementación. Nadie va a llamar a uno de ellos y esperar que funcione de una manera particular.

En su lugar, debe probar su interfaz pública. Si los métodos que llaman a sus métodos privados funcionan como espera, entonces asume por extensión que sus métodos privados funcionan correctamente.

Adam Lear
fuente
39
+1 bazillion. Y si nunca se llama a un método privado, no lo pruebe, ¡elimínelo!
Steven A. Lowe
246
Estoy en desacuerdo. A veces, un método privado es solo un detalle de implementación, pero sigue siendo lo suficientemente complejo como para justificar la realización de pruebas, para asegurarse de que funcione correctamente. La interfaz pública puede estar ofreciendo un nivel de abstracción demasiado alto para escribir una prueba que se dirija directamente a este algoritmo particular. No siempre es factible factorizarlo en una clase separada, debido a los datos compartidos. En este caso, diría que está bien probar un método privado.
quant_dev
10
Por supuesto, bórralo. La única razón posible para no eliminar una función que no está utilizando es si cree que puede necesitarla en el futuro, e incluso entonces debe eliminarla, pero tenga en cuenta la función en sus registros de confirmación, o al menos coméntela para que la gente sabe que es algo extra que pueden ignorar.
jhocking
38
@quant_dev: a veces una clase necesita refactorizarse en varias otras clases. Sus datos compartidos también se pueden extraer en una clase separada. Digamos, una clase de "contexto". Luego, sus dos nuevas clases pueden referirse a la clase de contexto para sus datos compartidos. Esto puede parecer poco razonable, pero si sus métodos privados son lo suficientemente complejos como para necesitar pruebas individuales, es un olor a código que indica que su gráfico de objeto debe volverse un poco más granular.
Phil
43
Esta respuesta NO responde la pregunta. La pregunta es cómo deben probarse los métodos privados, no si deben probarse. La cuestión de si un método privado debe probarse como unidad es una pregunta interesante y digna de debate, pero no aquí . La respuesta apropiada es agregar un comentario en la pregunta que indique que probar métodos privados puede no ser una buena idea y dar un enlace a una pregunta separada que profundizaría en el asunto.
TallGuy
118

En general, lo evitaría. Si su método privado es tan complejo que necesita una prueba de unidad separada, a menudo significa que merecía su propia clase. Esto puede alentarlo a escribirlo de una manera que sea reutilizable. Luego, debe probar la nueva clase y llamar a la interfaz pública de la misma en su clase anterior.

Por otro lado, a veces factorizar los detalles de implementación en clases separadas conduce a clases con interfaces complejas, muchos datos que pasan entre la clase antigua y la nueva, o a un diseño que puede verse bien desde el punto de vista de OOP, pero no coincidir con las intuiciones que provienen del dominio del problema (por ejemplo, dividir un modelo de precios en dos partes solo para evitar probar métodos privados no es muy intuitivo y puede generar problemas más adelante al mantener / extender el código). No desea tener "clases gemelas" que siempre se cambian juntas.

Cuando me enfrento a una elección entre encapsulación y capacidad de prueba, prefiero ir por el segundo. Es más importante tener el código correcto (es decir, producir el resultado correcto) que un buen diseño de OOP que no funciona correctamente, porque no se probó adecuadamente. En Java, simplemente puede dar acceso al método "predeterminado" y colocar la prueba unitaria en el mismo paquete. Las pruebas unitarias son simplemente parte del paquete que está desarrollando, y está bien tener una dependencia entre las pruebas y el código que se está probando. Significa que cuando cambie la implementación, es posible que necesite cambiar sus pruebas, pero eso está bien: cada cambio de la implementación requiere volver a probar el código, y si las pruebas deben modificarse para hacerlo, entonces simplemente debe hacerlo eso.

En general, una clase puede estar ofreciendo más de una interfaz. Hay una interfaz para los usuarios y una interfaz para los mantenedores. El segundo puede exponer más para garantizar que el código se pruebe adecuadamente. No tiene que ser una prueba unitaria en un método privado; podría ser, por ejemplo, el registro. El registro también "interrumpe la encapsulación", pero aún lo hacemos, porque es muy útil.

cuant_dev
fuente
99
+1 Punto interesante. Al final, se trata de la mantenibilidad, no del cumplimiento de las "reglas" de una buena POO.
Phil
99
+1 Estoy completamente de acuerdo con la capacidad de prueba sobre la encapsulación.
Richard
31

La prueba de métodos privados dependería de su complejidad; algunos métodos privados de una línea realmente no justificarían el esfuerzo adicional de las pruebas (esto también se puede decir de los métodos públicos), pero algunos métodos privados pueden ser tan complejos como los públicos y difíciles de probar a través de la interfaz pública.

Mi técnica preferida es hacer que el paquete del método privado sea privado, lo que permitirá el acceso a una prueba unitaria en el mismo paquete, pero seguirá encapsulado de todos los demás códigos. Esto le dará la ventaja de probar la lógica del método privado directamente en lugar de tener que confiar en una prueba del método público para cubrir todas las partes de la lógica (posiblemente) compleja.

Si esto se combina con la anotación @VisibleForTesting en la biblioteca Google Guava, claramente está marcando este método privado del paquete como visible solo para pruebas y, como tal, no debería ser llamado por ninguna otra clase.

Los opositores a esta técnica argumentan que esto romperá la encapsulación y abrirá métodos privados para codificar en el mismo paquete. Si bien estoy de acuerdo en que esto rompe la encapsulación y abre el código privado a otras clases, sostengo que probar la lógica compleja es más importante que la encapsulación estricta y no usar métodos privados de paquetes que estén claramente marcados como visibles para la prueba solo debe ser responsabilidad de los desarrolladores usando y cambiando la base del código.

Método privado antes de la prueba:

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

Método privado del paquete listo para probar:

@VisibleForTesting
int add(int a, int b){
    return a + b;
}

Nota: Poner pruebas en el mismo paquete no es equivalente a ponerlas en la misma carpeta física. Separar el código principal y el código de prueba en estructuras de carpetas físicas separadas es una buena práctica en general, pero esta técnica funcionará siempre que las clases se definan como en el mismo paquete.

Ricardo
fuente
14

Si no puede usar API externas o no quiere, aún puede usar API JDK estándar pura para acceder a métodos privados utilizando la reflexión. Aquí hay un ejemplo

MyObject obj = new MyObject();
Method privateMethod = MyObject.class.getDeclaredMethod("getFoo", null);
privateMethod.setAccessible(true);
String returnValue = (String) privateMethod.invoke(obj, null);
System.out.println("returnValue = " + returnValue);

Consulte el Tutorial de Java http://docs.oracle.com/javase/tutorial/reflect/ o la API de Java http://docs.oracle.com/javase/7/docs/api/java/lang/reflect/package-summary. html para más información.

Como @kij citó en su respuesta, hay momentos en que una solución simple usando la reflexión es realmente buena para probar un método privado.

Gustavo Coelho
fuente
9

El caso de prueba de unidad significa probar la unidad de código. No significa probar la interfaz porque si está probando la interfaz, eso no significa que esté probando la unidad de código. Se convierte en una especie de prueba de caja negra. Además, es mejor encontrar problemas en el nivel de unidad más pequeño que determinar los problemas en el nivel de la interfaz y luego tratar de depurar lo que no funcionaba. Por lo tanto, el caso de prueba de unidad debe probarse independientemente de su alcance. Lo siguiente es una forma de probar métodos privados.

Si está usando Java, puede usar jmockit que proporciona Deencapsulation.invoke para llamar a cualquier método privado de la clase bajo prueba. Utiliza la reflexión para llamarlo eventualmente, pero proporciona un buen envoltorio a su alrededor. ( https://code.google.com/p/jmockit/ )

agrawalankur
fuente
¿Cómo responde esto a la pregunta que se hace?
mosquito
@gnat, la pregunta era: ¿Cuál es la mejor manera de probar los métodos privados en las clases de Java? Y mi primer punto responde a la pregunta.
agrawalankur
su primer punto simplemente anuncia una herramienta, pero no explica por qué cree que es mejor que las alternativas, como, por ejemplo, probar métodos privados indirectamente como se sugirió y explicó en la respuesta principal
mosquito
jmockit se ha movido y la página oficial anterior apunta a un artículo sobre "Orina sintética y orina artificial". Por cierto, todavía está relacionado con cosas de burla, pero no es relevante aquí :) La nueva página oficial es: jmockit.github.io
Guillaume
8

En primer lugar, como sugirieron otros autores: piénselo dos veces si realmente necesita probar un método privado. Y de ser así, ...

En .NET puede convertirlo al método "Interno" y hacer que el paquete " InternalVisible " se adapte a su proyecto de prueba de unidad.

En Java, puede escribir pruebas en la clase que se probará y sus métodos de prueba también deberían poder llamar a métodos privados. Realmente no tengo una gran experiencia en Java, por lo que probablemente esa no sea la mejor práctica.

Gracias.

Budda
fuente
7

Usualmente protejo tales métodos. Digamos que tu clase está en:

src/main/java/you/Class.java

Puede crear una clase de prueba como:

src/test/java/you/TestClass.java

Ahora tiene acceso a métodos protegidos y puede probarlos de forma unitaria (JUnit o TestNG realmente no importan), pero mantiene estos métodos de las personas que llaman que no deseaba.

Tenga en cuenta que esto espera un árbol fuente de estilo maven.

Gyorgyabraham
fuente
3

Si realmente necesita probar un método privado, con Java quiero decir, puede usar fest asserty / o fest reflect. Utiliza la reflexión.

Importe la biblioteca con maven (creo que las versiones dadas no son las últimas, creo) o impórtela directamente en su classpath:

<dependency>
     <groupId>org.easytesting</groupId>
     <artifactId>fest-assert</artifactId>
     <version>1.4</version>
    </dependency>

    <dependency>
     <groupId>org.easytesting</groupId>
     <artifactId>fest-reflect</artifactId>
    <version>1.2</version>
</dependency>

Como ejemplo, si tiene una clase llamada 'MyClass' con un método privado llamado 'myPrivateMethod' que toma un String como parámetro y actualiza su valor a '¡esto es una prueba genial!', Puede hacer la siguiente prueba de junit:

import static org.fest.reflect.core.Reflection.method;
...

MyClass objectToTest;

@Before
public void setUp(){
   objectToTest = new MyClass();
}

@Test
public void testPrivateMethod(){
   // GIVEN
   String myOriginalString = "toto";
   // WHEN
   method("myPrivateMethod").withParameterTypes(String.class).in(objectToTest).invoke(myOriginalString);
   // THEN
   Assert.assertEquals("this is cool testing !", myOriginalString);
}

Esta biblioteca también le permite reemplazar cualquier propiedad de bean (sin importar que sean privadas y no se escriban setters) por un simulacro, y usar esto con Mockito o cualquier otro marco simulado es realmente genial. Lo único que tiene que saber en este momento (no sé si esto será mejor en las próximas versiones) es el nombre del campo / método de destino que desea manipular y su firma.

kij
fuente
2
Si bien la reflexión le permitirá probar métodos privados, no es bueno para detectar su uso. Si cambia el nombre de un método privado, esto interrumpirá su prueba.
Richard
1
La prueba se romperá, sí, pero este es un problema menor desde mi punto de vista. Por supuesto, estoy totalmente de acuerdo con su explicación a continuación sobre la visibilidad del paquete (con clases de pruebas en carpetas separadas pero con los mismos paquetes), pero a veces no lo hace. Realmente tengo la opción. Por ejemplo, si tiene una misión realmente corta en una empresa que no aplica métodos "buenos" (clases de prueba que no están en el mismo paquete, etc.), si no tiene tiempo para refactorizar el código existente en el que está trabajando / testing, esta sigue siendo una buena alternativa.
kij
2

Lo que normalmente hago en C # es hacer que mis métodos estén protegidos y no privados. Es un modificador de acceso un poco menos privado, pero oculta el método de todas las clases que no heredan de la clase bajo prueba.

public class classUnderTest
{
   //this would normally be private
   protected void methodToTest()
   {
     //some code here
   }
}

Cualquier clase que no herede directamente de classUnderTest no tiene idea de que methodToTest incluso existe. En mi código de prueba, puedo crear una clase de prueba especial que se extiende y proporciona acceso a este método ...

class TestingClass : classUnderTest
{
   public void methodToTest()
   {
     //this returns the method i would like to test
     return base.methodToTest();
   }
}

Esta clase solo existe en mi proyecto de prueba. Su único propósito es proporcionar acceso a este método único. Me permite acceder a lugares que la mayoría de las otras clases no tienen.

astronauta
fuente
44
protectedforma parte de la API pública e incurre en las mismas limitaciones (nunca se puede cambiar, debe documentarse, ...). En C # puedes usarlo internaljunto con InternalsVisibleTo.
CodesInChaos
1

Puede probar métodos privados fácilmente si coloca sus pruebas unitarias en una clase interna en la clase que está probando. Usando TestNG, sus pruebas unitarias deben ser clases internas estáticas públicas anotadas con @Test, de esta manera:

@Test
public static class UserEditorTest {
    public void test_private_method() {
       assertEquals(new UserEditor().somePrivateMethod(), "success");
    }
}

Como es una clase interna, se puede llamar al método privado.

Mis pruebas se ejecutan desde maven y automáticamente encuentra estos casos de prueba. Si solo quieres probar una clase, puedes hacerlo

$ mvn test -Dtest=*UserEditorTest

Fuente: http://www.ninthavenue.com.au/how-to-unit-test-private-methods

Roger Keays
fuente
44
¿Está sugiriendo incluir código de prueba (y clases internas adicionales) en el código real del paquete que se implementa?
1
La clase de prueba es una clase interna de la clase que desea probar. Si no desea que se implementen esas clases internas, puede eliminar los archivos * Test.class de su paquete.
Roger Keays
1
No me encanta esta opción, pero para muchos es probablemente una solución válida, no vale la pena los votos negativos.
Bill K
@RogerKeays: Técnicamente, cuando haces una implementación, ¿cómo eliminas esas "clases internas de prueba"? Por favor, no digas que los cortas a mano.
gyorgyabraham
No los quito. Si. Implemento código que nunca se ejecuta en tiempo de ejecución. Es realmente así de malo.
Roger Keays