You are currently browsing the tag archive for the ‘Test Driven Development’ tag.

Esta nota viene a cerrar una serie de tres posts anteriores

TDD… y las cosas, felizmente, no volvieron a ser las mismas…,

TDD, por dónde empezar y

TDD, ahora Refactoring.

En este caso, a pedido de Luis Petek en un comentario al tercer post de la serie, explico algunos otros atributos de NUnit que podrían ser de utilidad.

Así como la rutina que esté marcada por el atributo <Setup> se ejecuta antes de cada uno de los Tests, existe una forma de marcar otra rutina para que se ejecute después de cada uno de los Test.

Este atributo es <TearDown>.

En el ejemplo que venimos siguiendo, vamos a desdoblar la declaración de la variable configurationAppSettings de su instancia, sólo con el fin de darle sentido al ejemplo.

Dejo la declaración donde está y su instancia pasa al setup.

C#

using NUnit.Core;

using NUnit.Framework;

 

[TestFixture()]

public class Class1

{

    public System.Configuration.AppSettingsReader configurationAppSettings;

    public string sKeyClave;

 

    [SetUp()]

    public void Setup()

    {

        configurationAppSettings = new System.Configuration.AppSettingsReader();

        sKeyClave = (string)(configurationAppSettings.GetValue(«Clave», typeof(string)));

    }

 

    [Test()]

    public void Prueba()

    {

        Assert.IsNotNull(sKeyClave);

    }

 

    [TearDown]

    public void Dispose()

    {

        configurationAppSettings = null;

    }

 

}

 

VB

Imports NUnit.Core

Imports NUnit.Framework

 

<TestFixture()> _

Public Class Class1

    Public configurationAppSettings As System.Configuration.AppSettingsReader

    Public sKeyClave As String

 

    <SetUp()> _

    Public Sub Setup()

        configurationAppSettings = New System.Configuration.AppSettingsReader

        sKeyClave = CType(configurationAppSettings.GetValue(«Clave», GetType(System.String)), String)

    End Sub

 

    <Test()> _

    Public Sub Prueba()

        Assert.IsNotNull(sKeyClave)

    End Sub

 

    <TearDown()> _

    Public Sub Dispose()

        configurationAppSettings = Nothing

    End Sub

End Class

 

Compilamos y ejecutamos un Testing:

¡Verde!…

Podemos continuar.

Cabe aclarar que, del mismo modo que con <Setup>, no puede haber más que un <TearDown> por TestFixture.

Existe también una forma de verificar la generación de una excepción, para nuestro ejemplo voy a plantear el caso de intentar recuperar una clave inexistente de app.config:

C#

using NUnit.Core;

using NUnit.Framework;

 

[TestFixture()]

public class Class1

{

    public System.Configuration.AppSettingsReader configurationAppSettings;

    public string sKeyClave;

 

    [SetUp()]

    public void Setup()

    {

        configurationAppSettings = new System.Configuration.AppSettingsReader();

        sKeyClave = (string)(configurationAppSettings.GetValue(«Clave», typeof(string)));

    }

 

    [Test()]

    public void VerificarClave()

    {

        Assert.IsNotNull(sKeyClave);

    }

 

    [Test]

    [ExpectedException(typeof(System.InvalidOperationException))]

    public void EsperaUnaExcepcion()

    {

        sKeyClave = (string)(configurationAppSettings.GetValue(«NoExiste», typeof(string)));

        Assert.IsNull(sKeyClave);

    }

 

    [TearDown]

    public void Dispose()

    {

        configurationAppSettings = null;

    }

}

 

VB

Imports NUnit.Core

Imports NUnit.Framework

 

<TestFixture()> _

