Inicio > Delphi, TDD, Test > Test Unitarios; Framework DUnitX (Entrega 4)

Test Unitarios; Framework DUnitX (Entrega 4)

Share Button

Completamos con esta entrada la serie sobre test unitarios, centrándonos en el Framework DUnitX, que es el que actualmente embarcadero recomienda y se incluye en las últimas versiones de Delphi.

INTRODUCCION

Como ya he comentado en las entradas anteriores, DUnitX es una evolución de DUnit (y que además recoge ideas de otros paquetes similares como NUnit). Si no lo tenéis en la versión actual de Delphi con la que estáis trabajando, podéis descargarlo desde el repositorio de Github, y deberíais poder instalarlo sin problemas. DUnitX soporta versiones de Delphi 2010 y posteriores.

A continuación os adjunto una tabla con las diferencias más significativas en cuanto a características que DUnitX nos aporta frente a DUnit. (click sobre ella para verla ampliada).

Tabla_Caracteristicas

(fuente: DUnitX – A Unit Test Framework For Delphi)

Siguiendo con el ejemplo que hemos utilizado en las entradas anteriores, nuestro punto de partida es nuestra clase TAritmeticaBasica, cuya definición es la que se ve a continuación; Cuenta con 4 métodos que son los que vamos a testar:

 // Clase para cálculos aritméticos
 TAritmeticaBasica = class
 strict private
 class var FInstance:TAritmeticaBasica;
 private
 function EsPrimo(x:integer):boolean;
 function MenorFactor(x:integer):integer;
 public
 /// Devuelve si el número es primo
 function EsNumeroPrimo(const ANumero:int64):boolean;
 /// Calcula el máximo común Divisor de 2 números enteros 
 function MaximoComunDivisor(const ANumero1, ANumero2:int64):int64;
 /// Calcula en Minimo comun Multiplo de 2 números enteros 
 function MinimoComunMultiplo(const ANumero1, ANumero2:int64):int64;
 /// Calcula la factorizacion de un número entero 
 procedure Factorizacion(ANumero: int64; ALista:TIntegerList);
 /// Patron singleton para la clase 
 class function GetInstance:TAritmeticaBasica;
 end;

Aquí podéis descargar la unit completa. Una versión con alguna codificación más que las que utilizamos en las otras entradas.

CREACIÓN DEL PROYECTO DE TEST (DUNITX)

CRearTestDUnitXPodéis seguir los pasos en la docwiki de Embarcadero, de todas formas vemos rápidamente lo que hay que hacer. Al crear un nuevo proyecto nos aparece una pantalla con unas opciones básicas.

Debemos rellenar como nombre de la clase TAritmeticaBasica, que es nuestra clase  a testear.

Una vez aceptada esta pantalla, se nos genera un proyecto al que tenemos que añadir nuestra unit y que tendrá una estructura similar a esta (después de asignar un nombre y grabar).

Protecto_DUnitX

Si compilamos y ejecutamos ya podemos ver el resultado de los test. Dado que hemos añadido métodos de ejemplo, podemos ver algunos resultados aunque sean de ejemplo.

En este caso estamos probando un proyecto de consola. Podemos comprobarlo revisando el fichero DPR y viendo que aparece el siguiente calificativo:

  {$APPTYPE CONSOLE}

Result_Test

Si revisamos un poco por encima la unit TestX.UClass.TAritmeticaBasica.pas (que es como yo he llamado a la clase generada para los test) veremos que es similar a la generada por DUnit en las primeras entradas.

A continuación os pongo una imagen comparativa de ambas clases. En la parte izquierda la de DUnitX que acabamos de generar, à la derecha la de DUnit anterior.

Test_Case_Parametros

Tenemos unos métodos de inicialización (que os he marcado con flechas) y que son equivalentes en ambos.

Lo que realmente considero importante es la parte que os he marcado en rojo en la imagen de la izquierda y cuyo comentario reza lo siguiente:

Test with TestCase Attribute to supply parameters.

Esto nos permite “salvar” el mayor inconveniente que nos habíamos encontrado con DUnit en la entrada anterior (Test Unitarios; Framework DUnit (Entrega 2)) que era poder parametrizar o poder definir parámetros para un Test.

El sistema de DUnitX nos hace mucho más fácil este trabajo y por ende, también la tarea de definir los test.

GENERAR CÓDIGO DE LOS TEST

Si tomamos como referencia la primera función de nuestra clase EsNumeroPrimo:

///  Devuelve si el número es primo
function EsNumeroPrimo(const ANumero:int64):boolean;

Y recordamos cómo eran los test generados con DUnit, teníamos lo siguiente:

procedure TestEsNumeroPrimo_0; 
procedure TestEsNumeroPrimo_1; 
procedure TestEsNumeroPrimo_2; 
procedure TestEsNumeroPrimo_3; 
procedure TestEsNumeroPrimo_4; 
procedure TestEsNumeroPrimo_20; 
procedure TestEsNumeroPrimo_11;

Realmente con DUnit teníamos que hacerlo de esta manera (para hacer test independientes) u optar por la parametrización (programada por nosotros mediante clases). Con DUnitX ya se incluye un sistema de parametrización, de forma que nuestros test para esta clase estarán definidos de la siguiente forma:

[Test]
[TestCase('Test_0', '0,False')]  
[TestCase('Test_1', '1,True')]    
[TestCase('Test_2', '2,True')]
[TestCase('Test_3', '3,True')]    
[TestCase('Test_4', '4,False')]
[TestCase('Test_20','20,False')]
[TestCase('Test_11','11,True')]
[TestCase('Test_18','18,False')]
[TestCase('Test_17','17,True')]
procedure TestEsNumeroPrimo(Value1: Int64; Result: boolean);

