You are currently browsing the category archive for the ‘.NET’ category.

Hasta la versión 2.0 del Framework.NET, la declaración de un delegado sólo era posible programando métodos con nombre. A partir de la versión 2.0 se introduce el concepto Método Anónimo.

Un método anónimo nos permite definir una función de manera implícita, sin necesidad de declararla dándole un nombre.

Como los Métodos Anónimos no tienen nombre, la forma de invocarlos es a través de un delegado.

Usar un Método Anónimo es pasar como parámetro a un bloque de código definido como tipo Delegate, sin asignarle un nombre.

Por ejemplo, si en el constructor de un formulario insertamos la siguiente línea de código:

 

  this.Click += delegate { MessageBox.Show(@”Funciona.”); };

 

Al hacer un click sobre el formulario se desplegará un MessageBox que muestre el string “Funciona”.

En el ejemplo podemos ver que en la misma línea se define el Delegate y su implementación. Eso es exactamente un Método Anónimo.

Los Métodos Anónimos reducen la sobrecarga de código al crear instancias de delegados sin tener que crear métodos independientes.
En caso que quisiéramos capturar algún parámetro del evento, sólo tenemos que declarar los argumentos a continuación de la palabra delegate:

 

  this.Click += delegate(object sender, EventArgs e)

  {

      System.Windows.Forms.MessageBox.Show(@”Funciona. “ + e.ToString());

  };

 

Para el uso de Métodos Anónimos existen algunas restricciones:

  • No se puede invocar código no manejado en un Método Anónimo.
  • Las variables declaradas en el mismo bloque donde se declara un Método Anónimo se llaman Externas (Outer) al Método Anónimo. No pueden ser accedidos parámetros de ámbito externo calificados con ref u out.
  • En un Método Anónimo no pueden usarse goto, break ni continue si apunta fuera del ámbito del Método Anónimo, tampoco pueden usarse desde fuera del Método Anónimo si el destino se encuentra dentro del bloque del Método Anónimo. Más allá de Método Anónimo, siempre es bueno evitar el uso de goto, break y continue.
  • No pueden usarse Método Anónimo en el lado izquierdo del operador is.

Los Métodos Anónimos son ideales en el manejo de eventos cuando lo que hay que hacer son cosas simples permitiendo un código compacto. Pero no hay que perder de vista que el abuso de esta técnica puede tornar nuestro código como algo muy difícil de leer y entender.

To implement an interface is to set up one contract between two code blocks, where the block that implements the interface is committed to implement specifically the methods defined by the interface.

At glance this sounds somehow weird. The question that arises is: Don’t I’m complicating my life? It depends…

Let’s suppose that, in the frame of a process, we have a heterogeneous set of objects that we unknown. Some have a serialization method named Save, if the method exists, then this is the responsible of the object persistence. If doesn’t exist such Save method, the object is persisted in the database by another method.

The question is how to find out, in runtime, if an object is implementing one method with a specific name? The following attempts to answer this. To develop that example, you will need to start Visual Studio and create one project of windows Console type for Visual Basic or C#.

Now we create the interface ISerializable, that contains the Save function that returns a boolean value:

[VB]

Public Interface ISerializable

Function Save() As Boolean

 End Interface

