Buscar en este blog....

Mostrando entradas con la etiqueta Testing. Mostrar todas las entradas
Mostrando entradas con la etiqueta Testing. Mostrar todas las entradas

miércoles, 31 de diciembre de 2014

¿Cómo testear métodos privados?

A la hora de hacer los unit testing, tarde o temprano nos llega indefectiblemente la pregunta: ¿cómo testeo un método privado?

El asunto en cuestión es motivo de distintos puntos de vista, opiniones, y discusiones. Sin embargo basado un poco en la experiencia personal y la ajena, más un poco de literatura, ofrezco mi punto de vista.

Existen dos opiniones diferentes al respecto: unos dicen que los métodos privados no deben testearse, y otros que si. Ah, y bueno, están los grises que dicen que "depende de la situación". Yo me incluyo en este último grupo. La ingeniería de software es una ciencia muy "gris" todavía.

La cruda realidad, es que con muchos de los frameworks no podemos testear métodos privados, por la simple razón de que son privados y no podemos acceder a ellos. (No lo dije antes, pero doy por hecho que los tests se encuentran en un proyecto aparte de aquel que contiene el código que estamos testeando y por eso no podemos accederlos).

Los que dicen que no deben testearse métodos privados, tienen sus razones. Principalmente sostienen que solo debe testearse la interfaz de, pongámosle, una clase. Es decir, todos aquellos métodos públicos que sirven de interfaz al mundo exterior (los programadores que utilizan estos métodos). Y que a su vez, los métodos privados son solo "ayudantes" de los métodos públicos. Estos pueden cambiar con mayor frecuencia que los métodos públicos, y por tanto, una eventual modificación de los métodos privados que no debiera alterar los resultados de los métodos públicos, podría hacer fallar los tests que testean los métodos privados. Es decir que se cambió el comportamiento interno sin alterar el comportamiento externo y que, por lo tanto, solo debemos preocuparnos por testear el comportamiento externo.

Yo no estoy precisamente de acuerdo con este punto de vista. Si queremos testear el método, deberíamos poder. La realidad es que así nomás no se puede. Entonces salen alguna alternativas que voy a mencionar, y me alegraría siempre de escuchar o leer nuevas.

1) Mover los métodos privados a una nueva clase.
Si el/los método/s privados que queremos testear lo ameritan, los refactorizamos creando una -o más- nueva clase que solo los contenga a ellos -pero como métodos públicos- y no formen parte de la interfaz de nuestra clase. De esta forma seguirán estando "ocultos" hacia el mundo exterior (porque no pertenecen a la interfaz), y sin embargo ya podemos accederlos tranquilamente desde el proyecto de testing.

2)  Convertir los métodos privados en públicos.
No soy un gran entusiasta de esta opción. Creo que si un método se pensó privado, privado debe permanecer. Si se cambia a público, la razón debería tener que ver con otras cuestiones y no con el testing. Pero ya dicho, es una opción más.

3) Convertir los métodos en internal.
Desconozco si existe en otros lenguajes el equivalente a internal de C#. Con esto permitimos que las clases dentro del mismo ensamblado se puedan ver. Sin embargo, fuera del ensamblado siguen permaneciendo ocultas. Y debemos especificar a qué ensamblado le damos visibilidad (con [assembly:InternalsVisibleTo("MyTests")]).

Cuando puede utilizarse, soy partidario de la tercera forma, ya que es bastante más limpia. En otro caso, habrá que evaluar qué hacer, o incluso si coincidir con quienes dicen que los métodos privados no tiene estrictamente que testearse.

Hasta pronto!

jueves, 25 de diciembre de 2014

Fakes, Stubs y Mocks... ¿cuál es la diferencia?

Buenas!
Como me viene pasando últimamente, pasan décadas de un posteo a otro. Sin embargo y pese a ello: sigo posteando ;)

El tema de este post surgió nada menos que de darme cuenta que los conceptos que voy a explicar, no estaban adecuadamente explicados en la facultad a la que asistí. Quizás esto ya haya cambiado, pero considero que son términos importantes como para estar mezclándolos cual ensalada de frutas.

Básicamente, donde yo estudié la idea -muy genérica y nada desarrollada- de un Stub nunca se mencionó la diferencia entre Stubs y Mocks, y tampoco la diferencia entre éstos dos con un Fake. Es más, me atrevo a decir que nunca se habló de Fakes y Mocks. Pero bueno, no es el fin de este artículo evaluar ni criticar lo que tuve que estudiar, sino más bien, aclarar los conceptos.

Todo esto viene a colación de la importancia que han tomado los Unit Tests (Tests de Unidad) de los cuales no voy a hablar ahora para no extenderme en el tema. Doy por hecho que ya sabes lo que son.

Pero, ¿por qué es importante? 

Es de suprema importancia realizar UTs que sean legibles, que cualquier persona que nunca los vio antes pueda leerlos y darse cuenta de inmediato qué quiso testear quien lo escribió. Si esto no es así, se pierde la filosofía que está detrás de los UT, que es justamente proporcionar un método muy rápido de testeo. Si un programador tiene que dedicar mucho tiempo a entender un test que ha dado FAIL (o peor aún: muchos tests!) por el solo hecho de que se escribió mal o que es ilegible, el resultado es terriblemente predecible: no lo va a leer y lo va a dejar de lado porque entender y/o arreglar ese test le va a consumir mucho tiempo, y seguramente "tiene cosas más importantes que hacer". Se ignora el test, se lo marca como obsoleto, etc. Y allí comienza la decadencia del sistema. Y de la calidad. Y de la mantenibilidad. Y etc, etc.

