viernes, 21 de febrero de 2014

Introducción a NUnit (III): Ciclo de vida de un test

En este tercer post de la serie veremos qué atributos hay disponibles en NUnit para configurar la inicialización y destrucción de datos requeridos por los tests.

 

Antes de comenzar sólo un par de tips para saber dónde y cómo capturar cierta salida de tracing de un test para poder seguir la pista al orden en que los atributos son aplicados.

 

Si por ejemplo escribimos el siguiente test:

 

[Test]

public void Prueba()

{

    System.Diagnostics.Debug.WriteLine("panicoenlaxbox");

    Assert.Pass();

}

¿Dónde aparecerá la información de tracing?

 

En ReSharper directamente en la salida:

 

clip_image001

 

En Test Explorer, pulsando en el enlace “Salida”

 

clip_image002

 

En NUnit.exe en la pestaña “Text Output”, siempre y cuando hayamos activado la opción “Gui\Text Output\Trace Output” (por defecto desactivado).

 

clip_image003

 

En NUnit 3.0 deberemos usar TestContext.Write/WriteLine https://github.com/nunit/docs/wiki/TestContext#write, mientras que en xUnit.net hay que tirar algo de código para usar ITestOutputHelper https://xunit.github.io/docs/capturing-output.html.

 

Una vez que tenemos localizado donde aparecerá la información de tracing, es momento de ponerse manos a la obra y llenar nuestro código de información de traza al más viejo y rudimentario estilo ASP clásico con Response.Write ¿Te acuerdas?... Ya sé que no es muy digno, pero es la opción más sencilla :-)

 

Los atributos que permiten gestionar la inicialización y destrucción de datos en los test son:

  • SetUp
  • TearDown
  • TestFixtureSetUp
  • TestFixtureTearDown

SetUp marca un método para que se ejecute antes de cada test.

 

TearDown marca un método para que se ejecute después de cada test.

 

TestFixtureSetUp marca un método para que se ejecute una sola vez antes de que se ejecute cualquier test en un fixture.

 

TestFixtureTearDown marca un método para que ejecute una sola vez cuando hayan finalizado todos los tests de un fixture.

 

Por ejemplo, para el siguiente código:

 

    class CalculadoraText

    {

        [SetUp]

        public void InicializarTest()

        {

            System.Diagnostics.Trace.WriteLine("SetUp");

        }

 

        [TearDown]

        public void LiberarTest()

        {

            System.Diagnostics.Trace.WriteLine("TearDown");

        }

 

        [TestFixtureSetUp]

        public void InicializarFixture()

        {

            System.Diagnostics.Trace.WriteLine("TestFixtureSetUp");

        }

 

        [TestFixtureTearDown]

        public void LiberarFixture()

        {

            System.Diagnostics.Trace.WriteLine("TestFixtureTearDown");

        }

 

        [Test]

        public void Test1()

        {

            System.Diagnostics.Trace.WriteLine("Test1");

            Assert.Pass();

        }

 

        [Test]

        public void Test2()

        {

            System.Diagnostics.Trace.WriteLine("Test2");

            Assert.Pass();

        }

    }

Esta es la salida:

 

clip_image004

 

Otro atributo interesante es SetUpFixture. Este atributo decora una clase cuyos métodos decorados con SetUp y TearDown serán ejecutados una sola vez dentro del espacio de nombres. Lógicamente, en esta clase no tiene sentido (tampoco serán reconocidos) agregar ningún método de test. Este atributo podría servir para organizar fixtures relacionados con tareas de inicialización compartidas bajo un mismo espacio de nombres

 

[SetUpFixture]

class PreparacionTests

{

    [SetUp]

    public void Inicializar()

    {

        System.Diagnostics.Trace.WriteLine("SetUpFixture SetUp");

    }

 

    [TearDown]

    public void Liberar()

    {

        System.Diagnostics.Trace.WriteLine("SetUpFixture TearDown");

    }

}

clip_image005

 

Si hay varios métodos decorados con estos atributos se ejecutarán todos ellos (por ejemplo podríamos llegar a esa situación si heredemos de una clase base que ya incorpora métodos decorados).

 

En NUnit 3.0 este ciclo de vida ha cambiado https://github.com/nunit/docs/wiki/SetUp-and-TearDown-Changes.

 