[C#]

using System;

interface ISerializable

{

bool Save();

}

By convention the names of the interfaces are prefixed with an “I” for better identification.

Then we are going to create one class, named PettyControl, with one unique property named Data of string type:

[VB]

Public Class PettyControl

Private mData As String

Public Property Data() As String

Get

Return mData

End Get

Set(ByVal value As String)

mData = value

End Set

End Property

End Class

[C#]

using System;

public class PettyControl

{

protected string Data;

public string data

{

get

{

return Data;

}

set

{

Data = value;

}

}

}

I define now one control, named myControl, that inherits from PettyControl and implements the ISerializable interface.

[VB]

Public Class myControl

Inherits PettyControl

Implements ISerializable

Public Sub New()

Data = “These are the test data…”

End Sub

Public Function Save() As Boolean

Implements ISerializable.Save

Console.WriteLine(“Saving…{0}”, Data)

Return True

End Function

End Class

[C#]

using System;

class myControl : PettyControl, IValidate, ISerializable

{

public myControl()

{

data = “These are the test data…”;

}

public bool Save()

{

Console.WriteLine(“Saving…{0}”, data);

return true;

}

}

In this way we have implemented the Save method of ISerializable interface. You will see that when typing the code line referred to the implementation of the interface, when you click on Enter, Visual Studio writes automatically the header of the function that the interface defines.

At this point, we know how to implement an Interface.

Now the problem is to know, programmatically, if one class implements some specific interface. For the example we are seeing, in concrete terms, is to know whether the class has implemented some Save method, in other words, if the class implements some serialization method.

So,we are going to write the application that will start in console, naming it App.

[VB]

Module App

Sub Main()

Console.WriteLine(“Visual Basic Version”)

Dim myControl As New myControl

Dim ser As ISerializable = DirectCast(myControl, ISerializable)

Dim success As Boolean = ser.Save

Console.WriteLine(“‘{0}’ Validation was{1}success.”, myControl.Data, IIf(success, ” “, ” not “))

End Sub

Console.ReadLine()

End Module

[C#]

class App

{

public static void Main()

{

Console.WriteLine(“C# Version”);

myControl myControl = new myControl();

ISerializable ser = (ISerializable)myControl;

bool success = ser.Save();

Console.WriteLine(“‘{0}’ Validation was{1}success.”, myControl.data, (success ? ” “ : ” not “));

}

Console.ReadLine();

}

This seems to work, but if the interface is not implemented “DirectCast(myControl, ISerializable)” in VB, or “(ISerializable)myControl” in C#, both in this case fail.

The correct way to ask is: “TypeOf (myControl) Is ISerializable”, in VB, or “myControl Is ISerializable” in C#. Let’s see:

[VB]

Module App

Sub Main()

Console.WriteLine(“Visual Basic Version”)

Dim myControl As New myControl

If TypeOf (myControl) Is ISerializable Then

Dim ser As ISerializable = DirectCast(myControl, ISerializable)

Dim anotherSuccess As Boolean = ser.Save

Console.WriteLine(“{0}’ Have{1}been successfully saved., myControl.Data, IIf(anotherSuccess, ” “, ” not “))

End If

Console.ReadLine()

End Sub

End Module

[C#]

class App

{

public static void Main()

{

Console.WriteLine(“C# Version”);

myControl myControl = new myControl();

if (myControl is ISerializable)

{

ISerializable ser = (ISerializable)myControl;

bool anotherSuccess = ser.Save();

Console.WriteLine({0}’ Have{1}been successfully saved., myControl.data, (anotherSuccess ? ” “ : ” not “));

}

Console.ReadLine();

}

}

This works fine in both languages. But there is an optimized way, in terms of MSIL.

In VB, “TryCatch” is used, which evaluates whether the interface is implemented, if doesn’t it, doesn’t return an error but instead Nothing. In C# this is analogous to the “as” operator.

[VB]

Module App

Sub Main()

Console.WriteLine(“Visual Basic Version”)

Dim myControl As New myControl

Dim serial As ISerializable = TryCast(myControl, ISerializable)

If Not serial Is Nothing Then

Dim anotherMoreSuccess As Boolean = serial.Save

Console.WriteLine(‘{0}’ Have{1}been successfully saved, with an optimized version., myControl.Data, IIf(anotherMoreSuccess, ” “, ” not “))

End If

Console.ReadLine()

End Sub

End Module

[C#]

class App

{

public static void Main()

{

Console.WriteLine(“C# Version”);

myControl myControl = new myControl();

ISerializable serial = myControl as ISerializable;

if (null != serial)

{

bool anotherMoreSuccess = serial.Save();

Console.WriteLine(‘{0}’ Have{1}been successfully saved, with an optimized version., myControl.data, (anotherMoreSuccess ? ” “ : ” not “));

}

Console.ReadLine();

}

}

The solution, with example projects may be downloaded from Interfaces.zip.

I hope this will be useful. 🙂

En el ámbito del desarrollo de software, siempre es bueno eliminar dependencias. En otros términos, bajar el nivel de acoplamiento.

Hay casos en que se logra más directamente y otros en que no es tan simple.

Para estos últimos contamos con un pattern llamado Dependency Injection, Inyección de Dependencia, término por primera vez usado por Martin Fowler. Si te interesa ir a la fuente, acá está su documento sobre Dependency Injection.

En este post voy a intentar explicar que viene a resolver este patrón y sus distintas implementaciones.

Cuando tenemos un objeto que necesita de otro para funcionar correctamente, tenemos definida una dependencia. Esta dependecia puede ser altamente acoplada, tight coupling, o levemente acoplada, loose coupling. Si el acoplamiento es bajo el objeto independiente es fácilmente reemplazable, si, en cambio el acoplamiento es alto, el reemplazo no es fácil y se dificulta el diseño de tests unitarios.

Supongamos que tenemos una clase que define un producto, que necesita un componente, este componente puede provenir de distintos fabricantes, en general cubren las mismas funciones. Los productos son varios y alternativamente pueden valerse del componente de cualquier fabricante.

Si instanciamos uno de los componentes en forma directa necesitamos referenciarlo. Si el mercado nos exige cambiar de componente, tendríamos que eliminar la referencia anterior, crear la nueva, revisar si los métodos, funciones y propiedades son homogéneos, si no lo son, corregir el código donde sea necesario… Si a esto le agregamos que valdría la pena tener una versión con el primer componente y otra con el segundo, la situación empeora. Si hubiera cincuenta componentes alternativos, la situación ya sería inmanejable.

Acá es donde la necesidad del patrón Dependency Injection se hace evidente.

Supongamos que este conjunto de componentes ejecuta básicamente cuatro métodos, que son los que nosotros necesitamos: Initialize, Shutdown, Prepare y DoIt.

Entonces comencemos por escribir una interface que defina estos cuatro métodos:

[VB]

Public Interface ISomeHardware

Sub Initialization()

Sub Shutdown()

Sub Prepare()

Sub DoIt()

End Interface

[C#]

public interface ISomeHardware

{

void Initialization();

void Shutdown();

void Prepare();

void DoIt();

}

Las clases que definan los componentes deben implementar la interface ISomeHardware. En nuestro ejemplo, ComponentA y ComponentB:

[VB]

Public Class ComponentA

Implements ISomeHardware

Public Sub InitializationBegin() Implements ISomeHardware.Initialization

End Sub

Public Sub ShutdownBegin() Implements ISomeHardware.Shutdown

End Sub

Public Sub PrepareBegin() Implements ISomeHardware.Prepare

End Sub

Public Sub DoItBegin() Implements ISomeHardware.DoIt

End Sub

End Class

Public Class ComponentB

Implements ISomeHardware

Public Sub InitializationBegin() Implements ISomeHardware.Initialization

End Sub

Public Sub ShutdownBegin() Implements ISomeHardware.Shutdown

End Sub

Public Sub PrepareBegin() Implements ISomeHardware.Prepare

End Sub

Public Sub DoItBegin() Implements ISomeHardware.DoIt

End Sub

End Class

[C#]

class ComponentA : ISomeHardware

{

public void Initialization() { }

public void Shutdown() { }

public void Prepare() { }

public void DoIt() { }

}

class ComponentB : ISomeHardware

{

public void Initialization() { }

public void Shutdown() { }

public void Prepare() { }

public void DoIt() { }

}

Aclaración: Acá hago una simplificación a efecto didáctico. Las clases que definen ComponentA y ComponentB, en realidad debieran ser wrappers de las dll’s provistas por los fabricantes. Para no complicar inútilmente las dejaré así.

Veremos tres implementaciones del patrón Dependency Injection: por Constructor, por Setter y por Interface.

Por Constructor:

Ahora voy a usar otro patrón llamado Facade o Fachada, la idea es encapsular todos los aspectos complejos de un subsistema de clases en una única y simple interface. En nuestro caso, ProductFacade:

[VB]

Public Class ProductFacade

Private SomeHardware As ISomeHardware

Public Sub New(ByVal pSomeHardware As ISomeHardware)

SomeHardware = pSomeHardware

End Sub

End Class

[C#]

public class ProductFacade

{

private ISomeHardware SomeHardware;

public ProductFacade(ISomeHardware SomeHardware)

{

this.SomeHardware = SomeHardware;

}

}

En esta clase se implementa la Inyección de Dependencia por medio del constructor, que acepta un parámetro de entrada de tipo de interface ISomeHardware. Este punto es determinante. Cualquier clase que implemente la interface ISomeHardware es aceptada como parámetro del constructor de ProductFacade. Este es todo el secreto.

Entonces, para inyectar la dependencia de un componente de un fabricante u otro por Constructor, se hace así:

[VB]

Dim ComponentA As ISomeHardware = New ComponentA

Dim Product01 As ProductFacade = New ProductFacade(ComponentA)

Dim ComponentB As ISomeHardware = New ComponentB

Dim Product02 As ProductFacade = New ProductFacade(ComponentB)

[C#]

ISomeHardware componentA = new ComponentA();

ProductFacade product01 = new ProductFacade(componentA);

ISomeHardware componentB = new ComponentB();

ProductFacade product02 = new ProductFacade(componentB);

Realmente elegante.

Para las siguientes dos implementaciones del patrón, usaremos la misma interface ISomeHardware y la definición de las clases ComponentA y ComponentB.

Las variaciones estarán en la Fachada y en la Inyección de Dependencia.

Por Setter:

En este caso la Inyección de Dependencia se efectiviza a través de una propiedad definida en la Fachada.

[VB]

Public Class ProductFacade

Private mSomeHardware As ISomeHardware

Public Property SomeHardware() As ISomeHardware

Get

Return mSomeHardware

End Get

Set(ByVal value As ISomeHardware)

mSomeHardware = value

End Set

End Property

End Class

[C#]

public class ProductFacade

{

private ISomeHardware SomeHardware;

public ISomeHardware Component

{

get

{

return SomeHardware;

}

set

{

SomeHardware = value;

}

}

}

Para el caso de Setter, la Inyección de Dependencia queda determinada así:

[VB]

Dim ComponentA As ISomeHardware = New ComponentA

Dim Product01 As ProductFacade = New ProductFacade()

Product01.SomeHardware = ComponentA

Dim ComponentB As ISomeHardware = New ComponentB

Dim Product02 As ProductFacade = New ProductFacade()

Product02.SomeHardware = ComponentB

[C#]

ISomeHardware componentA = new ComponentA();

ProductFacade product01 = new ProductFacade();

product01.Component = componentA;

ISomeHardware componentB = new ComponentB();

ProductFacade product02 = new ProductFacade();

product02.Component = componentB;

Por Interface:

Ahora la Inyección de Dependencia está implementada sobre un método que acepta un parámetro de tipo de interface ISomeHardware.

[VB]

Public Class ProductFacade

Private mSomeHardware As ISomeHardware

Public Sub SetComponent(ByVal pSomeHardware As ISomeHardware)

mSomeHardware = pSomeHardware

End Sub

End Class

[C#]

public class ProductFacade

{

private ISomeHardware SomeHardware;

public void SetComponent(ISomeHardware SomeHardware)

{

this.SomeHardware = SomeHardware;

}

}

Cuando la Inyección de Dependencia es por Interface, se invoca de esta forma:

[VB]

Dim ComponentA As ISomeHardware = New ComponentA

Dim Product01 As ProductFacade = New ProductFacade()

Product01.SetComponent(ComponentA)

Dim ComponentB As ISomeHardware = New ComponentB

Dim Product02 As ProductFacade = New ProductFacade()

Product02.SetComponent(ComponentB)

[C#]

ISomeHardware componentA = new ComponentA();

ProductFacade product01 = new ProductFacade();

product01.SetComponent(componentA);

ISomeHardware componentB = new ComponentB();

ProductFacade product02 = new ProductFacade();

product02.SetComponent(componentB);

Las tres implementaciones difieren sutilmente entre si. La diferencia más importante que yo encuentro es que, Por Constructor, nos obliga a inyectar al crear la clase, mientras que las otras modalidades, la difieren para más adelante.

La diferencia entre estas dos últimas está en si se prefiere inyectar la dependencia en el set de una propiedad, Por Setter, o como argumento de un parámetro de entrada a un método o función, Por Interface. Cuestión de gusto sintáctico.

Espero que les sea útil. 🙂

En este post voy a escribir sobre métodos genéricos. La idea es presentar un método que acepte dos parámetros de tipo genérico.

Para quien no maneje el concepto de Generics recomiendo antes leer este post.

Vamos a usar un proyecto de tipo Consola con Visual Studio .NET, y la misma clase Libro que usé en el post anterior.

Primero, referenciamos lo siguiente:

C#

using System;

using System.Collections.Generic;

VB.NET

Imports System

Imports System.Collections.Generic

Ahora si, la clase Libro:

C#

public class Libro

{

public string sTitulo, sAutor;

public Libro(string Titulo, string Autor)

{

sTitulo = Titulo;

sAutor = Autor;

}

public override string ToString()

{

return string.Format(“{0} escribió {1}.”, sAutor, sTitulo);

}

}

VB.NET

Public Class Libro

Dim sTitulo, sAutor As String

Public Sub New(ByVal Titulo As String, ByVal Autor As String)

MyBase.New()

sTitulo = Titulo

sAutor = Autor

End Sub

Public Overrides Function ToString() As String

Return String.Format(“{0} escribió {1}.”, sAutor, sTitulo)

End Function

End Class

En el módulo principal codificamos nuestro método MostrarPorConsola que podrá recibir dos parámetros de cualquier tipo:

C#

static void MostrarPorConsola<T>(ref T a, ref T b)

{

Console.WriteLine(“Primer  argumento del método MostrarPorConsola(), {0}, de tipo {1}.”, a, typeof(T));

Console.WriteLine(“Segundo argumento del método MostrarPorConsola(), {0}, de tipo {1}.”, b, typeof(T));

}

VB:NET

Private Sub MostrarPorConsola(Of T)(ByRef a As T, ByRef b As T)

Console.WriteLine(“Primer  argumento del método MostrarPorConsola(), {0}, de tipo {1}.”, a, GetType(T))

Console.WriteLine(“Segundo argumento del método MostrarPorConsola(), {0}, de tipo {1}.”, b, GetType(T))

End Sub

El método imprime una línea con el primer argumento y otra más con el segundo, sin interesar de que tipo son.

En la rutina principal consumimos al método genérico, primero declaro dos enteros, les asigno un valor y se los paso como parámetros al método MostrarPorConsola, luego declaro dos objetos de tipo Libro, los hidrato y se los paso como parámetros al mismo método.

Parece mentira… pero funciona 🙂

A partir de la versión 2.0 del Framework .NET contamos con el namespace Generics que define una cantidad de clases e interfaces que permiten administrar sub ítems en una variedad de contenedores. En este post me voy a dedicar al contenedor List<T> o List(Of T) según sea C# o VB.NET

Veamos un ejemplo de colección sin Generics. Con Visual Studio .NET, abrimos un nuevo proyecto de Consola, si queremos manipular un conjunto de objetos, por ejemplo Libros, en primer lugar tenemos que referenciar lo siguiente:

C#

using System;

using System.Collections;

VB.NET

Imports System

Imports System.Collections

y definir la clase Libro:

C#

public class Libro

{

public string sTitulo, sAutor;

public Libro(string Titulo, string Autor)

{

sTitulo = Titulo;

sAutor = Autor;

}

public override string ToString()

{

return string.Format(“{0} escribió {1}.”, sAutor, sTitulo);

}

}

VB.NET

Public Class Libro

Dim sTitulo, sAutor As String

Public Sub New(ByVal Titulo As String, ByVal Autor As String)

MyBase.New()

sTitulo = Titulo

sAutor = Autor

End Sub

Public Overrides Function ToString() As String

Return String.Format(“{0} escribió {1}.”, sAutor, sTitulo)

End Function

End Class

Además, necesitamos programar otra clase, por ejemplo BibliotecaCollection que implemente la intefaz IEnumerable. En nuestro caso le agregaremos un método AgregarLibro que recibe un parámetro de tipo Libro y lo agrega a un ArrayList que la clase encapsula.

El tipo ArrayList en el namespace Collection, es el equivalente al tipo List<T> o List(Of T) del namespace Generics

C#

public class BibliotecaCollection : IEnumerable

{

private ArrayList arBiblioteca = new ArrayList();

public BibliotecaCollection() { }

public void AgregarLibro(Libro pLibro)

{

arBiblioteca.Add(pLibro);

}

IEnumerator IEnumerable.GetEnumerator()

{

return arBiblioteca.GetEnumerator();

}

}

VB.NET

Public Class BibliotecaCollection

Implements IEnumerable

Private arBiblioteca As ArrayList = New ArrayList

Public Sub New()

MyBase.New()

End Sub

Public Sub AgregarLibro(ByVal pLibro As Libro)

arBiblioteca.Add(pLibro)

End Sub

Function IEnumerable_GetEnumerator() As IEnumerator Implements IEnumerable.GetEnumerator

Return arBiblioteca.GetEnumerator

End Function

End Class

Cualquier otro tipo de función que queramos que soporte la clase, debemos codificarla. Para este ejemplo sólo vamos a dejar definida la función AgregarLibro.

Ahora si podemos hacer uso de nuestra biblioteca en una aplicación de consola:

C#

class Program

{

static void Main(string[] args)

{

Console.WriteLine(“Collections\n”);

BibliotecaCollection miBiblioteca = new BibliotecaCollection();

miBiblioteca.AgregarLibro(new Libro(“Trópico de Cáncer”, “Henry Miller”));

miBiblioteca.AgregarLibro(new Libro(“Yonqui”, “William Burrougs”));

miBiblioteca.AgregarLibro(new Libro(“Esperando a Godot”, “Samuel Beckett”));

miBiblioteca.AgregarLibro(new Libro(“La senda del perdedor”, “Charles Bukowski”));

miBiblioteca.AgregarLibro(new Libro(“En el camino”, “Jacques Kerouac”));

foreach (Libro pLibro in miBiblioteca)

Console.WriteLine(pLibro);

Console.ReadLine();

}

}

VB.NET

Module Module1

Sub Main()

Console.WriteLine(“Collections”)

Dim miBiblioteca As BibliotecaCollection = New BibliotecaCollection

miBiblioteca.AgregarLibro(New Libro(“Trópico de Cáncer”, “Henry Miller”))

miBiblioteca.AgregarLibro(New Libro(“Yonqui”, “William Burrougs”))

miBiblioteca.AgregarLibro(New Libro(“Esperando a Godot”, “Samuel Beckett”))

miBiblioteca.AgregarLibro(New Libro(“La senda del perdedor”, “Charles Bukowski”))

miBiblioteca.AgregarLibro(New Libro(“En el camino”, “Jacques Kerouac”))

For Each pLibro As Libro In miBiblioteca

Console.WriteLine(pLibro)

Next

Console.ReadLine()

End Sub

End Module

Ahora, con Generics, no necesitamos de la clase BibliotecaCollection, ni implementar la interfaz IEnumerable, ni el método adicional AgregarLibro.

Sólo necesitamos declarar una lista genérica de Libros de la siguiente forma:

C#

List<Libro> miBiblioteca = new List<Libro>();

VB.NET

Dim miBiblioteca As List(Of Libro) = New List(Of Libro)

Tenemos que modificar levemente las referencias a:

C#

using System;

using System.Collections.Generic;

VB.NET

Imports System

Imports System.Collections.Generic

Y con la misma declaración de la clase Libro que tenemos más arriba usamos nuestra lista genérica de Libros de esta forma:

C#

class Program

{

static void Main(string[] args)

{

Console.WriteLine(“Generics\n”);

List<Libro> miBiblioteca = new List<Libro>();

miBiblioteca.Add(new Libro(“Trópico de Cáncer”, “Henry Miller”));

miBiblioteca.Add(new Libro(“Yonqui”, “William Burrougs”));

miBiblioteca.Add(new Libro(“Esperando a Godot”, “Samuel Beckett”));

miBiblioteca.Add(new Libro(“La senda del perdedor”, “Charles Bukowski”));

miBiblioteca.Add(new Libro(“En el camino”, “Jacques Kerouac”));

foreach (Libro pLibro in miBiblioteca)

Console.WriteLine(pLibro);

Console.ReadLine();

}

}

VB.NET

Module Module1

Sub Main()

Console.WriteLine(“Generics”)

Dim miBiblioteca As List(Of Libro) = New List(Of Libro)

miBiblioteca.Add(New Libro(“Trópico de Cáncer”, “Henry Miller”))

miBiblioteca.Add(New Libro(“Yonqui”, “William Burrougs”))

miBiblioteca.Add(New Libro(“Esperando a Godot”, “Samuel Beckett”))

miBiblioteca.Add(New Libro(“La senda del perdedor”, “Charles Bukowski”))

miBiblioteca.Add(New Libro(“En el camino”, “Jacques Kerouac”))

For Each pLibro As Libro In miBiblioteca

Console.WriteLine(pLibro)

Next

Console.ReadLine()

End Sub

End Module

Como vemos en el namespace Generics se resuelve todo lo que antes debíamos codificar en una clase adicional, en este ejemplo BibliotecaCollection.

Los otros contenedores del namespace que no cubro en este post son:

Collection<T>, la base de una colección genérica,

Comparer<T>, Comparación de igualdad entre dos objetos genéricos,

Dictionary<K, V>, colección genérica de pares nombre/valor,

Queue<T>, implementación genérica de lista FIFO (first-in, first-out),

SortedDictionary<K, V>, implementación genérica de un conjunto ordenado de pares nombre/valor,

Stack<T>, implementación genérica de lista LIFO (last-in, first-out),

LinkedList<T>, implementación genérica de una lista doblemente vinculada,

ReadOnlyCollection<T>, implementación de un conjunto de items genéricos de sólo lectura.

En fin, contamos con una nueva serie de elementos para aprovechar la potencia del Framework.

Siguiendo con la serie de posts sobre Cooperator Framework, esta vez voy a mostrarles cómo codificar un ABM de lo más simple.

Seguramente van a encontrar muchas formas de mejorar el ejemplo. La idea es brindar un punto de partida.

Para los que todavía no conocen Cooperator Framework les recomiendo los posts anteriores sobre el tema:

Introducción a Cooperator Framework,

Cooperator, un ejemplo sencillo para comenzar y

Cooperator, modificando el modelo.

Supongamos que tenemos una entidad en nuestro dominio denominada Occupations, con un ID autonumérico y un campo Descripcion de tipo VARCHAR.

Vamos a tener un formulario Occupations que contiene una DataGridView, de nombre dgDatos, con la lista de Occupations y un formulario Occupation con el que veremos el detalle de la Occupation al agregar o modificar un registro.

En el formulario Occupations tendremos una botonera y/o menú con las opciones de Agregar, Eliminar y Modificar.

La grilla de Occupations la completo con el siguiente código:

C#

OccupationList mOccupationList = OccupationMapper.Instance().GetAll;

OccupationListView mOccupationListView = new OccupationListView(mOccupationList);

mOccupationListView.Sort(“Description”, true);

dgDatos.DataSource = mOccupationListView;

dgDatos.Columns[ColumnaGrilla.ID].Visible = false;

dgDatos.Columns[ColumnaGrilla.Description].HeaderText = “Ocupación”;

VB.NET

Dim mOccupationList As OccupationList = OccupationMapper.Instance().GetAll

Dim mOccupationListView As OccupationListView = New OccupationListView(mOccupationList)

mOccupationListView.Sort(“Description”, True)

dgDatos.DataSource = mOccupationListView

dgDatos.Columns(ColumnaGrilla.ID).Visible = False

dgDatos.Columns(ColumnaGrilla.Description).HeaderText = “Ocupación”

En la primera linea declaro una lista que completo con el método GetAll del Mapper de Occupation. Luego completo una ListView a partir de la lista que complete con el GetAll. En la tercera linea le practico un ordenamiento, en la cuarta linea le paso este ListView al DataSource de la DataGridView con lo que queda poblada. En la quinta linea hago invisible la columna del ID y en la última linea de código le doy un título a la columna Descripcion.

Veamos ahora el código correspondiente a Eliminar:

C#

if ((dgDatos.SelectedRows == null))

{

MsgBox(“No se seleccionó ninguna fila de la grilla.”, ((MsgBoxStyle)((MsgBoxStyle.Exclamation + MsgBoxStyle.OkOnly))));

}

else if ((MsgBox((“¿Está a punto de eliminar a la Ocupación “

+ (dgDatos.SelectedRows.Item[0].Cells[ColumnaGrilla.Description].Value.ToString.Trim + “?”)), ((MsgBoxStyle)((MsgBoxStyle.Exclamation + (MsgBoxStyle.YesNo + MsgBoxStyle.DefaultButton2))))) == MsgBoxResult.Yes))

{

OccupationMapper.Instance.Delete(((int)(dgDatos.SelectedRows.Item[0].Cells[ColumnaGrilla.ID].Value)));

MsgBox((“La Ocupación ” + (dgDatos.SelectedRows.Item[0].Cells[ColumnaGrilla.Description].Value.ToString.Trim + ” se eliminó exitosamente.”)), ((MsgBoxStyle)((MsgBoxStyle.Information + MsgBoxStyle.OkOnly))));

CargarGrilla();

}

VB.NET

If dgDatos.SelectedRows Is Nothing Then

MsgBox(“No se seleccionó ninguna fila de la grilla.”, CType(MsgBoxStyle.Exclamation + MsgBoxStyle.OkOnly, MsgBoxStyle))

Else

If MsgBox(“¿Está a punto de eliminar a la Ocupación “ + dgDatos.SelectedRows.Item(0).Cells(ColumnaGrilla.Description).Value.ToString.Trim + “?”, CType(MsgBoxStyle.Exclamation + MsgBoxStyle.YesNo + MsgBoxStyle.DefaultButton2, MsgBoxStyle)) = MsgBoxResult.Yes Then

OccupationMapper.Instance.Delete(CType(dgDatos.SelectedRows.Item(0).Cells(ColumnaGrilla.ID).Value, Integer))

MsgBox(“La Ocupación “ + dgDatos.SelectedRows.Item(0).Cells(ColumnaGrilla.Description).Value.ToString.Trim + ” se eliminó exitosamente.”, CType(MsgBoxStyle.Information + MsgBoxStyle.OkOnly, MsgBoxStyle))

CargarGrilla()

End If

End If

ColumnaGrilla es un Enum donde ColumnaGrilla.ID es igual a cero y ColumnaGrilla.Descripcion es igual a uno.

CargarGrilla es el primer bloque de código del post.

Ahora les muestro el código que corresponde a Agregar:

C#

Occupation miOccupation = new Occupation();

miOccupation.ShowDialog(this);

if (!miOccupation.Cancelo)

{

CargarGrilla();

}

miOccupation = null;

VB.NET

Dim miOccupation As New Occupation

miOccupation.ShowDialog(Me)

If Not miOccupation.Cancelo Then

CargarGrilla()

End If

miOccupation = Nothing

Y lo correspondiente a Modificar

C#

int intPivot;

if ((dgDatos.SelectedRows == null))

{

MsgBox(“No se seleccionó ninguna fila de la grilla.”, ((MsgBoxStyle)((MsgBoxStyle.Exclamation + MsgBoxStyle.OkOnly))));

}

else

{

CoopRules.Entities.Occupation mOccupation = new CoopRules.Entities.Occupation();

intPivot = ((int)(dgDatos.SelectedRows.Item[0].Index));

mOccupation = OccupationMapper.Instance.GetOne(((int)(dgDatos.SelectedRows.Item[0].Cells[ColumnaGrilla.ID].Value)));

Occupation miOccupation = new Occupation(mOccupation);

miOccupation.ShowDialog(this);

if (!miOccupation.Cancelo)

{

CargarGrilla();

dgDatos.Rows.Item[intPivot].Selected = true;

dgDatos.CurrentCell = dgDatos.Rows[intPivot].Cells[ColumnaGrilla.Description];

MsgBox(“La modificación se registró exitosamente.”, ((MsgBoxStyle)((MsgBoxStyle.Information + MsgBoxStyle.OkOnly))));

}

miOccupation = null;

}

VB.NET

Dim intPivot As Integer

If dgDatos.SelectedRows Is Nothing Then

MsgBox(“No se seleccionó ninguna fila de la grilla.”, CType(MsgBoxStyle.Exclamation + MsgBoxStyle.OkOnly, MsgBoxStyle))

Else

Dim mOccupation As New CoopRules.Entities.Occupation

intPivot = CType(dgDatos.SelectedRows.Item(0).Index, Integer)

mOccupation = OccupationMapper.Instance.GetOne(CType(dgDatos.SelectedRows.Item(0).Cells(ColumnaGrilla.ID).Value, Integer))

Dim miOccupation As New Occupation(mOccupation)

miOccupation.ShowDialog(Me)

If Not miOccupation.Cancelo Then

CargarGrilla()

dgDatos.Rows.Item(intPivot).Selected = True

dgDatos.CurrentCell = dgDatos.Rows(intPivot).Cells(ColumnaGrilla.Description)

MsgBox(“La modificación se registró exitosamente.”, CType(MsgBoxStyle.Information + MsgBoxStyle.OkOnly, MsgBoxStyle))

End If

miOccupation = Nothing

End If

Acá llamo la atención especialmente en la diferencia en la invocación al constructor del formulario Occupation entre Agregar y Modificar

C#

Agregar

Occupation miOccupation = new Occupation();

Modificar

Occupation miOccupation = new Occupation(mOccupation);

VB.NET

Agregar

Dim miOccupation As New Occupation

Modificar

Dim miOccupation As New Occupation(mOccupation)

Podemos observar que en el caso de Modificar al constructor le pasamos un Objeto Entity de tipo Occupation y en Agregar no.

Esto es porque el constructor del formulario Occupation tiene un parámetro opcional de tipo Occupation.

Les muestro el código del formulario Occupation:

C#

using CoopRules.Entities;

using CoopRules.Objects;

using CoopRules.Mappers;

using CoopCore.Core;

public class Occupation {

private bool mCancelo = true;

private CoopRules.Objects.OccupationObject mOccupation = new CoopRules.Objects.OccupationObject();

private CoopCore.Core.Estado mEstadoActual;

public Occupation(CoopRules.Entities.Occupation pOccupation = Nothing) {

InitializeComponent();

if ((pOccupation == null)) {

this.EstadoActual = CoopCore.Core.Estado.Agregar;

}

else {

mOccupation = pOccupation;

txtDescription.Text = pOccupation.Description;

this.Text = pOccupation.Description;

this.EstadoActual = CoopCore.Core.Estado.Modificar;

}

txtDescription.Focus();

}

public CoopCore.Core.Estado EstadoActual {

get {

return mEstadoActual;

}

set {

mEstadoActual = value;

}

}

bool Cancelo {

get {

return mCancelo;

}

set {

mCancelo = value;

}

}

private void Occupation_Load(object sender, System.EventArgs e) {

}

private void btnCancelar_Click(object sender, System.EventArgs e) {

this.Close();

}

private void btnAceptar_Click(object sender, System.EventArgs e) {

try {

Validar();

Grabar();

}

catch (Exception ex) {

if (ex.Message.Contains(“Cannot insert duplicate key row”)) {

MsgBox(“Ya existe una Ocupación con ese nombre.”, MsgBoxStyle.OkOnly, NombreEmpresa);

}

else {

MsgBox(ex.Message.ToString, MsgBoxStyle.OkOnly, NombreEmpresa);

}

}

}

private void BlanquearControles() {

txtDescription.Text = “”;

}

private void Validar() {

mOccupation.Description = txtDescription.Text;

mOccupation.Validate();

}

private void Grabar() {

if ((this.EstadoActual == CoopCore.Core.Estado.Agregar)) {

CoopRules.Entities.Occupation eOccupation = new CoopRules.Entities.Occupation();

eOccupation.Description = mOccupation.Description;

OccupationMapper.Instance.Insert(eOccupation);

this.Cancelo = false;

MsgBox(“La Ocupación se agregó exitosamente.”, MsgBoxStyle.OkOnly);

BlanquearControles();

txtDescription.Focus();

}

else {

OccupationMapper.Instance.Save(mOccupation);

this.Cancelo = false;

this.Close();

}

}

}

VB.NET

Imports CoopRules.Entities

Imports CoopRules.Objects

Imports CoopRules.Mappers

Imports CoopCore.Core

Public Class Occupation

#Region “Declaraciones Privadas”

Private mCancelo As Boolean = True

Private mOccupation As New CoopRules.Objects.OccupationObject

Private mEstadoActual As CoopCore.Core.Estado

#End Region

#Region “Eventos”

Public Sub New(Optional ByVal pOccupation As CoopRules.Entities.Occupation = Nothing)

InitializeComponent()

If pOccupation Is Nothing Then

Me.EstadoActual = CoopCore.Core.Estado.Agregar

Else

mOccupation = pOccupation

txtDescription.Text = pOccupation.Description

Me.Text = pOccupation.Description

Me.EstadoActual = CoopCore.Core.Estado.Modificar

End If

txtDescription.Focus()

End Sub

Private Sub Occupation_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load

End Sub

Private Sub btnCancelar_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCancelar.Click

Me.Close()

End Sub

Private Sub btnAceptar_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnAceptar.Click

Try

Validar()

Grabar()

Catch ex As Exception

If ex.Message.Contains(“Cannot insert duplicate key row”) Then

MsgBox(“Ya existe una Ocupación con ese nombre.”, MsgBoxStyle.OkOnly, NombreEmpresa)

Else

MsgBox(ex.Message.ToString, MsgBoxStyle.OkOnly, NombreEmpresa)

End If

End Try

End Sub

#End Region

#Region “Propiedades”

Public Property EstadoActual() As CoopCore.Core.Estado

Get

Return mEstadoActual

End Get

Set(ByVal value As CoopCore.Core.Estado)

mEstadoActual = value

End Set

End Property

Property Cancelo() As Boolean

Get

Return mCancelo

End Get

Set(ByVal value As Boolean)

mCancelo = value

End Set

End Property

#End Region

#Region “Rutinas y Funciones Privadas”

Private Sub BlanquearControles()

txtDescription.Text = “”

End Sub

Private Sub Validar()

mOccupation.Description = txtDescription.Text

mOccupation.Validate()

End Sub

Private Sub Grabar()

If Me.EstadoActual = CoopCore.Core.Estado.Agregar Then

Dim eOccupation As New CoopRules.Entities.Occupation

eOccupation.Description = mOccupation.Description

OccupationMapper.Instance.Insert(eOccupation)

Me.Cancelo = False

MsgBox(“La Ocupación se agregó exitosamente.”, MsgBoxStyle.OkOnly)

BlanquearControles()

txtDescription.Focus()

Else

OccupationMapper.Instance.Save(mOccupation)

Me.Cancelo = False

Me.Close()

End If

End Sub

#End Region

End Class

Espero les sea útil. 🙂

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

  • 135,199 hits

Carlos Marcelo Santos

Mejor calificado

del.icio.us