Inicio > Delphi, TDD, Test, Unit test > Test Unitarios; Introducción (Entrega 1)

Test Unitarios; Introducción (Entrega 1)

martes, 24 de octubre de 2017 Dejar un comentario Ir a comentarios

Este artículo forma parte de una serie que cuenta con varios otros alrededor del mismo tema. A continuación os adjunto los links de todas ellas (que iré completando a medida que salgan):

¿Qué es un test unitario?

UnitTesting1El objetivo más a corto plazo de un test unitario (o prueba unitaria) es el de probar una porción de nuestro código, de forma que podamos comprobar que funciona correctamente y que se ejecuta con el comportamiento para el que fue diseñado. Por lo tanto nos ayuda a encontrar fallos en nuestro código.
Podríamos decir que un objetivo más amplio de los test unitarios es, en general, mejorar la calidad del software que desarrollamos. Por un mejor diseño y por un menor número de errores.

Hay quien dice que los test unitarios están pensados para equipos de trabajo donde hay roles definidos. Personalmente considero que hoy en día los test unitarios para un programador, son como el «control de versiones» o los «repositorios de código». Para mi NO SON EXCLUSIVOS de equipos de trabajo, sino que un programador a nivel personal (individual) DEBE trabajar con ellos, aunque no esté integrado en un equipo.

TDD (Test Driven Development)

Los test unitarios se enmarcan dentro de lo que se conoce como TDD (desarrollo guiado por pruebas) que nos ayuda a no crear test y probar código «a lo loco».

La idea sería seguir los siguientes pasos:

  • PASO 1: Definimos/diseñamos una prueba para validar un código que cumple determinados requisitos.
  • PASO 2: Desarrollamos el código para ejecutar la tarea que se ha especificado.
  • PASO 3: Ejecutamos la prueba. Si falla corregimos el código y repetimos, si funciona correctamente, refactorizamos y limpiamos el código (si es necesario) y damos este «step» por concluido, pasando al siguiente.

Ventajas de realizar Test unitarios

Tal vez podamos empezar por las más básicas, pero también más sencillas.

  1. Un test nos sirve como «documentación de código».
    Dado que un test nos muestra los posibles resultados que una determinada función nos debe dar a partir de unas entradas, a su vez nos sirve como explicación y documentación del funcionamiento de esa procedimiento.
  2. Nos obliga a separar Interfaz e Implementación.
    Es algo imprescindible en una programación avanzada y los test nos obligan en cierta forma a separar ambas. Dado que los test se ejecutan sin interacción del usuario, necesitamos que la interfaz esté obligatoriamente excluida de las funciones que probamos en los test. Indirectamente esto hace que el código sea más mantenible.
    Además indirectamente facilita que el código esté focalizado en una tarea. En un desarrollo a veces se tiende a tener código «que mezcla» diferentes funcionalidades, interfaz, acceso a datos, utilidades,…
    Al realizar las pruebas tenemos más fácil separar las funcionalidades que necesitamos testear.
  3. Facilita cambios de código.
    Los test nos dan más seguridad sobre que los cambios realizados no introducen nuevos errores en el código existente, por lo tanto nos aporta más confianza a la hora de realizarlos. Los test nos añaden como una «red de seguridad» a la hora de desarrollar.
  4. Mejora la detección de errores.
    Por supuesto es una de las razones más importantes por las que realizamos los test. La existencia de los test nos facilita la detección de errores si se producen, tanto al crear como al actualizar nuestros desarrollos.
  5. Aceleran el desarrollo.
    Aunque a priori pueda parecer que tenemos que escribir más código para realizar la misma tarea y por lo tanto vamos a tardar más en realizarla, ese tiempo que invertimos inicialmente a la larga se demuestra que se recupera pues se minimizan las pruebas, la revisión de código, la búsqueda de errores,…

¿Cómo debe ser un buen test unitario?

Las características que pueden definir de forma esquemática un buen test unitario, serían las siguientes:

  • TestingSe deben poder ejecutar de forma automatizada.
  • Podemos ejecutarlos tantas veces como sea necesario.
  • Debe testear lo correcto.
  • Deben ser sencillos y rápidos.
  • Deben evitar dependencias. Se deben poder ejecutar todos o parte de ellos y sin orden determinado.
  • Deben ser desarrollados antes de programar.
  • A todos los efectos son código de la aplicación, no algo externo.

«LA CLASE» (TAritmeticaBasica)

Para poder realizar pruebas sobre un determinado código, lo primero que vamos a necesitar es un código que testear. Para esto vamos a crear la clase «TAritmeticaBasica«. Es decir, nuestro escenario es el siguiente:

«Debemos crear una clase que llamaremos TAritmeticaBasica, que como su nombre indica, necesitamos que realice determinadas operaciones aritméticas y hemos decidido que esta clase incluirá Tests Unitarios.»

Los pasos que vamos a seguir son los siguientes:

  1. Definir la estructura de nuestra clase. Qué va a tener, qué necesitamos probar,…
  2. Definir una batería de Tests Unitarios que nos permitan probar las características definidas en el punto 1.
  3. Implementar la Clase TAritmeticaBasica.
  4. Ejecutar los test y a partir de los resultados de cada test:
    1. (Correcto) Refactorizar código si es necesario.
    2. (Error) Corregir la clase y volver a ejecutar el punto 4.

ESTRUCTURA DE LA CLASE