Public Class Class1

    Public configurationAppSettings As System.Configuration.AppSettingsReader

    Public sKeyClave As String

 

    <SetUp()> _

    Public Sub Setup()

        configurationAppSettings = New System.Configuration.AppSettingsReader

        sKeyClave = CType(configurationAppSettings.GetValue(«Clave», GetType(System.String)), String)

    End Sub

 

    <Test()> _

    Public Sub VerificarClave()

        Assert.IsNotNull(sKeyClave)

    End Sub

 

    <Test(), ExpectedException(GetType(System.InvalidOperationException))> _

    Public Sub EsperaUnaExcepcion()

        sKeyClave = CType(configurationAppSettings.GetValue(«NoExiste», GetType(System.String)), String)

        Assert.IsNull(sKeyClave)

    End Sub

 

    <TearDown()> _

    Public Sub Dispose()

        configurationAppSettings = Nothing

    End Sub

End Class

 

Además le modifiqué el nombre Prueba por VerificarClave para mayor claridad.

Compilemos y ejecutemos el Test:

¡Verde!…

Podemos ver que en EsperaUnaExcepcion el Test es exitoso cuando se produce la excepción, esto es porque al intentar recuperar la clave NoExiste de app.config se produce la excepción.

Si quieren verificar, cambien el string NoExiste por Clave, que si existe en app.config, y podrán ver que el Test EsperaUnaExcepcion falla.

Existen más atributos de NUnit, les recomiendo que lean la documentación, aquí tienen la lista completa de atributos y en este otro link pueden acceder a la documentación en general de NUnit para todas sus versiones.

Ojalá les sea útil y espero sus comentarios. 🙂

Este post es continuación y cierre de estos dos:

TDD… y las cosas, felizmente, no volvieron a ser las mismas…

TDD, ¿por dónde empezar?

De acuerdo a lo que expresé en los post anteriores, TDD se se lleva a cabo sobre ciclos. Al finalizar cada ciclo TDD, la técnica se completa con la práctica de Refactoring.

Refactoring es la aplicación de distintas técnicas que mejoran la calidad del código. Como en TDD, se recomienda realizar cambios pequeños, a fin de garantizar un tránsito entre estados deseables, en otras palabras, mantener la integridad de los Tests.

Refactoring, también se practica sobre ciclos.

El Cliclo de Refactoring lo podemos resumir en los siguientes pasos:

  • Identificar un problema,
  • Seleccionar la técnica de Refactoring a aplicar,
  • Realizar los cambios en el código,
  • Compilar,
  • Ejecutar los Tests.
  • Recomenzar

Los cambios en código operados por el Refactoring deben ser pequeños, esto garantiza la transición de una versión a otra de forma estable.

Vuelvo al código del ejemplo anterior. Repasemos la clase de la aplicación, Recupera, está encargada de recuperar un string:

C#

public class Recupera

{

    public string Valor()

    {

        return «Prueba»;

    }

}

 

VB

Public Class Recupera

    Public Function Valor() As String

        Return «Prueba»

    End Function

End Class

 

Voy a declarar la función Recupera como estática, de esa forma no va a ser necesario instanciarla para invocarla. Veamos como queda:

C#

public class Recupera

{

    public static string Valor()

    {

        return «Prueba»;

    }

}

 

VB

Public Class Recupera

    Public Shared Function Valor() As String

        Return «Prueba»

    End Function

End Class

 

Pero si vamos a nuestra clase de Test nos encontramos con un error. Es lógico, ya que estamos instanciando una clase estática y eso no es correcto.

C#

using NUnit.Core;

using NUnit.Framework;

using SyPCS;

 

namespace SyPCS.Test

{

    [TestFixture]

    public class Class1

    {

        [Test]

        public void Prueba()

        {

            Recupera recupera = new Recupera();

            string s = recupera.Valor();

            Assert.IsNotNull(s);

        }

    }

}

VB

Imports NUnit.Core

Imports NUnit.Framework

Imports SyPVB

 

Namespace SyPVB.Test

    <TestFixture()> _

    Public Class Class1

        <Test()> _

        Public Sub Prueba()

            Dim mRecupera As New Recupera

            Dim s As String = mRecupera.Valor

            Assert.IsNotNull(s)

        End Sub

    End Class

End Namespace

 

Practicamos el Refactoring correspondiente y obtenemos

C#

using NUnit.Core;

using NUnit.Framework;

using SyPCS;

 

namespace SyPCS.Test

{

    [TestFixture]

