Hacer accesible un método Java en tiempo de ejecución

No es habitual. Puede que incluso sea síntoma de un mal diseño OO. Pero en ocasiones nos puede venir muy bien ser capaces de hacer accesible un método en tiempo de ejecución. Por ejemplo, para hacer pruebas de un método privado.

El truco está en usar Reflection. Reflection es un API Java que forma parte del JDK (paquete java.lang.reflect) y que permite acceder y manipular la meta-información de las clases y objetos de tu código Java en tiempo de ejecución. No es algo que se use todos los días (a no ser que programes ides, debuggers o frameworks), pero conviene saber que existe y conocer sus capacidades, por si acaso...

Pero vayamos al grano. El código es muy sencillo. A partir de una instancia, (1) obtenemos su objeto Class, (2) obtenemos el objeto Method que representa el método en cuestión, (3) modificamos su accesibilidad y (4) finalmente lo ejecutamos pasándole los argumentos necesarios y guardamos su salida si tiene. Un ejemplo de un método sayHello con un parámetro String y un return String sería éste:


Method metodo = instancia.getClass().getDeclaredMethod("sayHello", new Class[]{String.class});
metodo.setAccessible(true);
String salida = (String)metodo.invoke(instancia, new Object[]{"argumento1"}));


¿Y si fuera un constructor? Practicamente igual, pero usando esta vez el objeto Constructor y teniendo en cuanta que ahora no tenemos una instancia. Un ejemplo con un constructor sin parámetros sería éste:


Constructor constructor = NombreClase.class.getDeclaredConstructor(new Class[0]);
constructor.setAccessible(true);
NombreClase instancia = (NombreClase)constructor.newInstance();


Y esto es todo. Rápido y fácil. Ahora no lo uséis mal...

7 comentarios :: Hacer accesible un método Java en tiempo de ejecución

  1. Gracias por el truco, me parece especialmente útil para hacer tests unitarios directamente sobre métodos privados.

    Saludos

  2. Yo lo utilizo en un proyecto interno para sacar un botón por programa y que me ponga el nombre y el tooltip de las propiedades del mismo

  3. No hagais test sobre metodos privados hombre po dios!!

    Precisamente una de las ventajas de los test unitarios es que ayudan a guiar un buen diseño. Solo se deben probar los metodos públicos, y si probar un metodo publico es muy complejo porque tiene N metodos privados a los que llama entonces es que el código es demasiado complejo y hay que refactorizar en clases más pequeñas y con responsabilidades más concretas.

    En mi opinión, y se que algunos les sonara radical, es preferible evitar los metodos privados o protected.

  4. Sí, probar metodos privados puede (suele) ser un síntoma de mal diseño. Ya lo decía al principio...
    Pero a veces no queda otra, como cuando te toca código legacy o simplemente no puedes modificar el código fuente.

    Lo de los métodos protected sí que no lo comparto. Las clases de tests deben estar en el mismo package que la clase que prueban (pero en diferentes carpetas físicas). De ese modo, la clase de tests tiene visibilidad sobre todos los métodos que no sean private.

    Podéis ver más sobre buenas prácticas de tests aquí: http://jcesarperez.blogspot.com/2009/04/buenas-practicas-para-programar-tests.html

  5. Es buena practica que las clases de test estén en el mismo paquete y distinta carpeta fisica. Pero en mi opinión no para poder hacer pruebas directas sobre metodos protected sino para poder llamar a constructores (o setters aunque esto tampoco me gusta demasiado) donde puedas establecer objetos mock para las interfaces de las que dependa tu clase. Aunque esto usando inyección de dependencias en realidad también se puede resolver, y los test en el mismo paquete terminan siendo simplemente una buena practica porque mantiene el proyecto organizado.

    En mi opinión los test nunca se deben hacer sobre nada que no sea public, de esta forma los test son independientes de la organización interna de la clase y puedes cambiar esta sin tener que modificar los test.

    Y otra que a muchos les sonara radical: Nunca usar el modificar protected, esto en realidad deriva de otras dos practicas:
    - nunca usar herencia de clases (de interfaces si por supuesto) y siempre preferir composición como mecanismo de reutilización.
    - siempre depender de interfaces y nunca de implementaciones concretas, es decir, ninguna clase depende de nada que no sea un interfaz.

    Con estas dos normas carece de sentido el uso de protected excepto quiza para algún constructor donde se inyecten dependencias a mano para los test.

    Llevamos más de un año siendo así de radicales, y estamos muy contentos con el resultado, menor acoplamiento, mayor cohesión , clases con responsabilidades mucho mejor definidas y menos complejas y test que se entienden!! (esto nos mataba antes, test que eran tan complejos que al final eran una pesadilla de mantener).

  6. Hola Alfredo. Me alegra mucho verte por aquí. No se porqué pensaba que eras tú...

    Estoy contigo en, por lo general, hacer tests de la parte pública de las clases. Yo iría un poco más allá y diría que los tests deben hacerse principalmente en las clases Service. Así obtienes mayor cobertura y sobretodo no repites tests que luego son costosos de mantener. Como los tests de las clases dao que luego se repiten en las clases service.

    Lo del protected es bastante radical sí. ¿Tus métodos que convierten los objetos de dominio a dtos y viceversa también son públicos? ¿O lo que haces es que esas clases menos importantes no sean públicas?

    Por mi parte, coincido también contigo en preferir la composición como mecanismo de reutilización de código frente a la herencia. Pero de ahí a prohibirla... Es cierto que suele abusarse de ella, pero hay ocasiones donde encaja de forma natural. ¿Tampoco usas clases abstractas?

    Un saludo.

  7. Aún no he visto librería que no utilize herencia... No se, la suelo utilizar para reusar código. De hecho, Herencia y Polimorfismo eran las características fundamentales en que pensaba Bjarne Stroustrup cuando inventó el C++.

Publicar un comentario