Este post y siguientes tienen como finalidad hacer una introducción a xUnit y el desarrollo guiado por pruebas (TDD o del inglés Test Driven Development). El desarrollo guiado por pruebas es una práctica que forma parte de las metodologías ágiles para el desarrollo de software y que consiste en el uso de pruebas unitarias para escribir el código de una forma más limpia y con menos errores.

Lo que se pretende, entre otras cosas, es evitar el escribir código innecesario “por si en un futuro lo usamos” y con menos fallos. Muchas veces empezamos a escribir código e intentamos adaptarlo para posibles cambios que pueden darse lugar en un futuro pero que luego y de forma general no suelen llegar, con lo que trabajamos en algo que no nos va a servir de mucho. La idea es que por cada requerimiento que tengamos escribamos una prueba que inicialmente fallará. Si la prueba no falla puede ser porque la funcionalidad ya estaba implementada o que la prueba es errónea. Cada prueba unitaria se escribe con el código mínimo necesario y posteriormente se puede realizar una refactorización. De esta forma nos aseguramos que al hacer cambio en el código tenemos la forma o herramienta de probar que esos cambios son válidos y no se han introducido nuevos errores.

El ciclo del desarrollo guiado por pruebas consiste en primer lugar en elegir un requerimiento para el cual vamos a escribir una prueba, después comprobamos que falla (como ya hemos comentado antes) y entonces implementamos el código lo más simple posible para que funcione. Verificamos que todas nuestras pruebas unitarias son válidas y entonces hacemos una refactorización del código para eliminar partes duplicadas. Por último actualizamos la lista de requerimientos, añadiendo requisitos nuevos si han surgido y/o eliminando los ya implementados, y realizamos un nuevo ciclo. Esto es una introducción muy general al TDD, solo para que uno se haga una idea y podéis buscar más información a través de nuestro amigo google.

Ahora vayamos al lío, de entre todos los frameworks existentes para hacer test unitarios he decido usar xUnit, basado en nUnit (aunque tienen algunas diferencias claras) y cuyo autor es el mismo. Para poder usar xUnit (descargar desde aquí) tenemos varias opciones, por un lado tenemos xunit.console y xunit.gui (que vienen con xUnit) y por otro tenemos la posibilidad de integrar xUnit con Visual Studio por medio de TestDriven (que cuenta con edición personal gratuita) o con ReSapher (evaluación de 30 días). Yo voy a usar TestDriven y para ello, una vez instalado TestDriven, hay que activar el soporte de xUnit para TestDriven a través de xunit.installer.

xUnit.installer

En este momento es cuando podemos crear un nuevo proyecto en Visual Studio con el que vamos a realizar pruebas unitarias. Imaginemos que vamos a hacer una calculadora y vamos a tener una librería que de soporte para realizar operaciones con una calculadora. Tenemos una clase muy sencilla llamada Calc:

public class Calc

{

    public Calc()

    { }

 

    public int SumaNumeros(int a, int b)

    {

        throw new ArgumentException("No implementado");

    }

 

    public double DivideNumeros(double a, double b)

    {

        throw new ArgumentException("No implementado");

    }

}

Después creamos otro proyecto de librería de clases que es el que va a incluir todas nuestras pruebas unitarias y agregamos la referencia a la dll de xUnit y la de la librería sobre la que vamos a realizar las pruebas:

referencias

En la imagen podemos ver las referencias agregadas tanto al proyecto como a la clase que contiene las pruebas unitarias. También podemos ver que existe un método llamado ComprobarSuma que es el encargado de comprobar la operación suma de la calculadora. Para que xUnit identifique este método como una prueba unitaria hay que marcarlo con el atributo [Fact], de lo contrario no se ejecutará ningún test. Queda resaltar la sentencia Assert que establece la condición para determinar si la prueba unitaria es satisfactoria o no. Para ejecutar xUnit pinchamos con el botón derecho del ratón sobre el proyecto que contiene las pruebas unitarias y le damos a Run Test(s).

runtest

Si hacemos correr el test, el resultado obtenido es el siguiente (hay que recordar que como no estaba implementado el método de sumar números hacíamos que lanzara una excepción):

------ Test started: Assembly: Tests.dll ------

 

TestCase 'Tests.Test.ComprobarSuma' failed: System.ArgumentException : No implementado

       E:\Documentos\Visual Studio 2008\Projects\DemoTDD\DemoTDD\Calc.cs(14,0): en DemoTDD.Calc.SumaNumeros(Int32 a, Int32 b)

       E:\Documentos\Visual Studio 2008\Projects\DemoTDD\Tests\Test.cs(17,0): en Tests.Test.ComprobarSuma()

 

0 passed, 1 failed, 0 skipped, took 0,72 seconds.

Una vez visto que la prueba falla, es cuando hay que implementar el método que estamos probando. Para ver otro ejemplo, vamos a hacer que el método SumaNumeros nos devuelva la suma de los dos argumentos más uno. En el resultado al ejecutar el test nos dice que el test ha fallado y nos indica el valor esperado y el valor obtenido:

------ Test started: Assembly: Tests.dll ------

 

TestCase 'Tests.Test.ComprobarSuma' failed: Assert.Equal() Failure

Expected: 7

Actual:   8

       en Xunit.Assert.Equal[T](T expected, T actual, IComparer`1 comparer)

       en Xunit.Assert.Equal[T](T expected, T actual)

       E:\Documentos\Visual Studio 2008\Projects\DemoTDD\Tests\Test.cs(19,0): en Tests.Test.ComprobarSuma()

 

 

0 passed, 1 failed, 0 skipped, took 0,76 seconds. 

También es interesante saber que podemos añadir parámetros al atributo [Fact] para contemplar dos casos, cuando una prueba tarda demasiado tiempo y cuando por el motivo que sea no queremos que una determinada prueba se ejecute en un momento concreto.

Si queremos saltarnos una prueba tenemos que añadir el parámetro Skip que toma un valor de tipo string:

[Fact(Skip = "Sin implementar")]

public void SkipTest()

{

    //implementar despues

} 

La salida obtenida es:

0 passed, 0 failed, 1 skipped, took 0,72 seconds

Si queremos que test falle cuando exceda de un determinado tiempo tenemos que añadir el parámetro Timeout que toma un valor entero en milisegundos:

[Fact(Timeout = 2000)]

public void TimeoutTest()

{

    Thread.Sleep(6000);

} 

La salida obtenida es:

TestCase 'Tests.Test.TimeoutTest' failed: Test execution time exceeded: 2000ms

Y por último dejo una lista con todas las aserciones que contempla xUnit para comprobar el resultado de una prueba unitaria y que me gustaría comentar en el siguiente post:

  • Equal<T>
  • NotEqual<T>
  • NotSame
  • Same
  • Contains, Contains<T>
  • DoesNotContain, DoesNotContain<T>
  • DoesNotThrow
  • Throws, Throws<T>
  • InRange<T>
  • NotInRange<T>
  • IsAsignableFrom, IsAsignableFrom<T>
  • Empty
  • False
  • IsNotType, IsNotType<T>
  • IsType, IsType<T>
  • NotEmpty
  • NotNull
  • Null
  • True

NOTA: la versión que he utilizado de xUnit es la 1.0.3 que parece presentar un bug (que ya ha sido reportado) en el que la salida por consola no aparece, por eso si añadís algún Console.WriteLine no esperéis a que os aparezca por consola.

NOTA 2: teneis adjunto el ejemplo de código para VS2008