    public class Class1

    {

        [Test]

        public void Prueba()

        {

            Assert.IsNotNull(Recupera.Valor());

        }

    }

}

 

VB

Imports NUnit.Core

Imports NUnit.Framework

Imports SyPVB

 

Namespace SyPVB.Test

    <TestFixture()> _

    Public Class Class1

        <Test()> _

        Public Sub Prueba()

            Assert.IsNotNull(Recupera.Valor)

        End Sub

    End Class

End Namespace

 

Se lee un código más sintético, evitamos la declaración de ‘s’, que, todos sabemos, es un nombre horrible.

Hagamos un Build, <Ctrl>/<Shift>/<B> y vamos a NUnit, encotramos todos los indicadores en gris, porque hicimos un build.

Ejecutamos y…

¡Verde!

Seguimos adelante.

Ahora vamos por un Refactoring que modifique Recupera de tal forma que extraiga el valor de un lugar un poco más serio, por ejemplo de un archivo de configuración, en este caso app.config como el siguiente:

<?xml version=«1.0« encoding=«utf-8« ?>

<configuration>

  <appSettings>

    <add key=«Clave« value=«Prueba«/>

  </appSettings>

</configuration>

 

Dentro de configuration, en el bloque appSettings agregamos una entrada que identificamos como ‘Clave’ con el valor que querramos recuperar, en este caso ‘Prueba’.

Las clases de Testing quedarían así:

C#

using NUnit.Core;

using NUnit.Framework;

using SyPCS;

 

namespace SyPCS.Test

{

    [TestFixture]

    public class Class1

    {

        System.Configuration.AppSettingsReader configurationAppSettings = new System.Configuration.AppSettingsReader();

 

        [Test]

        public void Prueba()

        {

            Assert.IsNotNull(configurationAppSettings.GetValue(«Clave», typeof(string)));

        }

    }

}

 

VB

Imports NUnit.Core

Imports NUnit.Framework

Imports SyPVB

 

Namespace SyPVB.Test

    <TestFixture()> _

    Public Class Class1

        Public configurationAppSettings As New System.Configuration.AppSettingsReader

 

        <Test()> _

        Public Sub Prueba()

            Assert.IsNotNull(configurationAppSettings.GetValue(«Clave», GetType(System.String)))

        End Sub

    End Class

End Namespace

 

Compilamos, no da errores, entonces hacemos un Test:

¡Verde!… A Recomenzar…

Si miramos las clases de Testing podremos ver que la referencia al proyecto SyPCS, en C#, y SyPVB, en VB, ya no son necesarios. Nuestro próximo ciclo se trata de eliminar esas referencias y su Namespace correspondiente.

Esto tiene un doble beneficio, independiza el proyecto de Testing del proyecto de desarrollo y como consecuencia resulta un código más claro de leer.

C#

using NUnit.Core;

using NUnit.Framework;

 

[TestFixture]

public class Class1

{

    System.Configuration.AppSettingsReader configurationAppSettings = new System.Configuration.AppSettingsReader();

 

    [Test]

    public void Prueba()

    {

        Assert.IsNotNull(configurationAppSettings.GetValue(«Clave», typeof(string)));

    }

}

 

VB

Imports NUnit.Core

Imports NUnit.Framework

 

<TestFixture()> _

Public Class Class1

    Public configurationAppSettings As New System.Configuration.AppSettingsReader

 

    <Test()> _

    Public Sub Prueba()

        Assert.IsNotNull(configurationAppSettings.GetValue(«Clave», GetType(System.String)))

    End Sub

End Class

 

Nuevamente compilamos y ejecutamos Testing:

¡Verde!…

Podemos dar por concluido nuestro Refactoring e incorporar la llamada al archivo de configuración al proyecto de desarrollo con la seguridad que el mismo funciona. Primero creamos en cada uno de ellos un archivo de configuración como los que creamos en los proyectos de Testing y luego hacemos el reemplazo:

C#

public class Recupera

{

    public static string Valor()