Si lo comparamos con xUnit.net, en este último el ciclo de vida es más sencillo. Para empezar se crea una instancia de clase de tests por cada test ejecutado, siendo así, con el constructor y el método Dispose (si es necesario teardown) nos bastaría. Para compartir estado entre tests de la misma clase usaríamos Class Fixtures - IClassFixture<> y para compartir estado entre distintas clases usaríamos Collection Fixtures - ICollectionFixture<> https://xunit.github.io/docs/shared-context.html.

 

Llegados a este punto, podrías pensar que si cada método de test requiere su propia configuración de inicialización y destrucción (no compartida con el resto de tests y asumiendo que no quieres escribir el código de estas tareas en el propio test) las soluciones aquí expuestas no parecen la mejor opción. Claro está que podrías hacer una clase por test, pero tampoco parece una opción viable. Es por ello que NUnit nos brinda otra vía distinta de personalización (de nuevo basada en atributos) para configurar de forma exclusiva la configuración que un test requiere. La idea está en sacar fuera del test la inicialización y destrucción, creando un nuevo atributo que herede de TestActionAttribute y sobrescriba los métodos BeforeTest y AfterTest, es lo que NUnit llama atributos de acción.

 

class SetupParaTest1Attribute : TestActionAttribute

{

    public override void BeforeTest(TestDetails testDetails)

    {

        System.Diagnostics.Trace.WriteLine("BeforeTest");

    }

 

    public override void AfterTest(TestDetails testDetails)

    {

        System.Diagnostics.Trace.WriteLine("AfterTest");

    }

}

 

class CalculadoraText

{

    [Test]

    [SetupParaTest1]

    public void Test1()

    {

        System.Diagnostics.Trace.WriteLine("Test1");

        Assert.Pass();

    }

}

clip_image006

 

Otra propiedad interesante de TestActionAttribute y que se puede sobrescribir es ActionTargets. Con esta propiedad indicamos cómo se comportará el atributo de acción en función de si decora un método de test o un fixture.

 

Los posibles valores son:

  • Default (por defecto).
  • Test.
  • Suite.

Con Default, si se pone en un test se ejecutará antes y después del test, si se pone en un fixture se ejecutará una sola vez para todos los test del fixture.

 

Con Test depende de a quien decore. Si se pone a un método de test se ejecutará antes y después del test, pero si se pone a un fixture se ejecutar una vez para cada uno de los test del fixture.

 

Con Suite sólo se aplicará a nivel de fixture (una sola vez para todos los tests) y si decora a un test, no fallara pero no hará nada.

 

Lógicamente, algunos pensarán que montar todo este tinglado para el setup de un test es mucha parafernalia, ¿Por qué no simplemente incluir código en la sección “Arrange” del test? Pues parece más legible, desde luego… al final los detractores de los atributos siempre esgrimirán el argumento de que no favorecen la lectura del código y quizás nos les falte razón.

 

Como añadido, si queremos saber el directorio actual donde se está ejecutando el test (para crear algún fichero por ejemplo), con NUnit usaremos TestContext.TestDirectory https://github.com/nunit/docs/wiki/TestContext#testdirectory mientras que xUnit.net no nos brinda ningún método de ayuda y tendremos que obtener esta información a mano con un código como el siguiente:

private string GetTestDirectory()
{
    var codebase = new Uri(Assembly.GetExecutingAssembly().CodeBase);
    return Path.GetDirectoryName(codebase.LocalPath);
}

Si estamos en .NET Core, el código sería el siguiente:

private string GetTestDirectory()
{
    var codebase = new Uri(typeof(YourTypeGoesHere).GetTypeInfo().Assembly.CodeBase);
    return Path.GetDirectoryName(codebase.LocalPath);
}

Después de ver este post, te adelanto que ya sólo queda uno pendiente en la serie y será en el que hablemos sobre los test parametrizados.

 

Si te preguntas si es mejor usar NUnit o xUnit, valga este reddit para decirte que da igual https://www.reddit.com/r/csharp/comments/4198ei/should_i_use_nunit_or_xunit/ 
“It doesn't matter. NUnit 3 is available now which has a bunch of nice new features, being a complete rewrite. XUnit continues to innovate. Neither will change your life for the better (or worse) in any dramatic way. Spend an hour with each and then pick the one that feels most intuitive. Or just pick XUnit if you want the current populist choice. It really doesn't matter.”

 

Mas post de esta serie:


Un saludo!

