Las pruebas
Introducción
Las pruebas son un medio esencial para que un proyecto compruebe que el código sigue siendo funcional y que no hay errores durante el desarrollo del sitio web. En un espíritu de integración continua y construcción automática de la solución, las pruebas están ahí para validar que cada nueva parte de código no ha incorporado nuevos errores al sistema.
ASP.NET Core se ha diseñado para mejorar la comprobabilidad del código de usuario y aumentar así la mantenibilidad de la aplicación. Las pruebas automatizadas son una buena forma de que los desarrolladores comprueben que el código que han escrito hace lo que se supone que debe hacer. Hay muchos tipos de pruebas, como las de integración, de escalabilidad, funcionales y muchas otras. Las pruebas que nos interesan en esta sección son las pruebas unitarias, es decir, pruebas cortas y sencillas que se utilizan para validar pequeños fragmentos de código.
Las pruebas unitarias pueden seguir una buena práctica llamada Test Driven Development, que consiste en escribir primero las pruebas y luego desarrollar las funcionalidades que validan las pruebas, pero no entraremos en esto en detalle. Es importante tener en cuenta que las pruebas unitarias no deben incluir dependencias de infraestructura que puedan ralentizar las pruebas. En esta sección veremos el framework xUnit...
Pruebas de servidor con xUnit
Escribir pruebas unitarias no es tan sencillo y el desarrollador debe diseñar sus clases y métodos con cuidado y desacoplar su arquitectura todo lo posible, para que sus pruebas sean lo más sencillas posible. Las dependencias de una clase pueden ocultarse siguiendo el Explicit Dependencies Principle y utilizando la inyección de dependencias: el desacoplamiento queda entonces garantizado.
Una buena práctica es separar las pruebas en un proyecto distinto del proyecto que contiene las clases. Un proyecto de pruebas es simplemente una librería de clases que hace referencia a un lanzador de pruebas (un test runner) y al proyecto que se va a probar (denominado System Under Test o SUT). La convención ASP.NET va incluso más allá, al separar los proyectos de prueba y los proyectos SUT en carpetas diferentes:
global.json
MiProyecto.sln
src/
MiProyecto/
Startup.cs
Services/
MiServicio.cs
test/
MiProyecto.UnitTests/
Services/
MiServicio_Test.cs
Un proyecto de prueba se debe configurar primero para indicar qué framework de pruebas debe utilizar. El framework recomendado por el equipo de ASP.NET Core es xUnit. Basta con añadirlo al archivo projet.json del proyecto de prueba.
"dependencies": {
"MiProyecto": "1.0.0",
"xunit": "2.1.0",
"dotnet-test-xunit": "1.0.0-rc2-build10025"
}
La primera dependencia se utiliza obviamente para probar las clases en nuestro proyecto SUT. La segunda dependencia permite añadir el test runner al proyecto de pruebas. Finalmente, la última dependencia se utiliza para ejecutar las pruebas utilizando la línea de comandos dotnet test.
Existen varios tipos de pruebas con xUnit:
-
Facts: pruebas que son siempre verdaderas y que prueban condiciones invariantes....
El arte del Mock
Al escribir una prueba unitaria, el desarrollador se encuentra a menudo con varias dependencias que hay que resolver, para poder probar su fracción de código.
Recuerde que las pruebas unitarias sólo deben probar pequeñas partes del código, sin preocuparse de la infraestructura existente.
Por lo tanto, no es fácil probar una clase que tiene un gran número de dependencias, sobre todo porque con la inyección de dependencias proporcionada por ASP.NET Core, el árbol de dependencias puede tomar rápidamente demasiadas proporciones. Además, al escribir una prueba, el desarrollador no desea necesariamente probar en profundidad las funciones de registro o de conexión a la base de datos: el objetivo es que los datos estén disponibles para la prueba, sin preocuparse de su procedencia, siempre y cuando satisfagan el código que se va a probar.
Por tanto, el desarrollador necesita una nueva herramienta que implemente el patrón de prueba "objeto simulacro", es decir, un mock. Un simulacro es un objeto que sustituye a un objeto real (en nuestro caso, puede corresponder a una de las dependencias del código que queremos probar), pero que devuelve un resultado que el desarrollador predefine. De esta forma, el desarrollador no tiene que preocuparse de las dependencias subyacentes y controla los datos de entrada del código que quiere probar.
Los mocks se utilizan generalmente en las siguientes situaciones:
-
Dependencias cuyo comportamiento no se puede predecir de antemano (lectura de la temperatura, fecha del día, etc.).
-
Dependencias difíciles de gestionar (acceso a bases de datos).
-
Dependencias con fuertes restricciones vinculadas al entorno en el que se ejecuta la aplicación (acceso a la red, datos GPS, etc.).
-
Dependencias de la interfaz de usuario.
-
Dependencias sujetas a métricas, como el número de veces que se ha llamado a un método. Este tipo de prueba se puede utilizar, por ejemplo, para validar el funcionamiento de una caché....
Pruebas de clientes con Jasmine y Karma
La Web ha cambiado mucho en los últimos años, con sitios cada vez más interactivos, que dejan cada vez más responsabilidad al cliente en lugar de al servidor. Los ejemplos siguientes ilustran las ventajas de este tipo de proceso:
-
Carga o recarga parcial de la página (como parte de un menú).
-
Validación de formularios por parte del cliente.
-
Recupera datos sin actualizar la página.
-
Reducción de la carga del servidor.
Por tanto, todas estas acciones se realizan en JavaScript en el cliente, pero esto rápidamente se puede convertir en algo inmanejable si el código no está al menos organizado y conservando una determinada arquitectura. De lo contrario, el navegador se puede comportar de forma inexplicable, lo que dificulta y hace tediosa la depuración. Las pruebas son una buena forma de garantizar que el código JavaScript realiza realmente las acciones solicitadas.
No siempre ha sido fácil probar código JavaScript, pero con las tendencias actuales, han surgido nuevas herramientas para satisfacer rápidamente esta necesidad. El resto de esta sección analizará una de ellas: Jasmine.js.
En primer lugar, es importante recordar algunos datos importantes sobre Jasmine:
-
Un runner se encarga de buscar y ejecutar las pruebas. Cada runner puede funcionar de forma diferente, por ejemplo, ofreciendo categorización de pruebas o paralelización.
-
Un reporter simplemente muestra los resultados generados por el runner.
Por defecto, Jasmine utiliza un runner para lanzar las pruebas y un reporter para mostrar los resultados en HTML. Observe el siguiente código de prueba, que simplemente convierte grados Celsius en grados Fahrenheit:
var MiLib = {
celsiusToFahrenheit: function(celsius) {
if(isNaN(libras - 1)) {
throw "ValorTemperaturaIncorrecta";
}
return (celsius * 1.8) + 32;
}
};
Según el código, hay dos pruebas relevantes:
-
Comprobar que la conversión se realiza correctamente, con un parámetro...
La alternativa con MSTest
El framework MSTest (Microsoft Test) permite crear, ejecutar y gestionar pruebas unitarias para código .NET. Es similar al framework xUnit, en el sentido de que los desarrolladores pueden utilizar atributos de métodos y clases para definir sus pruebas. La diferencia radica en la gramática de los atributos, pero también en el concepto básico: xUnit se diseñó con una sintaxis minimalista, mientras que MSTest se diseñó para ser compatible con las herramientas de pruebas existentes, por lo que quizás resulte un poco menos fácil para los desarrolladores familiarizarse con él al principio.
Los atributos importantes de MSTest son los siguientes:
-
[TestMethod]: este atributo se utiliza para decorar los métodos de prueba. Cada método será entonces una prueba que se ejecutará para MSTest.
-
[TestInitialize]: este atributo se utiliza para decorar un método que se ejecutará antes de cada prueba. Esto permite inicializar objetos que deben estar "limpios" antes de la ejecución de cada prueba.
-
[TestCleanup]: este atributo se utiliza para decorar un método que se ejecutará después de cada prueba. Esto hace posible limpiar ciertos objetos o dependencias que necesitan estar "limpios" después de cada ejecución de la prueba.
-
[ClassInitialize]: Este atributo se utiliza para decorar un método que se utilizará antes de un conjunto de pruebas y más concretamente, antes de todas las pruebas de la clase actual. Esto permite inicializar objetos y/o variables una sola vez antes de todas las pruebas.
-
[ClassCleanup]: este atributo se utiliza para decorar un método que se utilizará después de un conjunto de pruebas y, en particular, después de todas las pruebas de la clase actual. Esto permite limpiar ciertos objetos o dependencias una...