    {

        System.Configuration.AppSettingsReader configurationAppSettings = new System.Configuration.AppSettingsReader();

        return ((string)(configurationAppSettings.GetValue(«Clave», typeof(string))));

    }

}

 

VB

Public Class Recupera

    Public Shared Function Valor() As String

        Dim configurationAppSettings As New System.Configuration.AppSettingsReader

        Return CType(configurationAppSettings.GetValue(«Clave», GetType(System.String)), String)

    End Function

End Class

 

Un último detalle en nuestros proyectos de Testing.

Podemos pensar en que el valor levantado del archivo de configuración se va a utilizar en otros Test que van a irse desarrollando, entonces podemos pensar que es ineficiente que en cada Test que se ejecute, se esté recuperando exactamente el mismo valor.

Para este tipo de situaciones existe el tag <Setup> en NUnit. Este tag indica a NUnit que la rutina que clasifica es común a todos los Tests que encierra la clase.

Veamos cómo quedan nuestros ejemplos:

C#

using NUnit.Core;

using NUnit.Framework;

 

[TestFixture()]

public class Class1

{

    public System.Configuration.AppSettingsReader configurationAppSettings = new System.Configuration.AppSettingsReader();

    public string sKeyClave;

 

    [SetUp()]

    public void Setup()

    {

        sKeyClave = (string)(configurationAppSettings.GetValue(«Clave», typeof(string)));

    }

 

    [Test()]

    public void Prueba()

    {

        Assert.IsNotNull(sKeyClave);

    }

}

 

VB

Imports NUnit.Core

Imports NUnit.Framework

 

<TestFixture()> _

Public Class Class1

    Public configurationAppSettings As New System.Configuration.AppSettingsReader

    Public sKeyClave As String

 

    <SetUp()> _

    Public Sub Setup()

        sKeyClave = CType(configurationAppSettings.GetValue(«Clave», GetType(System.String)), String)

    End Sub

 

    <Test()> _

    Public Sub Prueba()

        Assert.IsNotNull(sKeyClave)

    End Sub

End Class

 

Y con esto estamos en condiciones de seguir practicando TDD.

Cierro este post agradeciendo los comentarios elogiosos y el aliento que me brindaron comentando en este blog y en el grupo CodeGeneration de Google a Ángel «Java» López, Martín Salías, Luis Petek y Luis Lobo Borobia. 🙂

Este post es continuación de este otro.

Supongamos que tenemos una aplicación que necesita recuperar una cadena. El Test que acá vamos a desarrollar es probar la recuperación de esta cadena.

Ya tendríamos que tener instalada la última versión de NUnit. Sino, podemos descargarla desde http://www.nunit.org e instalarla.

En el marco de nuestra solución vamos a agregar un proyecto de tipo Class Library donde iremos escribiendo el Test. El ejemplo lo desarrollo en Visual Basic .NET y C#.

Una característica curiosa de esta técnica es que estos proyectos no se ejecutan, sólo se compilan y la dll resultante es la que se prueba con la herramienta.

En el proyecto de test hay que agregar las referencias que exige NUnit, buscamos en la carpeta bin de la instalación de NUnit, tipicamente C:\Program Files\NUnit 2.4.6\bin dependiendo de la versión de la heramienta. Estas referencias son nunit.core y nunit.framework.

VB

En C# hay que agregar una referencia más, nunit.core.interfaces.dll

Para definir una clase básica para comenzar a practicar TDD veamos estos ejemplos:

C#

using nunit.core;

using nunit.framework;

 

namespace SyPCS.Test

{

    [TestFixture]

    public class Class1

    {

 

    }

}

VB

Imports NUnit.Core

Imports NUnit.Framework

 

Namespace SyPVB.Test

    <TestFixture()> _

    Public Class Class1

 

    End Class

End Namespace

Tenemos dos elementos novedosos. En primer lugar tenemos que importar las dlls de NUnit que posibilitan el trabajo con la herramienta y en segundo lugar el tag TestFixture que posibilita que NUnit reconozca a la clase como contenedore de Tests.

Compilemos esto.

Ya tenemos la base para programar nuestro primer Test.

