Las pruebas
Introducción
Cuando un cliente pide un desarrollo, se elaboran las especificaciones, que describen las funcionalidades de alto nivel, en forma de casos concretos de uso. Este documento servirá más tarde para validar los desarrollos realizados. Estos casos de uso van a originar muchos objetos desarrollados por el equipo. Estos objetos se van a comunicar entre ellos con los métodos y las secuencias diseñados durante el análisis. La mayor parte de las veces, cada intercambio se realizará con los argumentos de "ida" y "vuelta". Los rangos admisibles de estos argumentos serán conocidos y estos objetos funcionarán generalmente bien, cuando reciban lo que esperan, en el momento que lo esperan. Pero, ¿qué sucede cuando aumente el ritmo o los argumentos que se pasan estén fuera de los límites?
Es ahí cuando se muestra la solidez de aplicación, en los casos extremos por operaciones adaptadas a los fallos y una buena protección de los datos. Para ganar este grado de fiabilidad, en primer lugar, cada eslabón de la cadena debe permanecer estable, sean cuales sean sus condiciones de explotación. Para ello hay que experimentar llevándolos a sus límites. El desarrollador deberá imaginar los peores casos de uso de sus objetos, desde su codificación, y observarlos inmediatamente generando el código más seguro...
Entorno de ejecución de las pruebas unitarias
Siempre es posible escribir "pequeñas aplicaciones" autónomas que permitan probar los objetos de la futura "gran aplicación". Por ejemplo, un código cargado en una consola podrá instanciar la clase a probar, después desarrollar una serie de llamadas que muestren mensajes de error o que escriban los resultados en un archivo. Por supuesto es posible, pero no muy práctico.
.NET y Visual Studio 2015 son versiones que simplifican la codificación, ejecución y análisis de las pruebas unitarias. No son necesarias "pequeñas aplicaciones" autónomas; Visual Studio ofrece un tipo de proyecto especial que permite escribir directamente un conjunto de pruebas que el desarrollador podrá reproducir en su totalidad, en grupo (playlist) o individualmente, gracias al explorador de pruebas. Incluso será posible programar su ejecución automática después de la compilación. Los resultados de las pruebas se resumen en una tabla que utiliza los colores rojo y verde, y que permiten ir rápidamente en caso de error a la línea de código que ha fallado. Es posible ejecutar las pruebas en modo Debug y, por tanto, trazar los métodos llamados en los objetos que se están probando.
A continuación se muestra un ejemplo de resultado de ejecución de dos pruebas....
El proyecto de pruebas unitarias
Las pruebas son métodos de clase que forman parte de un proyecto especial Proyecto de prueba unitaria que añadimos, la mayor parte de las veces, a la solución que contiene las clases a probar.
Después hay que añadir al proyecto de prueba unitaria la o las referencias a los ensamblados que contienen las clases a probar.
La clase de pruebas
El código de inicio de una clase de prueba es el siguiente:
using System;
using Microsoft.VisualStudio.TestTools.TestClassing;
using ClassLibrary;
namespace TestClassProject1
{
[TestClass]
public class TestClass1
{
[TestMethod]
public void PruebaMetodo1()
{
}
}
}
Encontramos un espacio de nombres que contiene la clase TestClass1, que a su vez contiene el método PruebaMetodo1.
El nombre de la clase está precedido por el atributo TestClass, que permite al motor de ejecución identificarla como una clase de pruebas. Esta clase está cerrada, pero es posible definirla parcialmente (partial) y, por tanto, implementarla en varios archivos de código fuente.
Cada método de pruebas debe estar precedido por el atributo TestMethod, que la distinguirá de otros posibles métodos de la misma clase.
Contenido de un método de prueba
Cuando una prueba se ejecuta, la carga del ensamblado de destino se hace automáticamente además del entorno. La prueba debe instanciar la clase de destino si no es de tipo static, llamar a uno de sus métodos y validar el comportamiento esperado. Normalmente, solo debe haber una acción sobre el objeto para probar.
Por ejemplo, para un método Sumar, la prueba verifica que si 2 y 3 se pasan como argumentos, el resultado devuelto será 5.
Las comprobaciones utilizan los servicios de la clase Microsoft.VisualStudio.TestTools.TestClassing.Assert, que ofrece una colección de métodos mucho más completa que la de System.Diagnostics.Debug utilizada en este libro.
Entre este juego de métodos está el método IsTrue.
public static void IsTrue(bool condición, string mensaje);
Este método permite comprobar que una condición es verdadera y, si no lo es, devolver un mensaje en el explorador de pruebas.
Algunos métodos de la clase Assert ofrecen una version con mensaje y una version sin mensaje. Es aconsejable utilizar la version con mensaje para que el análisis de los problemas sea mucho más intuitivo.
Ejemplo de uso de la version Assert.IsTrue con mensaje:
[TestMethod]
public void PruebaMetodo2()
{
var vector = new Vector();
vector.Agregar(new Vector()...
Tratamientos de preparación y limpieza
La ejecución de las pruebas puede estar precedida por operaciones de inicialización y seguida de operaciones de "limpieza". Estas operaciones opcionales se escriben en métodos completados por atributos especiales que definen el momento en el que se ejecutarán durante el script.
Atributo del método |
Cuándo se ejecutará el método |
[AssemblyInitialize] |
Una vez en cada inicio de la serie de pruebas de todas las clases. |
[ClassInitialize] |
Una vez en cada inicio de la serie de pruebas de la clase. |
[TestInitialize] |
Antes de cada prueba. |
[TestCleanup] |
Después de cada prueba. |
[ClassCleanup] |
Una vez al final de la ejecución de la serie de pruebas, de la clase. |
[AssemblyCleanup] |
Una vez al final de la ejecución de la serie de pruebas, de todas las clases. |
Extracto de código que muestra la sintaxis de todos los métodos de inicialización:
[TestClass]
public class TestClass1
{
[AssemblyInitialize]
public static void MiAssemblyInitialize(TestContext tc)
{
Trace.WriteLine("Inicialización del assembly de las pruebas");
}
[AssemblyCleanup]
public static void MiAssemblyCleanup()
{ ...
TestContext y fuente de datos
El método de inicialización de la clase recibe como argumento un objeto de tipo TestContext. El uso de este objeto es opcional. Contiene determinada información sobre la configuración de la prueba que se está ejecutando, pero sobre todo permite definir una fuente de datos que va a alimentar las pruebas. Hablamos de "data-driven unit tests" y se entiende rápidamente el interés de este modo de pruebas.
Imaginemos que vamos a comprobar el comportamiento de un método en cientos de casos de uso. La colección de información será difícil de escribir y mantener en el código. Cualquier cambio hará necesaria una re-compilación de la prueba. Por estas razones se externaliza la colección. El desarrollador puede gestionar la carga y posterior iteración en la colección. Sin embargo, este no es el objetivo de la prueba.
El framework ofrece la posibilidad de alimentar las pruebas con información que proviene de bases de datos, como SQL, XML o CSV. La persona que prueba debe definir el tipo y los argumentos de acceso a la base de datos y TestUnit se encarga de hacer el resto, llamando a la prueba implicada para cada registro de la tabla.
La siguiente demostración utiliza una colección en formato CSV (Comma-Separated Values).
En primer lugar, hay que añadir el archivo CSV al proyecto de pruebas.
Algunos...
Automatización de las pruebas en la compilación
La calidad de un módulo de software empieza con una compilación sin errores ni advertencias (warning), y continúa con la superación de las pruebas de regresión.
Para automatizar esto en el puesto de desarrollo se pueden utilizar las propiedades Eventos de compilación del proyecto de pruebas.
La línea de comando del evento post-build es:
"$(DevEnvDir)\mstest.exe"
/testcontainer:$(SolutionDir)TestClassProject1\bin\$
(Configuration)\$(TargetFileName)
/noresults
/detail:errormessasge
Utiliza la herramienta Microsoft mstest.exe.
Los fragmentos de código anteriores forman parte de la solución ClassLibrary disponible para su descarga.
Automatización de las pruebas fuera de Visual Studio
Microsoft ofrece un software, Tests Agent, que permite ejecutar las pruebas en una máquina sin tener Visual Studio instalado. La ejecución se realiza desde una consola. Por supuesto, los ensamblados a comprobar se deben compilar, y también los ensamblados de pruebas.
Se prepara un archivo de consola (.BAT) que contiene las instrucciones de las pruebas a ejecutar.
: Definiciones de variables DOS
SET ProgFiles86Root=%ProgramFiles(x86)%
IF NOT "%ProgFiles86Root%"=="" GOTO amd64
SET ProgFiles86Root=%ProgramFiles%
:amd64
set MSDIR=%ProgFiles86Root%\Microsoft Visual Studio
14.0\Common7\IDE\CommonExtensions\Microsoft\TestWindow
set TESTFOLDER=C:\Usuarios\Angel\Documentos\MisPruebas
::::::::::::::::::::::::::::::::::::::::::
: Bucle de ejecución de las pruebas seleccionadas
for %%s in (
PruebaMetodo1
PruebaMetodo3
) do (
"%MSDIR%\vstest.console.exe" %TESTFOLDER%\TestClassProject1.dll /Tests:%%s /Logger:trx
)
pausa
La ejecución de las pruebas genera archivos de resultados (TRX) que se escriben en el directorio TestResults. Estos archivos se pueden copiar en el puesto de desarrollo...
CodedUI
Visual Studio 2015 Entreprise integra CodedUI, una herramienta de pruebas con muy buen rendimiento que permite manipular las interfaces de usuario. El desarrollador puede registrar los casos de uso de las especificaciones y reproducirlos como una serie de pruebas gestionadas de manera análoga a las pruebas unitarias.
CodedUI genera objetos que encapsulan los componentes gráficos de la aplicación a probar. Las clases del CodedUI son diferentes de las clases gráficas utilizadas por la aplicación.
El procedimiento de inicio es muy sencillo:
-
Agregar a la solución un proyecto de tipo CodedUI.
-
Situarse en un método [TestMethod].
-
Iniciar el registro desde el menú contextual.
Visual Studio se minimiza y se abre un asistente de aprendizaje, en la parte inferior izquierda de la pantalla.
Pulse el botón Registrar. Todas las operaciones del ratón/teclado que se produzcan a continuación se registrarán hasta que pulse el botón Fin de registro.
CodedUI habrá creado tantos objetos como sea necesario para reproducir el escenario. El desarrollador se podrá apropiar de este código y completarlo para reforzarlo con Assert, para comprobar que una acción concreta ha provocado la visualización de una información determinada, etc.
Ejercicio
1. Enunciado
Debe escribir
-
Una clase ClaseCadena que contenga un método DevuelveIniciales, que permita devolver las iniciales del nombre y apellido que se pasen como argumento en forma de cadena como se muestra a continuación:
string iniciales = ClaseCadena.DevuelveIniciales("Ángel Sánchez");
// iniciales debe contenir "Á.S."
Si el método recibe un argumento incorrecto, debe devolver una cadena vacía.
-
Una serie de pruebas unitarias que permitan comprobar que todos los casos de uso del método no provocan funcionamientos incorrectos.
2. Corrección
La corrección de este ejercicio está en la solución EjercicioDePrueba, disponible para su descarga.
Los casos de error son los siguientes:
-
El argumento de tipo string es, por naturaleza, nullable. El valor null, que se pasa como argumento, no debe provocar funcionamientos incorrectos.
-
Se debe tener en cuenta el caso de una cadena vacía.
-
También se debe comprobar el caso de una cadena que solo contenga una palabra.
using Microsoft.VisualStudio.TestTools.TestClassing;
using StringClassLibrary;
namespace TestClassProject
{
[TestClass]
public class TestClass1
{
/// <summary>
/// Uso nominal ...