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?
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?
Respuestas:
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.
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.
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.
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.
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/ )
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.
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.
Si realmente necesita probar un método privado, con Java quiero decir, puede usar fest assert
y / 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.
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.
protected
forma parte de la API pública e incurre en las mismas limitaciones (nunca se puede cambiar, debe documentarse, ...). En C # puedes usarlo internal
junto con InternalsVisibleTo
.
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