Nuestra aplicación tiene que ser capaz de leer un valor que se recupera desde algún elemento de persistencia. De esto se tratará nuestro Test.

Entonces, siguiendo con el esquema de Ciclo TDD que presenté en el post anterior,

  • Escribir un Test, aún de algo que todavía no hayamos codificado,
  • Compilar, va a dar error, ya que todavía no hemos codificado lo que estamos probando,
  • Escribir la mínima cantidad de código para que compile, aunque sea la cabecera del método o función a probar,
  • Compilar, ahora no da error, estamos en condiciones de ejecutar nuestro Test,
  • Ejecutar el Test, falla, ya que habíamos escrito la mínima cantidad de código que garantice la compilación, pero no la ejecución del Test,
  • Escribir la mínima cantidad de código para que el Test no falle,
  • Ejecutar el Test, esta vez no falla,
  • Refactorizar, este es un tema aparte, hay variadas técnicas de refactorización y muchos buenos libros escritos, más adelante veremos someramente alguna de esas técnicas,
  • Recomenzar escribiendo un nuevo Test.

Comienzo por Escribir un Test, aún de algo que todavía no hayamos codificado:

C#

using NUnit.Core;

using NUnit.Framework;

 

namespace SyPCS.Test

{

    [TestFixture]

    public class Class1

    {

        [Test]

        public void Prueba

        {

            Recupera recupera = new Recupera;

            Assert.IsNotNull(recupera.Valor);

        }

    }

}

VB

Imports NUnit.Core

Imports NUnit.Framework

 

Namespace SyPVB.Test

    <TestFixture()> _

    Public Class Class1

        <Test()> _

        Public Sub Prueba()

            Dim mRecupera As New Recupera

            Assert.IsNotNull(mRecupera.Valor)

        End Sub

    End Class

End Namespace

Si. Ya sé…

Yo también me indigné la primera vez que vi algo como esto…

Obviamente… no compila…

No hay nada declarado con el nombre Recupera y menos su propiedad Valor.

Pero esta es la idea.

El segundo paso es Compilar.

Y falla, como se lee en el ciclo TDD.

Ahora tenemos que Escribir la mínima cantidad de código para que compile

Entonces voy a crear dos proyectos, uno C# y otro VB, ambos de tipo Class Library (para hacerlo más simple) que jugarán el papel de proyectos de desarrollo.

En ambos codificamos una función de nombre Valor.

C#

public class Recupera

{

    public void Valor()

    {

 

    }

}

VB

Public Class Recupera

    Public Function Valor()

 

    End Function

End Class

Compilamos ambos proyectos.

Ahora vamos a los proyectos de Testing, en cada uno de ellos agregamos una referencia, en TestCS agregamos la referencia a SyPCS y en TestVB agregamos una a SyPVB.