Siempre es una necesidad que tengas claro los conceptos referidos a tu especialidad, a lo que te dedicas a hacer.

Antes que nada, esta entrada presupone que ya sabes lo que es un test de unidad, cual es su finalidad, y que significan las tres A (Arrange-Act-Assert). Si no es asi, sería bueno que le googlearas por ahí.

Bien, comencemos entonces:

¿Qué es un Fake?
El verbo "to fake" en inglés es fingir, falsificar. Eso es lo que es un Fake: una falsificación de un objeto. (Hablo de "objetos" por que normalmente en mi blog me refiero a la OO (orientación a objetos) por ser la que más utilizo, pero es aplicable también a otros paradigmas.)

Es decir, es un objeto que es una falsificación de otro. Se ve "igual" pero se comporta distinto.

¿Y para qué sirve?
Porque si queremos realizar pruebas de unidad, es indispensable que estas sean rápidas! Pero no sólo por esto! Sobre todo que el comportamiento de la clase (o unidad de trabajo) que estamos testeando (llamémsole UUT de "Unit Under Test") no se debe ver influenciado por el comportamiento de algún otro objeto del que pueda hacer uso. Si por ejemplo queremos testear el método de una clase que por dentro hace uso de un objeto que se comunica con la base de datos, tenemos dos problemas:

  1. El objeto que utiliza la base de datos (llamemosle "ConexionConBD") hará muy lento el testing, ya que debe conectarse con la mismísima base de datos, entorpeciendo este principio.
  2. Si el objeto "ConexionConBD" falla cuando nosotros esperamos que no falle, entonces fallará también el UUT (nuestra unidad bajo testing)..
  3. Si la mismísima base de datos falla (por la razón que fuere, cualquiera), nuevamente fallará también nuestro UUT.

Y el problema con las fallas que son "ajenas" a nuestro UUT, es que finalmente no vamos a saber si lo que falla es el UUT, o es alguno de los objetos que éste utiliza. Y pierde sentido el concepto de "Test de UNIDAD".

Entonces, para solucionarlo, inventemos un "ConexionConBD" que responda lo que nosotros queramos, cuando nosotros queramos, y como nosotros queramos. Es decir, falsifiquemos esa clase. Para ello, crearemos un Fake de la clase "ConexionConBD", la cual podemos controlar desde el test que estamos escribiendo.

No voy a entrar en detalles técnicos de como se hace, ya mucho en internet sobre como hacerlo.

Entonces....

¿Qué es un Stub?
El Stub, es ni más ni menos, un subtipo de Fake. Si, asi de simple: los Fakes se dividen en dos clases: o son Stubs, o son Mocks. ¿Y de qué depende? De según como se use, de eso depende!

En el caso anterior, podemos utilizar el Fake "ConexionConBD" para que siempre nos devuelva un valor esperado, así no nos encontramos con sorpresas. Una vez que el fake nos devuelve el valor que esperamos, podemos proseguir con el test.

Pero al usar este fake como un Stub, éste sólo puede utilizarse para devolver un valor o arrojar una exepción. Pero nada más. NADA MÁS! Es decir, el test se sigue haciendo directamente contra nuestro UUT.



¿Y qué es un Mock?
En el caso anterior, el fake se utiliza solo para que devuelva un valor esperado, es decir, que realice un comportamiento definido y así poder proseguir con nuestro test.

Pero en otras ocasiones, el test mismo consiste en saber si el UUT se comunicó de forma esperada con otro objeto. Por ejemplo, queremos saber si el UUT llamó al objeto "ConexionConDB" de forma correcta. En este caso, no podemos hacer un assert directamente contra la UUT, ya que la UUT mismo no sabe si la llamada -a un método por ejemplo- fue realizada con éxito. Quién sabe esto, es el objeto llamado en cuestión, es decir, el objeto "ConexionConBD". Pero el "ConexionConBD" original  (aquel que se conecta con la base de datos) no está programado para decirnos si la llamada que le hicieron fue correcta, y encima, se conecta con la base de datos! (algo que queremos evitar!).

Solución: le hacemos un fake. Pero este fake no devuelve un valor, sino que simplemente recibe la llmada que le hace la UUT y, nuestro test, en lugar de hacer un assert contra la UUT, lo hace contra el Fake.



Y esta es la clave: cuándo nuestro test verifica (assert) contra un fake, este fake es un Mock.
Si en cambio la verificación se hace contra la UUT, entonces cualquier fake que haya será un Stub.

Resumiendo.

Todo fake será indefectiblemente un Stub o un Mock dependiendo de cómo se lo utilice. Es más, un fake puede ser Stub en un test, y Mock en otro test. La diferencia está en el uso que se le da a ese fake. Nada más y nada menos.

Según Roy Osherove (The Art Of Unit Testing, un libro que recomiendo totalmente y del cual saqué la idea para las imágenes) cada UT (Unit Test) no debería tener más de un Mock, y sí puede tener más de un Stub. Osherove es partidario de que si un UT tiene más de un Mock, el test pierde legibilidad y se hace más complicado de entender "inmediatamente" qué es lo que se quiere testear.

Espero que haya sido suficientemente claro, ya que es un tema de muchas confusiones por lo que he visto yo.

Hasta pronto, espero!