Nuestra clase TAritmeticaBasica tendrá la siguiente estructura y propiedades

  // Clase para cálculos aritméticos
  TAritmeticaBasica = class
  public
    // Devuelve si el número es primo o no
    function EsNumeroPrimo(const ANumero:int64):boolean;
    // Devuielve el máximo común Divisor de 2 números enteros
    function MaximoComunDivisor(const ANumero1, ANumero2:int64):int64;
    // Devuelve el Minimo comun Multiplo de 2 números enteros
    function MinimoComunMultiplo(const ANumero1, ANumero2:int64):int64;
    // Calcula la factorizacion de un número entero; Resultado en una lista
    procedure Factorizacion(ANumero: int64; ALista:TIntegerList);
  end;
end;

Si seguimos de forma estricta los pasos que hemos realizado antes, debemos implementar los tests antes de implementar la clase. Lo que haremos será crear nuestra unit «vacía» y  a partir de ahí generaremos los test. Para ello a la definición que ya tenemos, le añadiremos lo siguiente en la parte de implementación:

unit UClass.TAritmeticaBasica;
 
interface
 
uses
  System.Generics.Collections;
 
type
  // Type form integerlist
  TIntegerList = TList;
 
  // Clase para cálculos aritméticos
  ...
 
Implemementation
 
function TAritmeticaBasica.EsNumeroPrimo(const ANumero: int64): boolean;
begin
  Result := True;
end;
 
procedure TAritmeticaBasica.Factorizacion(ANumero: int64; ALista:TIntegerList);
begin
  ALista.Clear;
end;
 
function TAritmeticaBasica.MaximoComunDivisor(const ANumero1, ANumero2: int64): int64;
begin
  Result := 0;
end;
 
function TAritmeticaBasica.MinimoComunMultiplo(const ANumero1, ANumero2: int64): int64;
begin
  Result := 0;
end;
end.

Aquí tenéis la unit al completo para descargar tal y como la tenemos ahora (sólo estructura).

<Descargar código fuente de UClass.TAritmeticaBasica.pas>

Os adjunto a continuación algunos links y algo de información que pueden servir de recordatorio sobre las 4 operaciones que posee nuestra clase (me recuerdan a mis clases de matemáticas en el colegio  ;-D  ).

Números primos

https://es.wikipedia.org/wiki/N%C3%BAmero_primo

Número primo es aquel que sólo es divisible por 1 y por si mismo.

Máximo común divisor

http://www.elabueloeduca.com/aprender/matematicas/divisibilidad/maximocomundivisor.html

El MCD de dos números enteros positivos es el mayor divisor común (que comparten ambos).

Ejemplos:

Los divisores comunes de 6, 12 y 18 son ? 1, 2, 3, 6

Como el mayor es 6, el M.C.D. (6 , 12 , 18) = 6

Utilizar factorización para calcular el M.C.D. de varios números.

Factorización de 24 = 2 x 2 x 2 x 3

Factorización de 88 = 2 x 2 x 2 x 11

M.C.D (24 , 88) = 8

Factorización de 15 = 3 x 5

Factorización de 24 = 2 x 2 x 2 x 3

M.C.D (15 , 24) = 3

Mínimo común múltiplo

http://www.elabueloeduca.com/aprender/matematicas/divisibilidad/minimocomunmultiplo.html

https://es.wikipedia.org/wiki/M%C3%ADnimo_com%C3%BAn_m%C3%BAltiplo

El M.C.M. de dos o más números enteros, es el menor múltiplo común que comparten entre ellos, distinto de cero.

Ejemplos:

M.C.M. (6 , 12 , 18) = 36

Los múltiplos de 6 son ? 6, 12, 18, 24, 30, 36, …

Los múltiplos de 12 son ? 12, 24, 36, 48, 60, …

Los múltiplos de 18 son ? 18, 36, 54, 72, 90, …

(el mínimo múltiplo que comparten los tres es el 36).

Otros ejemplos:

M.C.M. (36, 84, 120) = 2.520

M.C.M. (14 , 21) = 42

M.C.M. (5 , 3) = 15

M.C.M. (18 , 26) = 234

M.C.M. (700 , 24) = 4200

Factorización en números primos

http://www.elabueloeduca.com/aprender/matematicas/divisibilidad/factorizacion.html

Se trata de descomponer un número como producto de factores primos.

Ejemplos:

(Debe ser un número mayor o igual a 2)

2310 = 2 x 3 x 5 x 7 x 11

3150 = 2 x 3 x 3 x 5 x 5 x 7

543 = 3 x 181

2 = Primo

7 = primo

1111 = 11 x 101

850 = 2 x 5 x 5 x 17

Hasta aquí esta primera entrada de la serie. He decidido dividirla en varios bloques por la extensión de la misma.

La idea para las siguientes, es diseñar las pruebas unitarias para nuestra unit utilizando los paquetes DUnit y DUnitX. Ver los pasos para crearlos desde cero y ver las diferencias (no muchas) que hay entre ellos.

Un saludo y hasta la próxima.

5/5 - (2 votos)
  1. Fredy Caballero
    viernes, 2 de febrero de 2018 a las 18:19 | #1

    Este es un tema muy importante en el desarrollo de software, agradezco tu esfuerzo en publicar este material de gran utilidad.

    Saludos

  2. lunes, 5 de febrero de 2018 a las 11:44 | #2

    @Fredy Caballero
    Gracias por el comentario Fredy.

  3. miércoles, 28 de marzo de 2018 a las 17:51 | #3

    Hola Germán Estévez, ¡Excelente Día!

    Muchas Gracias por compartir buena información referente a las pruebas unitarias, es un excelente tema para iniciar en esto del mundo QA.

    Saludos Cordiales,
    ISC Óscar O. Bravo M.

  1. Sin trackbacks aún.