De esa forma podemos adicionar al código el using (en C#) o Imports (en VB) que corresponda.

Quedando así:

C#

using NUnit.Core;

using NUnit.Framework;

using SyPCS;

 

namespace SyPCS.Test

{

    [TestFixture]

    public class Class1

    {

        [Test]

        public void Prueba()

        {

            Recupera recupera = new Recupera();

            string s = recupera.Valor();

            Assert.IsNotNull(s);

        }

    }

}

 

VB

Imports NUnit.Core

Imports NUnit.Framework

Imports SyPVB

 

Namespace SyPVB.Test

    <TestFixture()> _

    Public Class Class1

        <Test()> _

        Public Sub Prueba()

            Dim mRecupera As New Recupera

            Dim s As String = mRecupera.Valor

            Assert.IsNotNull(s)

        End Sub

    End Class

End Namespace

Según el ciclo, nuestro próximo paso es Compilar.

Y compila. Estamos en condiciones de ejecutar nuestro primer Test.

Abrimos NUnit y vemos su IDE:

Vamos a File/New Project y le ponemos el nombre Ejemplo:

Ahora vamos a Project/Add assembly… y seleccionamos la dll de alguno de los proyectos de Testing, por ejemplo el de VB:

y veremos lo siguiente:

Vemos que el árbol de Test se ve en el Gris de Refactoring.

Siguiendo en el Cliclo TDD ejecutamos el Test. Hacemos Click sobre el botón Run en NUnit y …

¡Falló!…

Tal como dicta el Ciclo TDD.

Sigamos con Escribir la mínima cantidad de código para que el Test no falle.

Hacemos que nuestra función Valor devuelva un string cualquiera:

C#

public class Recupera

{

    public string Valor()

    {

        return «Prueba»;

    }

}

 

VB

Public Class Recupera

    Public Function Valor() As String

        Return «Prueba»

    End Function

End Class

Y volvemos a compilar los cuatro proyectos, primero los de Desarrollo, luego los de Prueba.

Volvemos a NUnit y vemos todo en Gris nuevamente:

Y ahora hacemos click nuevamente en Run y …

¡¡¡Green!!!

Anduvo. Según el Ciclo TDD nos toca Refactorizar.

Pero esto… en el próximo post. 🙂

TDD, Test Driven Development, es una técnica que establece que primero se escriben las pruebas y luego el código del aplicativo.

Estas pruebas se efectúan de acuerdo a lo que se espera que haga el aplicativo, es por eso que algunos popes de esta disciplina dicen que practicándola intensivamente podemos prescindir de la documentación ya que el código de las pruebas es en si mismo documentación acerca del aplicativo.

En otras palabras las pruebas especifican lo que se espera de la aplicación a desarrollar.

Esta práctica es de amplia difusión en las Metodologías Ágiles, con esto se garantiza código de calidad desde el inicio del proceso de desarrollo y proporciona un buen elemento para el establecimiento de métricas.

El Ciclo TDD se compone de los siguientes pasos:

  • Escribir un test, aún de algo que todavía no hayamos codificado,
  • Compilar, va a dar error, ya que todavía no hemos codificado lo que estamos probando,
  • Escribir la mínima cantidad de código para que compile, aunque sea la cabecera del método o función a probar,
  • Compilar, ahora no da error, estamos en condiciones de ejecutar nuestro Test,
  • Ejecutar el test, falla, ya que habíamos escrito la mínima cantidad de código que garantice la compilación, pero no la ejecución del Test,
  • Escribir la mínima cantidad de código para que el Test no falle,
  • Ejecutar el test, esta vez no falla,
  • Refactorizar, este es un tema aparte, hay variadas técnicas de refactorización y muchos buenos libros escritos, más adelante veremos someramente alguna de esas técnicas,
  • Recomenzar escribiendo un nuevo Test.

Es habitual encontrar TDD asociado a estas tres palabras en sus respectivos colores:

Red, Green, Refactor

Estos son términos y colores asociados a la familia de herramientas de Test Unitarios xUnit, dela que usaremos NUnit, se pueden descargar la última versión desde http://www.nunit.org, también van a encontrar documentación.

Red, está asociado a la ejecución fallida de uno o más Tests.

Green, es el color que se ve en la IDE de NUnit al ejecutar exitosamente uno o más Test.

Refactor, lo podemos ver cuando estamos refactorizando nuestro proyecto de Testing.

Es muy importante que las modificaciones se hagan en pequeños pasos, esto hace a la estabilidad del código probado a lo largo del proceso de desarrollo. Una buena estrategia de Testing evita los grandes objetos, los Test integrales, la interdependencia entre las pruebas.

Una buena estructura TDD tiende a que el equipo de desarrollo esté más y mejor comunicado. Como se elimina la probabilidad de romper funcionalidad existente al liberar código nuevo, se eliminan temores a «papelones».

La Barra Verde que nos muestra NUnit cuando los Test son exitosos se convierten en un factor motivacional positivo.

Por su propia naturaleza, TDD elimina los problemas derivados de las contínuas postergaciones del Debugging.

¿Y?… ¿cómo se hace?…

Te pido que me esperes a que termine el próximo post. 🙂

Asesoramiento a Organizaciones Públicas y Privadas.

Cursos a grupos ya constituidos.

Charlas de orientación.

Metodologías Ágiles.

Startups.

Visitas

  • 146.542 hits

Mejor calificado

del.icio.us