lunes, 17 de febrero de 2014

Convenciones de nombrado para ViewModels en ASP.NET MVC

En ASP.NET MVC es muy utilizado (y también aconsejable) practicar el patrón ViewModel. Básicamente, el patrón ViewModel consiste en crear una clase específica para una vista con todo lo necesario para que pueda renderizarse. De este modo, evitamos el paso de información no estructura a la vista desde el controlador (como hacemos cuando utilizamos ViewBag).

En cualquier caso, el propósito de este post no es contar qué es un ViewModel sino que convenciones utilizo “yo” para su nombrado y ubicación dentro del código. Te prometo que cuando empiezas a tener muchos controladores, muchas acciones y muchos ViewModels, o te organizas o terminas con una maraña de clases importante (al menos a mí me pasa).

“Mis” convenciones son:

Crear una carpeta raíz en el proyecto con el nombre ViewModels.

Aquí a veces pienso en crear un ensamblado para guardar los ViewModels, pero bueno…

Dentro de esta carpeta, crear una nueva carpeta por cada controlador con el nombre del mismo. Por ejemplo y para el controlador Account: ViewModels\Account

Por cada método de acción de un controlador crear una clase con el nombre {Controller}{Action}ViewModel

¿Por qué pongo {Controller} en el nombre de la clase cuando ya está contenida en una carpeta (y por ende un espacio de nombres) también con {Controller}?

Pues lo hago porque me ayuda a reconocer mejor la clase fuera de ciertos contextos. Por ejemplo para un método de acción Index del controlador Customers, puedo llamar al ViewModel bien IndexViewModel o bien CustomersIndexViewModel. El problema de la segunda forma (la que utilizo) es que podría llegar a ser muy verbose... pero no me importa, con franqueza. Sin embargo, el problema de la primera forma (la que no incluye {Controller}) es que si la utilizo sin estar plenamente cualificada (esto es importando el espacio de nombres - using ViewModels.Customers), fuera del ámbito del controlador o su vista pierde significado y no puedo ubicarla directamente ni saber su propósito de un primer vistazo.

Otra convención (o convención sugerida, que no obligada, porque la cumplo cuando quiero y cuando no quiero no), es cambiar el nombre del ViewModel a {Controller}{View}ViewModel. Es decir, cambiamos {Action} por {View}. Esto lo hago porque no siempre la vista tiene el mismo nombre que la acción y entonces me parece que es justo que el nombre del ViewModel esté más cerca de a quién realmente pertenece, que es la vista y no a la acción.

Por último, si tengo dos ViewModels para la misma acción (normalmente una para la petición y otro para la respuesta), utilizo la palabra Request y Response para diferenciarlos  (o también Get y Post) para diferenciarlos. Es decir, el nombre del ViewModel con este matiz queda así: {Controller}{Action}[Request|Response]ViewModel.

¿Y tus convenciones para ViewModel cuáles son?

¡¡Actualizado!!

Los buenos de @gulnor, @2Flores y @rf1souto me comentan otras opciones como la de crear una estructura basada en funcionalidades en vez de en la convención sugerida por MVC.

El post semilla de todo esto es Feature Folders in ASP.NET MVC. Para llevarlo a cabo (y agregando algo de nuestra propia cosecha) hemos hecho lo siguiente: 

Agregar una nueva ruta donde buscar vistas al motor RazorViewEngine en el global.asax (este código no es el mejor, está claro, accede al motor por índice, pero ya se mejorará…). Lo importante es que preserva las rutas originales (las que siguen la convención de MVC) pero agregar una nueva:

            var razorViewEngine = ViewEngines.Engines[1] as RazorViewEngine;
            var viewLocationFormats = razorViewEngine.ViewLocationFormats;
            var newViewLocationFormats = new List<string>(viewLocationFormats);
            newViewLocationFormats.Add("~/Features/{1}/Views/{0}.cshtml");
            razorViewEngine.ViewLocationFormats = newViewLocationFormats.ToArray();

Después simplemente crear una estructura de carpetas donde, como puedes ver, está agrupando por característica tanto el controlador como las vistas y ¿por qué no también los ViewModels?

Financiero

El fichero Web.config lo hemos tenido que meter porque al principio nos daba un error la vista porque no heredaba de WebViewPage o WebViewPage<TModel>, pero rápidamente llegó stackoveflow al rescate y nos dio la idea.

Un saludo!

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!