viernes, 14 de febrero de 2014

Introducción a NUnit (II): Atributos básicos

Si en el anterior post vimos una introducción a NUnit y su !tooling”, en este post repasaremos cuales son algunos de los atributos básicos que debes conocer para trabajar con NUnit.

Como ya habrás observado, NUnit se basa principalmente en atributos. Hay gente a las que no les gusta los atributos pero, en el caso de NUnit, se usan para evitar caer en un complejo y enrevesado sistema de herencia simplemente para describir y ejecutar nuestros tests. Además esta la imposición del lenguaje por la que no se soporta la herencia múltiple. En definitiva, NUnit está basado en atributos y, personalmente, no me parece una mala idea. De todas formas frameworks hay muchos y, por ejemplo, un framework basado en convenciones y que parece ahora estar muy de moda es fixie (chivatazo vía @gulnor).

En principio, los dos atributos clave sin los cuales no podrías vivir son TestFixture y Test.

TestFixture decora una clase e indica que será un contenedor de métodos de test. En realidad no es un atributo obligado, sólo debemos indicarlo si queremos crear un TestFixture parametrizado o un TestFixture genérico. Aunque todo esto lo veremos en un siguiente post…, un ejemplo de TestFixture parametrizado sería como sigue:

[TestFixture(1, 2, 3)]
[TestFixture(2, 2, 4)]
class CalculadoraText
{
    private readonly int _num1;
    private readonly int _num2;
    private readonly int _expected;
 
    public CalculadoraText(int num1, int num2, int expected)
    {
        _num1 = num1;
        _num2 = num2;
        _expected = expected;
    }
 
    [Test]
    public void Sumar()
    {
        var calculadora = new Calculadora();
        var resultado = calculadora.Sumar(_num1, _num2);
        Assert.AreEqual(_expected, resultado);
    }
}

Como era de esperar, ahora el método de test Sumar se ejecutará 2 veces porque se instanciará la clase CalculadoraTest 2 veces, cada vez con un conjunto distinto de parámetros.

image 

En cualquier caso, por ahora no utilizaremos el atributo TestFixture.

El siguiente atributo must-know es Test. Con Test indicamos que un método es una prueba. En principio los métodos de test no tiene que tener ningún parámetro ni tampoco devolver ningún valor. Más adelante veremos que, al igual que con TestFixture, existe el concepto de tests parametrizados y entonces esta regla de la firma de los métodos ya no será cierta.

Aunque tanto en el atributo Test como en el atributo TestFixture se puede asignar un valor al parámetro Description, sólo se percatará del mismo los test runners de NUnit (nunit.exe y nunit-console.exe). Además, tampoco es una información muy visible (sólo aparece en las propiedades del test o en el fichero de resultados XML). Otra opción es usar el atributo Description (aunque de nuevo sólo sigue apareciendo en los test runners de NUnit).

[Test][Description("Sumando 2 números enteros")]
public void Sumar()

{
    var calculadora = new Calculadora();
    var resultado = calculadora.Sumar(1, 2);
    Assert.AreEqual(3, resultado);
}

image

Algo bastante útil cuando empezamos a tener un gran número de tests es agruparlos. Con el atributo Category podemos agrupar tanto TestFixtures como Tests. No hay ningún problema en que un test pertenezca a varias categorías. De hecho, en el caso de un Test podría pasar que perteneciera a 2 distintas categorías si su TestFixture tiene establecido una categoría.

[Category("Pruebas de calculadora")]
class CalculadoraText
{
    [Test]
    [Category("Pruebas de suma")]
    public void Sumar()
    {
        var calculadora = new Calculadora();
        var resultado = calculadora.Sumar(1, 2);
        Assert.AreEqual(3, resultado);
    }
}

image

La gran ventaja de agrupar tests en categorías es que podemos seleccionar cuales queremos ejecutar y cuales no. Por ejemplo, nunit-exe tiene toda una pestaña dedicada a la gestión de las categorías, desde donde podemos seleccionar que categorías queremos ejecutar o que categorías no queremos ejecutar.


