You are currently browsing the category archive for the ‘Software Development’ category.
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.
Este post es un intento de acercar a ustedes los lineamientos principales que Kent Beck desarrolla en su texto “Kent Beck, Extreme Programming Explained, Embrace Change” con el firme interés de que se animen a completar su lectura desde la fuente misma. Así, expongo una breve descripción de los elementos que juegan en este marco, conformando la metodología: Valores, Principios y Prácticas, cerrando con los Roles que el autor identifica.
Valores
Son conceptos universales, idealmente se aplican en todos los ámbitos. Es el camino para conducir el propósito a la práctica.
Communication
Es un factor importante para crear sentido de equipo y lograr cooperación efectiva. Cuando surgen problemas sorpresivos puede ayudar a resolverlos.
La mayor parte de los problemas son causados por carencia de conocimiento o por carencia de comunicación.
Si el problema tiene origen en la falta de conocimiento, no hay nada que pueda hacerse de antemano.
Cuando nos encontremos con un problema, preguntémonos si puede ser causado por una carencia de comunicación. Si es así, analicemos ¿Qué tipo de comunicación necesitamos para abordar el problema? ¿Qué comunicación necesitamos para mantenernos fuera de esta situación en el futuro?
Simplicity
Es el valor más intensamente intelectual. Se trata de pensar las cosas de forma de eliminar toda complejidad innecesaria.
Feedback
Los cambios son inevitables y crean la necesidad del Feedback.
Personalmente entiendo que es común el escenario en el cual la coyuntura nos condiciona a implementar funcionalidad de forma imperfecta en uno o varios aspectos. Esta situación crea una fuerte necesidad de Feedback, de otra forma se asume el riesgo de perpetuar esa imperfección y quizá ya nadie recuerde su existencia.
Existen fundamentalmente tres motivos que dificultan hacer las cosas bien al primer intento:
· Al resolver un problema por primera vez, puede haber más de una solución que funcione o que la solución no sea totalmente clara,
· Lo que es bueno hoy puede no serlo mañana,
· Hacer algo con absoluta corrección podría insumir tanto tiempo que las circunstancias cambiantes futuras invaliden la solución de hoy, antes que se finalice.
Lograr satisfacción en un esquema de mejoras a cambio de, esperar la perfección al instante, es posible sólo acudiendo al Feedback, que puede producirse de varias formas:
· Escuchar distintas opiniones sobre una idea,
· Como se ve el código una vez implementada la idea,
· Si los tests fueron fáciles de escribir,
· Si los tests ejecutaron,
· Cómo la idea funciona una vez que se hizo el deploy.
Un equipo debe esforzarse en generar tanto Feedback como pueda, tan rápidamente como le sea posible. Tan pronto como se conoce la situación, tan pronto podremos adaptarnos.
Courage
Es la acción efectiva de enfrentar el temor.
A veces el coraje se manifiesta en forma de paciencia. Cuando uno sabe que existe un problema pero no lo conoce, se requiere coraje para esperar que se manifieste distintivamente.
Hay que tener en cuenta que el coraje como valor principal, sin valores que contrabalanceen, puede ser peligroso, incluso puede jugar contra el espíritu del equipo. Pero cuando el coraje juega en confluencia con los otros valores es una herramienta poderosa.
Respect
Este es el valor que subyace a los otros cuatro. XP funciona en un marco dónde cada integrante del equipo considera a cada otro y lo que está haciendo. La contribución de cada persona necesita respeto.
Others
Safety
Security
Predictability
Quality of Life
Principios
Existe gran distancia entre los Valores y las Prácticas, los Principios son la herramienta para sortear esa distancia y nos permitirán aplicar las Prácticas en armonía con los Valores.
Humanity
¿Qué es lo que necesita, en términos humanísticos, una persona para ser un buen desarrollador?
· Satisfacer necesidades básicas,
· Alcanzar logros, sentir que se contribuye en la sociedad,
· Sentido de pertenencia, identificación con el grupo, contribuir para alcanzar metas compartidas,
· Crecimiento personal y profesional, expansión de habilidades y perspectivas,
· Intimidad
Entonces parte del desafío es balancear las necesidades individuales con las del equipo.
Economics
Existen dos aspectos económicos que afectan al desarrollo de software,
· Time Value of Money. El desarrollo de software es más valorado cuando ahorra dinero más tempranamente y cuando comienza a gastarlo tan tarde como sea posible.
· Option Value of Systems and Teams. Las prácticas están destinadas a mejorar el valor tanto del software como del equipo, teniendo en cuenta el Time Value of Money, por ejemplo, para invertir o no en flexibilidad especulativa.
Mutual Benefit
Toda actividad debe beneficiar a todos los involucrados. Este es un principio muy difícil de adherir.
Siempre existen soluciones para cualquier problema que implica un costo para una persona mientras que otra se beneficia. Cuando la situación es desesperante estas soluciones se vuelven atractivas. Estos escenarios deterioran las relaciones.
Existen vías de beneficio mutuo que resuelven problemas a futuro:
· Escribir test automáticos. Ayudan a diseñar, integrar e implementar en el presente. Se mantienen los test para futuros programadores garantizando la escalabilidad del producto.
· Refactoring cuidadoso que remueva complejidad accidental. Satisface al autor, mejora su producto y deja el código más entendible para quien lo tome en un futuro.
· Seleccionar nombres partiendo de un coherente y explícito conjunto de metáforas que agilice el desarrollo y haga al código más claro para nuevos programadores.
Beneficio mutuo en XP es beneficiarme hoy, más tarde y también a mi cliente.
Self-Similarity
Cuando la naturaleza encuentra una forma que funciona, la usará siempre que pueda. Esto mismo se debe aplicar al desarrollo de software. Se debe tender a copiar la estructura exitosa de una solución a otro contexto, aún en diferentes escalas.
Improvement
El punto en XP es que la excelencia en software se alcanza a través de mejoras. El ciclo es hacer lo mejor posible hoy, esforzándose en conocer y entender para hacerlo mejor en el futuro.
La historia de la tecnología del desarrollo de software nos muestra una eliminación gradual del malgasto de esfuerzos.
Diversity
Los equipos necesitan reunir una variedad de habilidades, actitudes y perspectivas, para detectar problemas y trampas, recorrer múltiples caminos, resolver un problema, seleccionar e implementar la solución.
Inevitablemente surgen conflictos de la diversidad, conflictos del tipo “tengo varios caminos para resolver esto”, entonces el principio de la diversidad se nos debe presentar como una oportunidad y no un problema.
El desafío acá es cómo el equipo transita el conflicto y como suaviza su comunicación en momentos de stress.
Reflection
Los buenos equipos no sólo hacen su trabajo, también piensan cómo están trabajando y porqué están trabajando. Analizan porqué son exitosos o porqué fallaron.
Los ciclos del equipo, deben incluir tiempos de reflexión. Además deben generarse espacios informales de reflexión incluso aún con personas ajenas al ámbito del desarrollo de software. Esto permite alcanzar distintos enfoques para reflexionar acerca del porqué estamos trabajando en la forma en que lo hacemos.
Para maximizar el Feedback, la reflexión, en equipos XP, está en conjunto con el hacer.
Flow
Las prácticas de XP tienden hacia un flujo continuo de actividades, más que hacia fases discretas.
El desarrollo de software por mucho tiempo entregó valor en grandes módulos. Hay equipos que empeoran todo tendiendo a responder bajo stress haciendo estos módulos de valor cada vez más grandes, haciendo implementaciones cada vez menos frecuentes e integrando menos a menudo. En estas circunstancias es común que decaiga el Feedback con la consecuente toma de riesgo.
En oposición el principio Flow sugiere implementar pequeños incrementos de valor cada vez más frecuentemente.
Opportunity
Hay que aprender a ver los problemas como oportunidades para cambiar.
Esto no significa que no existan problemas en este ámbito. Parte de ser extremo es transformar el problema en una oportunidad de crecimiento personal, para profundizar relaciones y mejorar el software.
Redundancy
Los problemas críticos y difíciles en el desarrollo de software deberían ser resueltos de diversas formas. Entonces si una solución falla completamente, otra solución prevendrá el desastre.
Los defectos son un problema crítico y difícil y son abordados en XP por distintas prácticas, algunas de ellas podrían ser redundantes y este costo se asume porque la experiencia indica que no se resuelven los defectos con una simple práctica.
Failure
Cuando no sepamos, en cual de varias formas disponibles, implementar una Story, intentémoslo de todas estas formas simultáneamente. Aún cuando todas fallen, habremos aprendido algo valorable.
Una falla ¿es un malgasto?… No, si imparte conocimiento.
Suele suceder que al exponerse un conflicto se pierda demasiado tiempo discutiendo acerca de las características de cada implementación propuesta. Es recomendable que se limite el tiempo de discusión y si este se agota, el equipo deberá organizarse para implementar algo con cada una de las alternativas que queden en condiciones de competir después de la discusión, que podría durar entre quince minutos y media hora.
Quality
La calidad no debe ser una variable de control. Un proyecto no avanza más rápido al aceptar disminuir su calidad. Ni se avanza más lentamente demandando más alta calidad.
Elevar el estándar de calidad, a menudo resulta en entregas más rápidas. Mientras que una disminución de la calidad deriva en entregas tardías cada vez menos predecibles.
Baby Steps
Los cambios deben darse en pequeños pasos.
Siempre es tentador hacer grandes cambios en grandes pasos. Entonces la pregunta que Beck nos recomienda es “Cuál es el mínimo cambio que podrías hacer, que sea reconocible, en la dirección correcta”.
Accepted Responsibility
La responsabilidad no sólo puede ser asignada, también debe ser aceptada. La práctica de la responsabilidad aceptada nos sugiere que:
· Si una persona toma una tarea, es esa misma persona quien debería estimarla,
· La persona responsable de implementar una Story también es responsable de su diseño y testing.
Practices
Cómo aplicar las Prácticas en su contexto puede no ser obvio. Las Prácticas son dependientes de la situación. Las podemos ver como un vector desde donde estamos hasta donde podemos estar con XP.
Su aplicación es una elección. Las Prácticas Primarias son útiles independientemente de que se esté haciendo. Las Prácticas Corolarias son difíciles de aplicar sin antes tener una buena experiencia en las Prácticas Primarias.
Primary Practices
Sit Together
Se recomienda desarrollar en un espacio abierto lo suficientemente grande como para contener al equipo completo, donde cada integrante posea su espacio de privacidad.
Es importante que quienes conformen el equipo se sienten juntos y puedan establecer contacto visual con facilidad.
Esto no siempre es posible, entonces se debe recurrir a otros espacios que puedan frecuentarse regularmente. Hay que tener en cuenta que si el equipo está disperso y se presentan problemas es importante reunirse más a menudo, en la forma en que se pueda.
Whole Team
Hay que reunir en el equipo todas las habilidades y perspectivas necesarias para que el desarrollo sea exitoso.
Lo que consituye un Whole Team es dinámico, a mi criterio, de acuerdo fundamentalmente a, como se desarrolle la vida del producto, la evolución de la tecnología, el crecimiento del equipo y el desarrollo individual de los integrantes del mismo.
Informative Workspace
Armar un Workspace sobre nuestro trabajo. Un observador interesado que se mueva en el espacio del equipo debería, en quince segundos, tener una idea aproximada de cómo marchan las cosas.
Muchos equipos implementan esta práctica usando Story Cards.
Energized Work
Hay que trabajar solo las horas que se pueda ser productivo.
El desarrollo de software es un juego de insight y el este se produce en mentes preparadas, descansadas y relajadas.
Cuando se está muy cansado es muy fácil disipar valor en un proyecto de software.
Pair Programming
Escribir todos los programas de producción con dos personas sentadas en una misma máquina. Estas personas que programan juntas, también van a analizar, diseñar, testear en Pair Programming, intentando programar mejor.
Pair Programming permite:
· Mantener mutuamente en su tarea,
· Aclarar ideas,
· Tomar la iniciativa cuando el partner queda estancado, esto disminuye la frustración,
· Establecer responsabilidades mutuas acerca de las Prácticas del equipo.
La mayoría de los programadores no puede practicar pairing más que cinco o seis horas diarias. Es una buena idea establecer pausas y rotar los pares frecuentemente, por ejemplo, cada dos horas. Hay equipos que rotan cada hora valiéndose de un timer y algunos llegan a rotar cada treinta minutos cuando tiene que resolver algún problema difícil.
Stories
Planificar Stories usando unidades funcionales visibles para el cliente. Tan pronto como la historia se escribe hay que estimar el esfuerzo de desarrollo que representará implementarlo.
Las Stories deben tener un título corto y una pequeña descripción.
Una característica de XP es que las Stories se escriben temprano, esto da al equipo tiempo para pensar en cómo obtener el mejor resultado con la menor inversión.
Weekly Cycle
Planear el trabajo una vez a la semana. Al comenzar la semana se debe celebrar una reunión donde:
· Se revisan los progresos realizados a la fecha,
· Lograr que el cliente escoja Stories que insuman una semana,
· Descomponer las Stories en Tasks.
La semana comienza escribiendo los tests automáticos que deberían correr una vez que se implementen las Stories. El resto de la semana transcurre completando las Stories y haciendo correr los tests. El trabajo verdaderamente termina con la implementación de las Stories, no se queda en los tests.
Las Stories se deben dividir en Tasks de modo tal que haya un responsable por ellas y sea él mismo quien la estime.
Quarterly Cycle
Planificar el trabajo de a trimestres, como ritmo de observación de avance del proyecto y su alineación con metas de largo alcance.
En estos trimestres:
· Se identifican cuellos de botella, especialmente aquellos que están fuera del alcance del equipo,
· Iniciar reparaciones,
· Planificar el Theme o Themes del Quarter,
· Seleccionar para el Quarter aquellas Stories que se encaminen al Theme o Themes,
· Focalizar en la gran foto, donde el proyecto se adecúa a la organización.
Las estaciones del año son una escala de tiempo natural y ampliamente aceptada.
El tratamiento en términos de Theme y Story es para contener la tendencia de los equipos en focalizar en los detalles de lo que están haciendo sin considerar cómo estas Stories se adaptan a la organización en la gran foto.
Slack
Siempre se pueden agregar Stories y entregar más de lo que se prometió. Cuando sea posible, es recomendable comprometerse con objetivos modestos. Esto tiende a bajar la tasa de generación de bugs.
Ten Minute Build
El objetivo es que en diez minutos se ejecute el Build del sistema completo y se ejecuten todos los tests. Si este proceso lleva más de diez minutos, debe hacerse de todos modos, quizá menos a menudo.
Cuando este proceso es automático, hay riesgos que se eliminan. Si el proceso es manual hay que ser muy estricto en practicar un Build de todo el sistema y la ejecución de todos los tests. Cuando se compila sólo lo que se modificó y se ejecutan sólo los tests necesarios para cubrir estos cambios, se introduce el riesgo de cometer errores impredecibles.
Los Builds y Tests automáticos son más valorables que aquellos que requieran alguna intervención manual
Continuous Integration
Integrar y ejecutar tests como máximo cada dos horas. Programar en equipo no es el problema de dividir y conquistar, es más bien el problema de dividir, conquistar e integrar.
La integración es un paso impredecible y puede fácilmente tomar más tiempo que la misma programación de lo que se integra.
El estilo de Integración Contínua más común es el asíncrono, en el cual se envían los cambios al servidor, el sistema de Build se notifica acerca de estos cambios y ejecuta el Build completo y todos los Tests. Si hay algún problema se notifica por eMail, mensaje de texto o, recomienda el autor como most cooly, con una brillante lámpara roja.
Es preferible un modelo sincrónico, donde después de un episodio de Pair-Programming, de no más de dos horas, se envían los cambios y se espera el resultado del Build completo y la ejecución de todos los tests.
En un modelo sincrónico, esperar la compilación y los resultados del testing, es un tiempo natural dónde hablar y reflexionar en cómo mejorar lo hecho.
Los builds sincrónicos crean una presión positiva a favor de la definición clara de ciclos de Feedback cortos.
Hay que integrar y ejecutar el build de un producto completo. Si hay que quemar un CD, se quema un CD; si hay que implementar un sitio web, se implementa un sitio web.
Test-First Programming
Escribir un test automático que falle antes de modificar algo de código. Test-First Programming encara varios problemas a la vez.
· Scope Creep, Alcance Cambiante: Al declarar explícita y objetivamente que es lo que se espera que el programa haga, se logra enfocar más definidamente el trabajo. Y si realmente se quiere agregar algo, entonces hay que escribir otro test después de lograr que el primero funcione.
· Acoplamiento y Cohesión: Si es difícil escribir un test, es señal de un problema de diseño más que un problema del test. El código levemente acoplado y altamente cohesivo es fácil de testear.
· Confianza: Es difícil confiar en el autor de código que no funciona. Escribir código claro que funcione y demostrar las intenciones escribiendo tests automáticos logra establecer confianza entre los integrantes de un equipo.
· Ritmo: No es extraño perderse mucho tiempo mientras se está programando. Cuando se practica Test-First Programming queda claro qué es lo próximo que hay que hacer, o escribir otro test, o hacer que el test que falló funcione. Esto desarrolla un ritmo natural y eficiente, test, code, refactor, test, code, refactor…
Los tests que se escriben mientras se produce el código tienen la desventaja de tener una micro visión del sistema, cabe preguntarse, por ejemplo, si dos de estos objetos podrán trabajar bien juntos. A medida que se adquiera experiencia en el tema se estará en capacidad de acumular mayor seguridad con estos tests.
Incremental Design
Invertir en el diseño del sistema cada dia.
La estrategia opuesta, poner todo el diseño antes de implementar, produce software rígido frente a la necesidad de cambios, esto desencadena un crecimiento exponencial de los costos por cambios.
Los equipos XP trabajan para crear condiciones bajo las cuales el costo de modificar software no despegue catastróficamente.
La más económica estrategia de diseño es tomar las grandes decisiones de diseño tempranamente y diferir todas las decisiones de pequeña escala para más adelante.
El diseño incremental sugiere que el tiempo más efectivo para diseñar es a la luz de la experiencia.
Corollary Practices
El autor ve las siguientes Prácticas como difíciles o riesgosas de llevar a cabo sin implementar exitosamente las previas.
Real Customer Involvement
Hay que lograr que la gente cuyas vidas o negocios estén involucrados en el sistema a desarrollar sea parte del equipo. Clientes con buena visión del sistema pueden formar parte de la planificación trimestral y semanal.
La objeción que se escucha a esta Práctica es que si se hace todo lo que el cliente quiera el sistema no resultará adecuado para otros. Pero es más fácil generalizar un sistema exitoso que especializar un sistema que no resuelve el problema de nadie.
Incremental Deployment
Cuando se reemplace un sistema es recomendable tomar el control gradualmente empezando muy temprano en el proyecto. Los grandes Deployments involucran riesgos y altos costos humanos y económicos.
Team Continuity
Mantener efectivamente al equipo junto.
En grandes organizaciones existe la tendencia de administrar la gente como unidades de programación. El valor en software no sólo se construye por lo que la gente sabe y hace, sino también por sus relaciones y lo que logran juntos. Ignorar las relaciones y confianza sólo por simplificar un problema de agenda es una falsa economía.
En las pequeñas organizaciones este problema no existe ya que sólo se cuenta con un equipo.
Shrinking Teams
Cuando un equipo crece en capacidades hay que mantener su carga de trabajo y gradualmente reducir su tamaño. Con la gente liberada se forman nuevos equipos. Si algún equipo quedó muy pequeño se puede fusionar con otro pequeño equipo.
Root-Cause Analysis
Siempre que un defecto es encontrado después de un desarrollo, se debe eliminar el defecto y sus causas. El objetivo no sólo es que este defecto particular no se vuelva a repetir, sino que el equipo no vuelva a caer en este tipo de error otra vez.
En XP este es el proceso para responder frente a un defecto:
1. Escribir un test automático que reproduzca el error,
2. Escribir un Unit Test que reproduzca el error con el alcance más reducido posible,
3. Corregir el sistema hasta que el Unit Test funcione.
4. Una vez que el defecto esté resuelto, pensar en qué originó el defecto. Iniciar los cambios necesarios para evitar este tipo de defectos en el futuro.
Muchos equipos tienen demasiados bugs como para poner esta Práctica en uso.
Shared Code
Cualquiera en el equipo puede mejorar cualquier parte del sistema en cualquier momento.
Code and Tests
Mantener solo el código y los tests como artefactos permanentes.
Single Code Base
Tiene que haber un único código. Se puede trabajar con un branch temporario pero por no más de unas pocas horas.
Si se cuenta con múltiples versiones de código base, rápidamente hay que ponerse en plan de reducción gradual.
Daily Deployment
Poner en producción nuevas versiones de software cada fin del día. Esta práctica requiere que haya Builds automáticos. Las herramientas de Deploy también deben ser automáticas incluyendo la habilidad de hacer roll outs incrementales y roll backs en caso de fallas.
La tendencia a hacer deploys más frecuentes es clara. Los grandes sitios web cambian diariamente en forma imperceptible.
Negotiated Scope Contract
Los riesgos se reducen firmando una secuencia de contratos cortos en lugar de uno largo. Tendremos mejor margen para negociar alcances.
Pay-Per Use
En la modalidad Pay-Per Use se factura cada vez que el sistema es usado. Aun cuando no se pueda implementar Pay-Per Use se puede pensar en un sistema que permita un modelo de suscripción en el cual el software es comprado mensualmente o trimestralmente.
The Whole XP Team
Testers
Eligen, escriben tests y entrenan a los programadores en técnicas de testing. Colaboran en la definición de lo que constituirá un aceptable funcionamiento antes de la implementación.
Interactions Designers
Seleccionan metáforas que expresen el sistema, escriben Stories y evalúan el uso del sistema implementado. Los Interactions Designers trabajan con los clientes, ayudan a clarificar las Stories, deciden qué es lo próximo que el sistema hará y refinan la interfaz de usuario durante el ciclo de vida del producto.
Architects
Deciden y ejecutan refactorings de larga escala, escriben tests de stress de arquitectura, implementan Stories y particionan sistemas para su mejor desarrollo.
Project Managers
Facilitan la comunicación interna del equipo y coordina la comunicación con clientes, proveedores y con el resto de la organización. Es responsable de recordar los progresos al equipo.
Planeamiento en XP es una actividad, no una fase, los Project Managers se responsabilizan de mantener lo planeado en sincronismo con la realidad.
Product Managers
Escriben y seleccionan Stories y Themes en el Quarterly Cycle, responden ante implementaciones no cubiertas o sobre areas de Stories sub especificadas.
Cuando el equipo está desbordado, el Product Manager ayuda al equipo a fijar prioridades analizando la diferencia entre la situación actual y lo asumido anteriormente. Luego adapta la Storie o Theme a lo que realmente sucede.
Executives
Articular y mantener metas de largo alcance es la responsabilidad de los ejecutivos, sean sponsors o supervisores. También deben monitorear, animar y facilitar mejoras. Los ejecutivos cuentan con dos métricas para conocer el estado de salud de su equipo XP, una es la cantidad de errores encontrados después de la instalación, la segunda es el tiempo que transcurre desde que la organización comienza a invertir en una idea hasta que la idea produzca su primer ingreso.
Technical Writers
El rol de las publicaciones técnicas en un equipo XP es proveer un temprano Feedback sobre las características y crear relaciones cercanas con los usuarios.
Explicar un sistema en prosa e imágenes es una fuente de Feedback para el equipo.
Las publicaciones pueden tener cualquier forma, tutoriales, manuales de referencia, manuales técnicos, videos, audios, blogs, wikis, etc.
Los equipos XP deberían tener algún Feedback acerca del uso. Si se tiene la documentación en línea se puede monitorear su uso. Si los usuarios nunca visitan cierto tipo de documentación, se debe suspender su producción. Si la documentación está integrada al producto y el producto cuenta con un sistema de seguimiento de usabilidad, agregar a este seguimiento el uso de la documentación.
Users
En XP los usuarios escriben y seleccionan Stories y toman decisiones de dominio durante el desarrollo. Los usuarios son más valiosos cuando tienen amplios conocimientos y experiencia en sistemas similares, más aún si forman parte de alguna importante comunidad de usuarios.
Programmers
Los programadores estiman Stories y Tasks, descomponen Stories en Tasks, escriben tests y código, automatizan procesos de desarrollo y gradualmente mejoran el diseño del sistema.
Como los programadores necesitan trabajar en un esquema de cercana colaboración técnica, es importante que desarrollen habilidades sociales y comunicacionales.
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…,
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…
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. 🙂
Otra vez acá.
Basado en el ejemplo del post anterior, voy a introducir modificaciones al modelo original con la idea de ver que tan fácil es hacerlo y regenerar código.
Partimos entonces de la solución generada que pueden descargar desde acá.
Supongamos que ahora nos piden que incluyamos un campo Telefono para la tabla Sucursal.
Conviene agregar algunos registros en las tablas para facilitar la prueba.En primer lugar abrimos SQL Server Management Studio, buscamos en la base de datos Prueba y en la tabla Sucursal agregamos un campo Telefono de tipo VARCHAR(50), grabamos los cambios.
Luego abrimos nuestro modelo con el Modeler y pulsamos Refresh. Al desplegar Sucursal veremos lo siguiente:
Ahora hacemos click sobre Quick generation, al finalizar abrimos la solución con Visual Studio y ejecutamos.Podremos ver que la grilla ya contiene la columna Telefono y si cargaron algunos registros correctamente en las tablas, podrán ver los valores.
Seguimos pensando en otro tipo de modificación, por ejemplo una Asociación, supongamos que nos piden poder calificar las Sucursales por Provincia. Esto nos obliga a crear una tabla Provincia con un campo ID (PK, autonumérico e INT) y Descripcion (VARCHAR(50)). Para vincularla a la tabla Sucursal, agregamos en esta última un campo IDProvincia (FK e INT) luego establecemos la relación entre la PK de Provincia con la FK de Sucursal.
Hacemos un Refresh desde el Modeler,
Desplegamos Sucursal, chequeamos ProvinciaString y quitamos el check de ProvinciaEntity, click en Save model,
Ahora un Quick Generation y volvemos a la solución.
Veremos que se han producido una serie de erroes.
Para solucionarlo iremos primero al proyecto PruebaEntities, en la carpeta Objects agregamos la clase ProvinciaObject.vb y en la carpeta Object/Auto la clase ProvinciaObject.Auto.vb.
Luego en el proyecto PruebaData en la carpeta Gateways agregamos a ProvinciaGateway.vb y en Gateways/Auto la clase ProvinciaGateway.Auto.vb.
Finalmente en la carpeta Mappers la clase ProvinciaMapper.vb y en Mappers/Auto la clase ProvinciaMapper.Auto.vb
Si ejecutamos la solución, vamos a ver la columna Provincia incorporada a la grilla.
Supongamos que nos indican que cada Sucursal está asociada a un Depósito y cada una tiene uno propio. Podemos suponer esto como una Agregación, una relación uno a uno.
Importante: Tengamos en cuenta que para que una relación quede establecida como uno a uno en una base de datos, la PK y la FK deben ser exactamente iguales, mismos campos, mismos tipos, mismos nombres.
En nuestro ejemplo la PK de Sucursal la forman los campos ID e IDCliente (podría haber sido ID solamente) entonces vamos crear una tabla Deposito con estos dos campos un ID (PK, autonumérico, INT)y un campo Descripcion (VARCHAR(50)).
Luego debemos crear una relación uno a uno entre Sucursal y Deposito.
Volvemos al Modeler y hacemos un Refresh. Desplegamos Sucursal y vemos un nuevo elemento llamado DepositoAggregation, si intentamos chequearlo vamos a ver que en pocos segundos pierde el check. Esto sucede porque todavía no definimos su dependencia. Para eso hacemos click sobre el elemento Deposito, en la propiedad GenerateAsChildOf seleccionamos Sucursal. Ahora si podemos chequear sobre el elemento DepositoAggregation de Sucursal.
Ahora vamos a mirar el elemento Cliente, lo desplegamos y seleccionamos SucursalCollection, si vemos su propiedad GenerateAsType vale ObjectList, esto significa que la entidad Cliente, trae una colección de objetos. Como antes les conté, los objetos mapean uno a uno con las tablas de la base de datos, esto quiere decir que la agregación que recién incorporamos no es accesible desde la entidad Cliente.
Para que esto suceda hay que fijar la propiedad GenerateAsType de SucursalCollection con valor EntityList en la entidad Cliente.
Volvemos a hacer un Quick generation y abrimos la solución.
Vamos al proyecto PruebaEntities, en la carpeta Objects agregamos la clase DepositoObject.vb y en la carpeta Object/Auto la clase DepositoObject.Auto.vb. En la carpeta Entities agregamos la clase Deposito.vb y en la carpeta Entities/Auto la clase Deposito.Auto.vb
Luego en el proyecto PruebaData en la carpeta Gateways agregamos a DepositoGateway.vb y en Gateways/Auto la clase DepositoGateway.Auto.vb.
Finalmente en la carpeta Mappers la clase DepositoMapper.vb y en Mappers/Auto la clase DepositoMapper.Auto.vb
Si todo esto fue bien todavía nos va a quedar un error en la lista:
Value of type ‘PruebaRules.Entities.SucursalList’ cannot be converted to ‘PruebaRules.Objects.SucursalObjectList’.
Esto es en el método CargarGrillaSucursal del fromulario Form1. La linea que muestra el error es la siguiente:
Dim mSucursalList As SucursalObjectList = SucursalMapper.Instance.GetByCliente(CType(cboCliente.SelectedValue, Integer))
Esto es poruqe a la collección de Sucursales contenida en la entidad Cliente la cambiamos de ObjectList a EntityList. Corregida queda:
Dim mSucursalList As SucursalList = SucursalMapper.Instance.GetByCliente(CType(cboCliente.SelectedValue, Integer))
Cambiamos los tipos SucursalObjectList por tipos SucursalList.
Ahora tenemos un error en la siguiente linea:
Dim mSucursalListView As SucursalObjectListView = New SucursalObjectListView(mSucursalList)
Corregida queda:
Dim mSucursalListView As SucursalListView = New SucursalListView(mSucursalList)
Y esto es todo. Cuando lo ejecutemos en la grilla no vamos a ver automáticamente los campos de la agregación, pero van a notar que la agregacion ya es accesible.
Para terminar con esto incorporamos una Composición. Por ejemplo, nos cuentan que vamos a tener una lista de Contactos por cada Sucursal. Ya nos estamos imaginando una tabla Contacto con ID (PK, autonumérico, INT), IDSucursal (FK, INT), NombreApellido (VARCHAR(50)) y antes de establecer la relación entre Sucursal y Contacto, creamos un índice IsUnique sobre la columna ID
Ahora si podemos establecer la relación entre Contacto y Sucursal:
Volvemos una vez más al Modeler y hacemos Refresh.
Desplegamos Sucursal y vemos el nuevo elemento llamado ContactoCollection.
Antes de chequear ContactoCollection, debemos ir a la entidad Contacto a la propiedad GenerateAsChildOf la fijamos en el valor Sucursal. Ahora si, en la entidad Sucursal, chequemos ContactoCollection
Última vuelta al Modeler, Refresh y Quick generation, volvemos a la solución.
Vamos al proyecto PruebaEntities, en la carpeta Objects agregamos la clase ContactoObject.vb y en la carpeta Object/Auto la clase ContactoObject.Auto.vb.
Luego en el proyecto PruebaData en la carpeta Gateways agregamos a ContactoGateway.vb y en Gateways/Auto la clase ContactoGateway.Auto.vb.
Finalmente en la carpeta Mappers la clase ContactoMapper.vb y en Mappers/Auto la clase ContactoMapper.Auto.vb
Una vez hecho esto podemos acceder a un Contacto a través de la entidad Cliente, por ejemplo:
Dim mCliente As Cliente = ClienteMapper.Instance.GetOne(1)
mCliente.SucursalCollection.Item(2).ContactoCollection.Item(3).NombreApellido.Trim()
En la primera linea recuperamos el Cliente con ID igual a 1, en la segunda linea obtenemos el Nombre y Apellido del cuarto Contacto de la tercera Sucursal.
Repasando, con estos cuatro ejemplos que les presento vimos que las tablas pueden relacionarse de tres formas distintas en Cooperator, estas son:
- Asociación, en el Modeler se ve como xxxxEntity, representa una relación que califica a la entidad base, en nuestro ejemplo, Sucursal Provincia.
- Composición, en el Modeler se ve como xxxxCollection, es una relación 1 a n, hay que definir que la entidad colección sea definida como hija de la entidad base por medio de la propiedad GenerateAsChildOf.
- Agregación, en el Modeler se ve como xxxxAggregate, es una relación 1 a 1 (las PK y FK deben se exactamente iguales), hay que definir que la entidad agregación sea definida como hija de la entidad base por medio de la propiedad GenerateAsChildOf.
Ýa en el estribo, tengamos en cuenta que, eL Mapper graba la Entidad con todas sus Composiciones y Agregaciones, no así con sus Asociaciones. Si el ID de la Asociación, no existe el motor SQL devuelve un error de integridad.
Acá les dejo el script de la base de datos resultante, el archivo Cooperator y el código generado para que jueguen.
Ojalá les sea útil 🙂