En el anterior artículo Unit test: ¿Antes o después del desarrollo?, introdujimos la creación de Unit Testing para nuestras aplicaciones. En este artículo mostraremos un ejemplo práctico para así entender qué es y cómo realizar una Prueba Unitaria.
Antes de empezar…
Cuando desarrollamos nuestras pruebas unitarias es importante aislar el código que deseamos testear. Esto significa que cualquier dependencia que implique que nuestro test puede comportarse de una manera o de otra deberá estar controlada. Para realizar esto, utilizamos lo que se denomina como “mocking”. El “mocking” consiste en simular clases o métodos, de los cuales nuestro código tiene una dependencia, para asignar valores o comportamientos predeterminados. De esta manera podremos asegurar que lo que queremos testear es realmente lo que vamos a testear.
Frameworks de “mocking” hay muchos NSubstitute, FakeItEasy, JustMock… Para el desarrollo de los ejemplos que mostraremos a continuación hemos utilizado el framework NMock3
Al final del artículo podréis encontrar un enlace al código de los ejemplos para que podáis descargarlo y jugar con ellos.
El cajero automático de ACME Bank
Nuestro cliente ACME Bank nos ha pedido que desarrollemos la lógica para poder validar cuándo un cliente puede extraer dinero de sus cajeros. Para ello hemos desarrollado un método llamado ValidateCreditCardToGetMoney en la clase CashMachineService.
Como podemos ver, el método realiza varias validaciones. Si todas las validaciones se cumplen satisfactoriamente el método retornará cierto, en caso contrario nos lanzará una excepción con la descripción del motivo de la no validación.
Vemos que las validaciones están encapsuladas en métodos de la misma clase. El primer método CheckCreditCardPin validará que el número de tarjeta y el pin sean correctos. El segundo CheckPositiveBalance comprobará que el balance de la cuenta asociada a la tarjeta sea positivo.
A continuación se muestra el código del método CheckCreditCardPin.
Este método llama a una función de la clase que implementa la interfaz ICreditCardService. El código de esta clase no sabemos qué acciones realiza para validar o no la correspondencia entre el número de tarjeta y el pin. Todo lo que se hace en este método queda fuera del alcance de nuestro test, por lo que necesitaremos proporcionar un comportamiento determinado. De esta manera podremos verificar la lógica de nuestro método y únicamente de nuestro método. Para esto será necesario el uso de nuestro framework de “mocking”.
A continuación mostramos un ejemplo del test unitario que verifica que nuestro método se comporta de la forma esperada en el caso que el pin no sea válido para la tarjeta de crédito introducida en el cajero.
El método de test está dividido en tres partes diferenciadas:
- Configuración de todos los objetos necesarios. En esta parte del código, es necesario el uso del framework de “mocking” para asignar un comportamiento específico a los objetos dependientes de la clase que estamos testeando. En este caso en concreto, se está configurando para que cuando llamemos al método ValidatePin con cualquier argumento, siempre nos devuelva falso en la respuesta. También se está configurando que ValidatePin reciba una única llamada y que GetBalance no reciba ninguna. De esta manera podremos asegurar que nuestro código se comporta de la forma esperada cuando el número de tarjeta y el pin no sean válidos.
-
Ejecución del propio método a probar. Usando unos parámetros previamente definidos se llama al método y se recoge el resultado en una variable para validarla, si es necesario, en la última parte.
-
Validación. En esta parte se realizan las evaluaciones necesarias con el fin de verificar que la ejecución de nuestro método ha finalizado realizando el comportamiento esperado. En el ejemplo en concreto, se valida que el método retorne una excepción y que esta excepción contenga un mensaje previamente definido. Finalmente se llama a un método propio del framework de NMock3, para asegurar que el número de llamadas a los métodos configurados por nuestro objeto “mockeado” se cumplen.
La solución completa tiene tres pruebas unitarias con el fin de cubrir todo el código del método ValidateCreditCardToGetMoney.
En un próximo post explicaremos con más detalles buenas prácticas en la creación de pruebas unitarias.