image

Si utilizamos nunit-console.exe, podemos realizar la misma operación de inclusión o exclusión de categorías con los parámetros /include y /exclude, e incluso en ReSharper y Test Explorer también podemos agrupar los test por categoría.

Si no te gusta harcodear el nombre de la categoría, también se puede crear un nuevo atributo que herede de CategoryAttribute y agrupar así los tests de forma programática.

[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class Critico : CategoryAttribute { }
 
class CalculadoraText
{
    [Test]
    [Critico]
    public void Sumar()
    {
        var calculadora = new Calculadora();
        var resultado = calculadora.Sumar(1, 2);
        Assert.AreEqual(3, resultado);
    }
}

Como último apunte sobre categorías, si estás trabajando con ReSharper podrías forzar que ciertas categorías sólo se ejecuten si se seleccionan explícitamente:


image 

También es importante reseñar como en las propias opciones de ReSharper hay un nodo dedicado en exclusivo a su integración con NUnit.

Otros atributos relacionados con la agrupación/ejecución de tests y que son muy utilizados son Ignore y Explicit.

Cuando decoramos un TestFixture o un Test con Explicit, estamos queriendo decir que el test sólo se ejecutará si explícitamente si le decimos que lo haga seleccionándolo directamente desde alguno de los test runners. En la práctica, se marca un test como Explicit bien cuando el test es de larga duración, bien cuando no debería ejecutarse siempre o bien cuando requiere de servicios externos, en definitiva, cuando queramos controlar cuando se ejecuta el test.

En teoría un test Explicit no debería contar en en el número total de tests, simplemente no se ejecuta y se reporta como skipped (distinto de ignorado). En la práctica sólo los test runners de NUnit incorporan este concepto y, por ejemplo, en Reshaper es etiquetado como Ignored.

Con Ignored estamos ignorando deliberadamente un test y al igual que pasa con Explicit, no se ejecutará. Normalmente se marca un test como ignorado cuando no queremos olvidarlo pero por contra no podemos implementarlo (sería un TODO test) o tenemos que fixearlo porque algo está roto. Es decir, seguirá apareciendo en los resúmenes, en el conteo total, seguirá compilándose como parte del proyecto… pero no nos olvidaremos de él porque siempre es reportado. Es una alternativa digna a comentar el código de un test, que podría acabar en un test olvidado y del que nadie sabe nada al respecto.

Un test ignorado es tratado de forma especial en los test runners de NUnit y aparece bajo la pestaña “Test Not Run”.

Ambos atributos (Explicit e Ignore) permite especificar un texto como motivo en el parámetro reason (que de uno u otro modo aparece en todos los test runners).

Otro atributo clave y muy utilizado es ExpectedException. Con este atributo indicamos que el test fallará si no se produce una excepción del tipo indicado durante la ejecución del test.

Por último, otros atributos interesantes podían ser Timeout y MaxTime (lo cierto es que todo lo relacionado con timeouts me intriga y fascina…¡qué se le va a hacer!).

Timeout indica el tiempo máximo en el que el test debe ejecutarse, si el test (o TextFixture) lo sobrepasa se cancelará su ejecución y será reportado como fallo (lógicamente si estamos depurando no se tiene en cuenta este valor). Si Timeout es indicado a nivel de TextFixture, podría pasar que incluso algunos tests no llegarán a ejecutarse porque se canceló el TextFixture.

Por otro lado MaxTime también reporta el test como no superado si sobrepasa el tiempo indicado pero, a diferencia de Timeout, no cancela la ejecución del test. Además, cualquier aserción prevalecerá sobre MaxTime. Por ejemplo, el siguiente test fallará por la aserción y no por MaxTime:

[Test]
[MaxTime(2000)]
public void Sumar()
{
    System.Threading.Thread.Sleep(3000);
    var calculadora = new Calculadora();
    var resultado = calculadora.Sumar(1, 2);
    Assert.AreEqual(5, resultado);
}
Mas posts de esta serie:
Un saludo!

No hay comentarios:

Publicar un comentario