Este post es continuación y cierre de estos dos:
TDD… y las cosas, felizmente, no volvieron a ser las mismas…
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. 🙂
8 comentarios
Comments feed for this article
Lunes 14 \14-03:00 abril \14-03:00 2008 a 9:04:21
Luis Petek
Hola Gente
Me gusto mucho esta tira de tres notas sobre TDD, y los ejemplos.Gracias Carlos.
Para tener en cuenta. Existen otros tags que pueden ser muy útiles en aplicaciones no triviales, donde nuestros objetos y sus pruebas pueden estar inmersos en situaciones mas complejas. A modo de sugerencia esta el caso del TearDown para la finalización. Esto solo como para sembrarles la curiosidad.
Y esto sin entrar en Mock Objects……. donde les recomiendo Moq.
—
Saludos.
Petek Luis.
Lunes 14 \14-03:00 abril \14-03:00 2008 a 10:04:19
Carlos Marcelo Santos
Muchas Gracias por tus observaciones Luis.
Me das tema para próximos posts.
Miércoles 14 \14-03:00 mayo \14-03:00 2008 a 12:05:38
José Franco
[…]
Viernes 20 \20-03:00 junio \20-03:00 2008 a 7:06:21
Manometer
Somehow i missed the point. Probably lost in translation 🙂 Anyway … nice blog to visit.
cheers, Manometer
Viernes 24 \24-03:00 julio \24-03:00 2009 a 18:07:10
Pedro O.
Hola.. Interesantes tus ejemplos, pero tengo una duda: ¿Qúe sucede si la clase a testear, esta compuesta sólo de métodos privados o protected?…
Tengo mis dudas con esto.
Gracias
Martes 28 \28-03:00 julio \28-03:00 2009 a 12:07:17
Carlos Marcelo Santos
Hola Pedro:
No estoy seguro de haberte entendido. A ver si voy bien.
La clase o las clases a testear son codificadas al sólo efecto de testing, no de funcionalidad. No se testean las clases que hacen al aplicativo directamente. Entonces todos los métodos y funciones a testear deben ser públicos para que NUnit pueda levantarlos.
¿Es esa tu duda?
Saludos.
Martes 28 \28-03:00 julio \28-03:00 2009 a 16:07:13
Pedro O.
Trataré de explicarme mejor: Existe un proyecto desarrollado por un amigo, el cual me lo ha enviado para que le aplique pruebas unitarias a su código. Entonces, Yo comenzaré al revés de lo que has hecho Tú. Las clases y metodos que componen la aplicación estan creados, y mi tarea es crear Test con Nunit para demostrar que sus métodos no estan bien construidos.
El problema que tengo son los métodos que él(mi amigo) ha creado. Casi el 80% son private, con lo cual me pregunto si ¿será una buena práctica el cambiar todos sus métodos a public y comenzar a realizar mis pruebas unitarias, sin mas?..
Martes 28 \28-03:00 julio \28-03:00 2009 a 18:07:45
Carlos Marcelo Santos
Hola Pedro:
No es una buena buena práctica hacer público todo lo que tu amigo declaró como privado. Seguramente tu amigo diseño sus clases con orientación a objetos y si hay métodos y funciones privadas lo habrá hecho, con la intencion de encapsular funcionalidad, entre otras cosas.
No le publiques todo, corrés el riesgo de perder un amigo… 🙂
Entiendo que el escenario no es el ideal para TDD, casi siempre es así, no te preocupes.
No es bueno montar los tests sobre el código de producción, es inevitable que el código se ensucie incluyendo tags propios de NUnit y código adicional. Además vas a tener una muy baja cohesión, etc.
Por eso te recomiendo que identifiques lo que querés testear, definas para cada caso los tests unitarios que se te parezcan necesarios y desarrollalos en una clase aparte para tal fin, incluso en un proyecto aparte, para practicar TDD con NUnit vas a necesitar generar una dll exclusivamente para ese fin.
Saludos.