bug.pngAl comenzar a desarrollar usando la técnica de Desarrollo Guiado por Tests (TDD - Test Driven Development) es común preguntarse qué hacer con los métodos privados. ¿Cómo se deben probar? ¿Qué ocurre con estos métodos que no podemos acceder directamente desde las pruebas unitarias?

Si realmente estamos haciendo TDD, los métodos privados tienen cobertura garantizada. Cuando el diseño y la implementación en el código se guia a partir de las pruebas unitarias, ningún método se crea como privado. En cambio, los métodos privados se extraen (el refactor) de un método público ya existente.

Creación de métodos nuevos usando TDD

En el siguiente ejemplo, pensemos en la secuencia de desarrollo usando TDD para crear a metodoA y metodoB.

  1. crear testMetodoA / crear metodoA / refactor
  2. crear testMetodoB / crear metodoB / refactor: extraer metodoC

En el primer paso, creamos testMetodoA para validar la funcionalidad que esperamos del metodoA. Luego creamos a metodoA: todas las pruebas de testMetodoA pasan con éxito. Luego buscamos oportunidades de mejorar el código: el refactor.

En el segundo paso, creamos testMetodoB para validar la funcionalidad que esperamos del metodoB. Luego creamos a metodoB: todas las pruebas de testMetodoB pasan con éxito. Luego buscamos oportunidades de mejorar el código: el refactor.

Cuando estamos haciendo el refactor de metodoB, nos damos cuenta que metodoA y metodoB tienen un fragmento de código en común (código duplicado). Extraemos entonces el fragmento en común y lo ubicamos en un método privado metodoC. Luego, metodoA y metodoB invocan a metodoC.

En este ejemplo, testMetodoA cubre a metodoA, el cual invoca al método privado metodoC; así, el código de metodoC también es cubierto.

Cuando se hace TDD, los métodos privados emergen del código. Y cómo las pruebas guian al desarrollo, no se agrega ninguna funcionalidad sin una prueba. Luego, si el código está desarrollo completamente usando TDD no se tienen que probar los métodos privados en forma separada: los métodos privados ya están siendo probados por otras pruebas escritas con anterioridad a su existencia.

Mejorando código legacy con Desarrollo Guiado por Refactor

Muchas veces no contamos con código nuevo, y tenemos una buena cantidad de código legacy que mantener (recordemos, código legacy es aquel que no tiene pruebas y, por lo tanto, es dificil y riesgoso de mantener). En estos casos, podemos usar Desarrollo Guiado por Refactors (TDR - Test Driven Refactoring).

TDR es un enfoque evolutivo para mejorar el código legacy. Básicamente consiste en comenzar escribiendo una prueba que pase con éxito la porción de código a mejorar, y luego realizar el refactor del código; mejoramos el contenido interno, pero seguimos pasando las pruebas.

Cuando se hace TDR, se realizan pequeños pasos de refactor y, a veces, es necesario probar a métodos privados. Esto pasa usualmente con código que no tiene cobertura alguna, y con métodos públicos que son muy complejos para escribir pruebas directamente.

Supongamos el siguiente código:

public void metodoComplicado() {
...
//codigo muy complejo acá
...
//se invoca a un metodo privado metodoY()
String s = metodoY();
...
}

En este escenario, el método público metodoComplicado no tiene pruebas unitarias. No nos sentimos cómodos en hacer un refactor ya que no tenemos pruebas para garantizar que siga todo funcionando igual. Por lo tanto, vamos a hacer TDR para mejorar el código. Veremos dos enfoques distintos para aplicar TDR y mejorar el código de metodoComplicado().

TDR de arriba hacia abajo

Primero creamos pruebas para el método complejo metodoComplicado():

public void testMetodoComplicado() { ... }

Así tenemos cobertura de código alrededor de la funcionalidad de metodoComplicado(), y podremos realizar el refactor del código, incluyendo el refactor del método privado metodoY().

En este enfoque "de arriba hacia abajo" creamos primero la prueba unitaria para el método público, y luego se realiza el refactor de su forma interna (incluyendo los métodos privados).

Veamos el otro enfoque de TDR.

TDR de abajo hacia arriba

No creamos pruebas para el método público complejo. En cambio, vamos a buscar pequeñas partes a mejorar.

Primero cambiamos el nivel de acceso del método privado para hacerlo accesible desde una prueba unitaria. Así, el método:

private String metodoY() {...}

se convierte en:

String metodoY() {...}  //nivel de acceso de paquete

Entonces escribimos la prueba para metodoY():

public void testMetodoY() {...}

Luego realizamos el refactor de metodoY() y verirficamos que los cambios funcionan ya que el testMetodoY() sigue pasando con éxito.

En el enfoque de TDR "de abajo hacia arriba" primero mejoramos la cobertura de código y el mismo código de los métodos privados y las partes internas de un método complejo. Al ir avanzando, el código se va volviendo menos complejo; eventualmente podremos ocuparnos del método público.

Cuando aplicamos este enfoque en el ejemplo, el método privado metodoY() se volvió de acceso de paquete para poder probarlo unitariamente. Luego de incrementar la cobertura de código y mejorar las partes internas, va a ser más facil crear una prueba para el metodoComplicado() y, finalmente, realizar el refactor de este método.

Diferencias de los dos enfoques de TDR

Aunque el enfoque de "arriba hacia abajo" es más purista (ya que no cambia el nivel de acceso de los métodos), su implementación no siempre es facil. El método público podría ser imposible de probar en el estado actual (por dependencias estáticas, largo, complejidad, etc.). Para muchos casos, el enfoque de "abajo hacia arriba" puede resultar más conveniente. En todo caso, en general se puede aplicar ambos enfoques para ir logrando una solución más limpia, dependiendo del caso.

En resumen, al hacer TDR creamos pruebas provisionales para los métodos privados. En Java esto significa cambiar el nivel de acceso de estos método para poder probarlos. De esta manera, la clase de prueba unitaria podrá tener acceso a estos métodos.

Pero el refactor no termina hasta que podamos quizar estas pruebas de los métodos privados.

Entonces, ¿debo crear pruebas para los métodos privados?

Si estamos aplicando TDD con éxito, el código no va a tener pruebas específicas para los métodos privados.

Al hacer TDD, toda la funcionalidad está respaldada por una prueba. Los métodos privados se crean sólo como resultado de un refactor. Y el camino de ejecución que pasa por ese método privado ya está siendo probado por alguna prueba ya escrita. Es decir, luego de usar TDD no vamos a tener pruebas específicas para métodos privados.

Basado en Testing private methods, TDD and Test Driven Refactoring.

Inspiración.

"Si tú tienes una manzana y yo tengo una manzana e intercambiamos las manzanas, entonces tanto tú como yo seguiremos teniendo una manzana cada uno. Pero si tú tienes una idea y yo tengo una idea, e intercambiamos las ideas, entonces ambos tendremos dos ideas"

Bernard Shaw