Vemos que la diferencia es considerable.

En este caso tenemos un sólo test que podemos parametrizar y utilizar los atributos definidos con [TestCase] para asignar diferentes valores al test, y tener de esta forma virtualmente N test con una única definición (cosa más lógica que lo que habíamos visto).

La implementación del procedimiento es la siguiente:

///  Testea si el número dado es primo y lo compara
///  con el resultado dado
procedure TestXTAritmeticaBasica.TestEsNumeroPrimo(Value1: Int64; Result: integer); 
var 
  ResultValue:boolean; 
begin 
  ResultValue := FAritmeticaBasica.EsNumeroPrimo(Value1); 
  Assert.AreEqual(ResultValue, Result,
    'El número [' + IntToStr(Value1) + '] no ha dado el resultado esperado.'); 
end;

Utilizo el método Assert.AreEqual para comprobar que el valor esperado es igual al que devuelve la función. Además podemos añadir como parámetro final un mensaje personalizado para el error.

Si cambiamos el resultado del Test_4 para que nos devuelva un error, en caso de no añadir mensaje obtendremos algo como esto:

 Test_error_1

Y en caso de añadir algún mensaje, podremos dar información añadida y que nos pueda resultar interesante a la hora de revisar los resultados.

test_error_2

En este caso he utilizado Assert.AreEqual, pero disponéis de muchas más funciones para utilizar en los test. En la DocWiki de embarcadero tenéis una lista detallada con las distintas funciones y una pequeña descripción de cada una de ellas, aunque los nombres ya son bastante descriptivos. En la imagen inferior podéis verlas:

Lista_funciones

Como podéis ver y tal como he comentado, los nombres son bastante explicativos de lo que hace cada una de ellas. Si queréis probar podéis modificar los ejemplos y el código propuesto para utilizar otras funciones y ver qué resultado obtenéis.

MODIFICADORES DE LOS TEST

Podemos llamarlos “Modificadores”, “Configuradores” o incluso “Decoradores” de los test. Me estoy refiriendo a las palabras entre corchetes que hemos colocado antes del procedimiento propio del test. Al principio de la entrada los hemos visto para definir los parámetros de la siguiente forma:

[TestCase(‘Test_2’, ‘2,True’)]

[TestCase(‘Test_3’, ‘3,True’)]

También en el código de ejemplo los hemos visto acompañando a los métodos de Setup y TearDown, que nos permiten realizar inicializaciones y finalizaciones en la clase del test.

OTROS DECORADORES

Estos no son todos los que hay y en este final de la entrada vamos a repasar algunos de los que nos quedan disponibles y que podemos utilizar al definir los procedimientos de prueba.

[Ignore]

Nos permite ignorar (tal y como su nombre indica) la ejecución de un determinado test. por ejemplo, porque temporalmente no nos interesa o porque nos ha quedado obsoleto. Podemos añadir además un texto explicativo. A diferencia del que veremos a continuación, un test que ignoramos, nos parece como tal en los resultados.

[Test]
[Ignore('Este es un test pensado para ignorarlo')]
procedure IgnorarEsteText;

El resultado que veríamos al usar el “ignored” será el siguiente:

[Test(False)]

Nos permite deshabilitar un determinado test, de forma que se ignora a la hora de ejecutar el proyecto de pruebas.

[RepeatTest(10)]

Si añadimos este modificador a un test, nos permitira ejecutarlo un número repetido de veces. Podemos ver un ejemplo simple a continuacion.

Podemos definir un procedimiento de test como el que se ve a continuacion>

procedure TestXTAritmeticaBasica.Test;
begin
  //El valor se inicializa a 0
  Inc(FContador);
  Assert.AreNotEqual(FContador, 1, 'Contador no debe ser igual a 1');
end;

Si ejecutamos el test una sola vez nos deber’ia fallar, pues el valor del contador valdr’a 1 y estamos utilizando la sentencia AreNotEqual.

[MaxTime(ms)]

Este atributo permite que el test se interrumpa (y devuelve un resultado de timeout)  si el tiempo de ejecuciónnexcede del parámetro que se le pasa al “decorador” en milsegundos.

Un procedimiento como este (que tardará 5 segundos en ejecutarse):

procedure TestXTAritmeticaBasica.MetodoTarda5sg;
var
  i:Integer;
begin
  for i := 0 to 4 do
    sleep(1000);
  Assert.Pass('Finalizado; 5 segundos.');
end;

Si se define en su cabecera de la siguiente manera:

[Test]
[MaxTime(2000)]
procedure MetodoTarda5sg;

Al cabo de 2 segundos finalizará y veremos un resultado como el siguiente:

Con esto concluyo esta entrada que debía ser la útima (aunque no será así). Me ha quedado una cosa en el tintero y prefiero dejarlo para la siguiente. Así que generaré un “bonus track”.

Os dejo algunos links interesantes, donde podéis encontrar más información:

Y aquí tenéis el link al proyecto completo.

<Descargar código fuente del proyecto de ejemplo con DUnitX>

Como siempre cualquier comentario, duda, crítica,… será bienvenido.

Hasta la próxima.

Share Button
Categories: Delphi, TDD, Test Tags: , , ,
  1. Sin comentarios aún.
  1. Sin trackbacks aún.
What is 11 + 8 ?
Please leave these two fields as-is:
IMPORTANTE! Para continuar, debes contestar la pregunta anterior (para evitar SPAM) :-)