lunes, diciembre 21, 2009

Compartir DataContext por cada HTTP Request

Trabajando en proyectos donde, por petición especifica del cliente, debo usar LinqToSql para mi acceso a datos. Donde además se me ha pedido usar las entidades generadas por LinqToSql  como mis entidades de negocio. Al abstraer mi acceso a datos con repositorios, Esto con la finalidad de poder hacer pruebas unitarias en mi capa de servicios, me encontré con la necesidad de compartir el DataContext a través de mis repositorios, en si lo que necesitaba era tener un solo DataContext por cada petición HTTP.
Para ello mis repositorios reciben en el constructor un IDataContextFactory la cual será la encargada de pasarle el datacontext a los repositorios
public interface IDataContextFactory<T> where T: DataContext, new()
{                                                                  
    T GetCurrentDataContext();                                     
    void DisposeCurrentDataContext();                              
}                                                                  

Así mis repositorios no necesitan saber de donde viene el datacontext. Aquí esta la implementación de esta interfaz para tener un datacontext por request

public class WebDataContextFactory<T>: IDataContextFactory<T> where T : DataContext, new()
{                                                                                         
    public T GetCurrentDataContext()                                                      
    {                                                                                     
        var dataContext = HttpContext.Current.Items[typeof(T)] as T;                      
        if (dataContext == null)                                                          
        {                                                                                 
            dataContext = new T();                                                        
            HttpContext.Current.Items[typeof(T)] = dataContext;                           
        }                                                                                 
                                                                                          
        return dataContext;                                                               
    }                                                                                     
                                                                                          
    public void DisposeCurrentDataContext()                                               
    {                                                                                     
        var dataContext = HttpContext.Current.Items[typeof(T)] as T;                      
        if (dataContext != null)                                                          
            dataContext.Dispose();                                                        
    }                                                                                     
}                                                                                         

lo que hago es guardar el datacontext dentro del diccionario Items del actual HttpContext, el cual esta vivo solo para ese request y se puede utilizar el evento request end del HttpAplication para hacer dispose del datacontext a través del IDataContextFactory

martes, diciembre 01, 2009

TDD con Fluent Validation Parte 2

En el post anterior (screencast) mostré como escribir las pruebas para un validador que usa la librería FluentValidation, debido a que se me acababan los 5 minutos, solo mostré las pruebas. en esta ocasión muestro como escribir el código para satisfacer dichas pruebas, el cual es muy simple. el screencast dura solo 2 minutos

aquí el link para verlo desde jing

sábado, noviembre 21, 2009

TDD con Fluent Validation

En este mini-screencast (5 min) muestro como escribir las pruebas unitarias para un validador que será escrito utilizando la librearía FluentValidation

Para ver el video desde el sitio de jing haz clic aquí

martes, octubre 27, 2009

Munq - Inversión de Control para ASP.NET MVC

Buscando un contenedor de inversión de control (IoC Container en ingles) ligero para utilizar en una aplicación ASP.NET MVC me encontré con Munq el cual esta escrito a partir del código de Funq. Me pareció muy fácil de configurar así que grabe un pequeño screencast (4 min), usando jing, en el cual muestro como configurarlo para usarlo con la aplicación que crea la platilla de nuevo proyecto ASP.NET MVC 1 en Visual Studio.

Para ver directamente desde el sitio de jing haz clic aquí.

miércoles, octubre 21, 2009

TDD ¿Por qué escribir primero las pruebas?

Hace poco leí el blog post de Eber Irigoyen donde escribe sobre duct tape programming. Lo que me llamo la atención en ese post es que Eber mencionó que al escribir pruebas, estas no las hace antes de escribir el código necesario para que las pruebas pasen e incluso menciona que la idea de escribir la prueba primero la considera algo tonta (no son sus palabras exactas pero es la idea). En lo personal la idea de escribir la prueba antes del código lo considero como una buena practica y no pensaría que es algo tonto.

Muchas de las veces cuando se me solicita realizar un nuevo programa o agregar funcionalidad a uno ya existente primero se realiza una entrevista con el usuario final, analista de negocio o cliente (de ahora en adelante lo llamaré cliente) que solicita la funcionalidad. Para que ahí explique a detalle que es lo que necesita. En ocasiones al cliente se le dificulta expresar que es lo que realmente necesita y eso se debe en gran parte porque el tampoco esta seguro que es lo que realmente necesita.

He notado que esto sucede principalmente cuando el cliente, al empezar a explicar el problema y lo que desea lograr con la nueva funcionalidad, esta pensando en la solución que le ayudará a resolver su problema. Inicia explicando como es que ve su solución en lugar de explicar el problema o lo que quiere lograr con ello. En ocasiones se empieza a discutir la implementación de esa solución y que problemas pudiéramos encontrar, después se discute como es que se podría ayudar a resolver esos problemas. Así la discusión puede continuar centrándose en como resolver los problemas de una posible solución que pudiera o no ser la ideal.

Si el desarrollo se centra en hacer que la posible solución funcione, se corre el riesgo que al terminar el desarrollo, esta no cumpla con las expectativas del cliente, ya que lo que se tomo en cuenta para desarrollarla fue la posible solución en lugar de lograr que el problema inicial del cliente se resolviera. Esto hace que el cliente se de cuenta que la solución no le sirve del todo pero el desarrollador siente que cumplió porque hizo que funcionara lo que le pidieron.

Cuando el cliente se centra primero en explicar el problema y en especificar lo que espera lograr, en lugar de pensar en la posible solución. Es entonces cuando yo como profesional puedo trabajar en un programa que resuelva su problema y logre lo que él espera.

Del mismo modo cuando el desarrollador inicia escribiendo el código que resuelva un problema sin especificar antes que es lo que quiere lograr con ello. Es posible que termine escribiendo código que no va a necesitar. Esto es porque se centra en escribir una solución robusta en lugar de solo resolver el problema.

Por eso que pienso que el escribir lo que esperamos del código, como una prueba unitaria, antes de escribir la implementación nos da la ventaja de centrarnos en lo que realmente es importante: cumplir con al expectativa. Y no tanto en hacer que nuestra posible solución funcione. De igual forma ayuda a no escribir código que posiblemente no se necesite, ya que la prioridad es hacer que la prueba unitaria (especificación) pase.

Considero que es benéfico que al iniciar el desarrollo de nueva funcionalidad primero se especifiquen las expectativas que se tienen sobre ella y después se evalúe en base a esas especificaciones. Las expectativas se escriben usando pruebas unitarias y es por eso que me gusta que las pruebas se escriban primero.

El desarrollo guidado por pruebas o TDD (Test Driven Development) no solo se trata de las pruebas. TDD es una tarea de diseño.

viernes, octubre 09, 2009

VB XML Literals – Parte 3 LinqToXml

Siguiendo con la serie de post sobre VB XML Literals en esta ocasión en lugar de copiar y pegar el código utilicé Jing para grabar un screencast de 5 minutos donde explico como buscar dentro de un archivo xml usando LinqToXml en VB XML Literals

Ver screencast desde el sitio Jing

Post Relacionados
VB XML Literals Parte 2
VB XML Literals Parte 1

lunes, octubre 05, 2009

VB XML Literals - Parte 2

En la parte 1 de esta serie de posts sobre XML Literals expliqué en el ejemplo como cargar un objeto a partir de un archivo xml. Ahora veré el caso contrario: A partir de un objeto Order creare un archivo xml con la información de la orden. Este es el test con que comprobaré que mi método esta funcionando:

[TestMethod]
public void should_save_order_to_file()
{
var fileName = @"..\..\Order_Temp.xml";

if (File.Exists(fileName)) File.Delete(fileName);

var order = CreateSampleOrder();

var orderXmlTasks = new OrderXmlTasks();
orderXmlTasks.SaveToFile(fileName, order);

File.Exists(fileName).Should().Be.True();

var fileLines = File.ReadAllLines(fileName);
var expectedLines = CreateExpectedXml();
for (int i = 0; i < fileLines.Length; i++)
{
var line = fileLines[i].Trim();
line.Should().Be.EqualTo(expectedLines[i]);
}
}

public Order CreateSampleOrder()
{
return new Order
{
Id = 321,
Customer = "Peter Griffin",
Items = new List<OrderItem>
{
new OrderItem { ProductId = 3, Quantity = 1, Price = 2.05m, Description = "product x" },
new OrderItem { ProductId = 7, Quantity = 4, Price = 3.45m, Description = "product y" },
new OrderItem { ProductId = 8, Quantity = 9, Price = 5.50m, Description = "product z" },
}
};
}

public string[] CreateExpectedXml()
{
var xml = new List<string>();

xml.Add("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
xml.Add("<order id=\"321\" xmlns=\"urn:schemas-developeando-com:xmlLiterals\">");
xml.Add("<customer>Peter Griffin</customer>");
xml.Add("<items>");
xml.Add("<item productId=\"3\" quantity=\"1\" price=\"2.05\">product x</item>");
xml.Add("<item productId=\"7\" quantity=\"4\" price=\"3.45\">product y</item>");
xml.Add("<item productId=\"8\" quantity=\"9\" price=\"5.50\">product z</item>");
xml.Add("</items>");
xml.Add("</order>");

return xml.ToArray();
}

Lo que hace el test es crear una orden de ejemplo (CreateSampleOrder), escribir los datos de la orden en un archivo xml y después leo el archivo generado y lo comparo con el xml esperado (CreateExpectedXml)

Generar xml usando XML Literals es tan simple como solo escribir el XML y agregar expresiones donde queremos insertar código. Entonces el código para que la prueba pase queda así:

Public Sub SaveToFile(ByVal FileName As String, ByVal Order As Order)
Dim xml = <order id=<%= Order.Id %>>
<
customer><%= Order.Customer %></customer>
<
items>
<%= From item In Order.Items _
Select <item
productId=<%= item.ProductId %>
quantity=<%= item.Quantity %>
price=<%= item.Price %>>
<%= item.Description %>
</item> %>
</items>
</
order>
xml.Save(FileName)
End Sub

Como se puede apreciar solo declaro una variable y le asigno el xml, usando expresiones para indicar donde quiero escribir los valores de las propiedades del objeto Order. Para los items utilizo una expresión LINQ para indicar que por cada item agregue un elemento <item> y asigno el valor a los atributos y el valor de la descripción en el texto interno.

martes, septiembre 29, 2009

VB XML Literals

Desde la versión 9.0 de VB .Net existe una característica llamada XML Literals la cual consiste en poder escribir XML directamente en el código VB y manejar el xml como parte del lenguaje.

Actualmente cuando en una solución necesito trabajar con XML, donde generalmente hay puros proyectos de C#, también agrego un proyecto en VB.Net solo para el manejo de XML por que se me facilita con XML Literals.

Para mostrar como se utiliza XML Literals mostraré un ejemplo (el clásico ejemplo de una orden) el cual consiste en una clase que exporta e importa ordenes a/desde XML.

Para esto creo una solución con 3 proyectos uno de tipo C# Windows library con el nombre XmlLiteralsExample.Domain el cual contiene las clases Order y OrderItem con esta definición

namespace XmlLiteralsExample.Domain
{
public class Order
{
public int Id { get; set; }
public string Customer { get; set; }
public List<OrderItem> Items { get; set; }

public Order()
{
Items = new List<OrderItem>();
}
}

public class OrderItem
{
public int ProductId { get; set; }
public int Quantity { get; set; }
public string Description { get; set; }
public decimal Price { get; set; }
}
}

Ahora agrego el proyecto de tipo VB Class library llamado XmlLiteralsExample.Tasks en el cual agregaré la clase en VB que me ayudará con el XML

Imports XmlLiteralsExample.Domain

Public Class OrderXmlTasks
Public Function ReadFromFile(ByVal FileName As String) As Order
Throw New NotImplementedException()
End Function

Public Sub
SaveToFile(ByVal FileName As String)
Throw New NotImplementedException()
End Sub

Public Function
FindOrderItem(ByVal FileName As String, _
ByVal ProductId As Integer) As OrderItem

Throw New NotImplementedException()
End Function

End Class

XmlLiteralsSolution Y por último agrego un proyecto para las pruebas unitarias en C# llamado XmlLiteralsExample.Tasks.Test en el cual agregaré las pruebas unitarias para nuestra clase OrderXmlTasks y un archivo xml para las pruebas.

El proyecto XmlLiteralsExample.Test tiene referencias al proyecto Domain así como el proyecto XmlLiteralsExample.Tasks.Test tiene referencias a los otros 2 proyectos de la solución

Además el proyecto de prueba incluye una referencia a las extensiones SharpTestsEx

El Archivo Order.xml contiene una orden de prueba

<?xml version="1.0" encoding="utf-8" ?>
<
order id="123" xmlns="urn:schemas-developeando-com:xmlLiterals">
<
customer>Homer Simpson</customer>
<
items>
<
item productID="1" quantity="2" price="1.50">product desc 1</item>
<
item productID="2" quantity="4" price="2.60">product desc 2</item>
<
item productID="3" quantity="6" price="3.80">product desc 3</item>
</
items>
</
order>

A partir del archivo de prueba (Order.xml) genero el xml schema utilizando la opción del menú XML de VisualStudio y la guardo en el folder del proyecto Tasks y después agrego el archivo al proyecto. Con esto puedo importar el xml namespace en el archivo OrderXmlTasks.vb como si fuera un namespace de .net

Imports <xmlns="urn:schemas-developeando-com:xmlLiterals">

Esto me ayudará a que visual studio conozca la estructura de mi xml y así pueda darme opciones a través de intellisense

Ahora que tengo los datos de prueba iniciamos con con la funcionalidad, para ello escribo mi primer test en la clase OrderXmlTasksTest (dentro del proyecto de prueba)

[TestMethod]
public void should_load_order_from_file()
{
var orderXmlTasks = new OrderXmlTasks();
var order = orderXmlTasks.ReadFromFile(@"C:\..\..\Order.xml");

order.Id.Should().Be.EqualTo(123);
order.Customer.Should().Be.EqualTo("Homer Simpson");
order.Items.Count.Should().Be.EqualTo(3);
}

Ahora el código para pasar esta prueba:

Public Function ReadFromFile(ByVal FileName As String) As Order
Dim orderXml = XElement.Load(FileName)

Dim order = New Order()
With order
.Id = orderXml.@id
.Customer = orderXml.<customer>.Value
End With

For Each
itemXml In orderXml.<items>.<item>
Dim item = New OrderItem()
With item
.ProductId = itemXml.@productID
.Quantity = itemXml.@quantity
.Price = itemXml.@price
.Description = itemXml.Value
End With
order.Items.Add(item)
Next

Return
order
End Function

Ahora explico un poco lo que hace el código; la primer línea carga el xml a un objeto. En este ejemplo uso la clase XElement la cual carga el xml a partir del elemento raíz (en este caso la etiqueta Order). Por lo tanto el objeto orderXml me representa la etiqueta Order del archivo xml. Después creo un objeto order que después lleno con los datos del archivo xml cargados en el objeto orderXml.

En XML Literals para accesar a los atributos de un elemento xml utilizamos la @ es por eso que podemos ver que de carga en la propiedad Id, del objeto order, el valor del atributo id del elemento order del xml. Después se puede notar que para tener acceso a los elementos hijos solo hace falta escribir las etiquetas tal y como lo haríamos en un archivo xml y gracias a que importe el namespace xml también tengo intellisense

intellisense

de igual forma en una ciclo for each acceso a la colección de items. En este caso acceso al elemento item a través de toda la ruta de elementos hijos.

intellisense2

Pero además XML literals me permite accesar a elementos hijos sin tener que seguir la ruta usando tres puntos entre el objeto y la etiqueta.

Para obtener al texto dentro de una etiqueta se utiliza la propiedad Value del objeto XElement. Así es como obtengo el nombre del cliente y la descripción de los ítems.

Bueno este post ya se extendió y tengo que dividirlo, seguiré con el ejemplo en otro post.

jueves, septiembre 17, 2009

Sharp Tests Ex

Descargue las extensiones para MsTests desarrolladas por Fabio Maulo: "Sharp Tests Extensions". La idea de estas extensiones es que tus aserciones en las pruebas sean escritas con mayor fluidez. ademas de que es mas clara la distinción entre el valor esperado y el valor obtenido. Evitando el uso de la clase Assert directamente.

Un ejemplo de como cambia un test usando estas extensiones

Test usando MsTest sin extensiones

[TestMethod]
public void should_change_password()
{
var username = "username";
var originalPassword = "original";
var newPassword = "newPassword";

var user = new User { Password = originalPassword };

mockRepository.Setup(r => r.GetUser(username))
.Returns(user);

bool result = accountService.ChangePassword(
username, originalPassword, newPassword);

Assert.IsTrue(result);
Assert.AreEqual(newPassword, user.Password);
mockRepository.Verify(r => r.Save());
}


ahora esta es la misma prueba utilizando Sharp TextsEx
[TestMethod]
public void should_change_password()
{
var username = "username";
var originalPassword = "original";
var newPassword = "newPassword";

var user = new User { Password = originalPassword };

mockRepository.Setup(r => r.GetUser(username))
.Returns(user);

bool result = accountService.ChangePassword(
username, originalPassword, newPassword);

result.Should().Be.True();
user.Password.Should().Be.EqualTo(newPassword);
mockRepository.Verify(r => r.Save());
}


estas son las lineas que cambian de:
Assert.IsTrue(result);
Assert.AreEqual(newPassword, user.Password);

a
result.Should().Be.True();
user.Password.Should().Be.EqualTo(newPassword);


Se escribe mas sin embargo queda mas descriptivo cual es la intensión de la prueba ademas de que tenemos la ventaja que intellisense nos ayuda a completar la aserción.

En lo personal me cuesta un poco acostumbrarme a no ver la clase Assert al final de los metodos, pero debo reconocer que el test es mas claro así.

martes, julio 28, 2009

TDD - Desarrollo Guiado Por Pruebas

En la reunión 30 de la comunidad TjNet estaré presentando el tema TDD (Test Driven Development), el cual es una práctica de programación que consiste en escribir primero las pruebas unitarias, después el código que cumple con las pruebas y refactorizar el código escrito.

La idea principal es que el comportamiento esperado del objeto que estamos probando lo definamos en pruebas y después nos preocupemos por que el objeto cumpla con lo que se definió en las pruebas.

Cabe aclarar que el TDD es una actividad del equipo de desarrollo y no del equipo de QA que realiza las pruebas al software. La intención de las pruebas unitarias no es que el equipo de calidad las use para realizar su trabajo. Las pruebas unitarias son para definir el comportamiento del código que vamos escribir, es decir es parte del diseño. Aunque sí aumentan en cierta forma la calidad del código escrito.

Efectos del desarrollo guiado por pruebas

Algunos de los efectos notables sobre el desarrollo guiado por pruebas es que se evita escribir código que no se utilizará. Ya que se trata de realizar solamente el código necesario para poder satisfacer las pruebas definidas.

Otro efecto que se obtiene al trabajar de esta manera es que como nuestras clases ya no solo van a ser utilizadas por la aplicación, sino también por el código de prueba, esto nos obliga a tener clases débilmente acopladas y que hacen uso de dependencias solo a través de interfaces, independientemente de la implementación. De tal forma que sea fácil utilizar ciertas clases concretas para la aplicación y otras para las pruebas. Para esto es común aplicar el principio de inyección de dependencias.

El mantenimiento del código es más manejable, la confianza en el código mejora, por que las pruebas nos sirven como alerta cuando introducimos código que hace que nuestras pruebas fallen. En lugar de introducir cambios al código sin saber cómo afectan a otras clases del sistema.

El uso del depurador disminuye ya que cuando existe un error en el código es más fácil encontrarlo en las pruebas que corriendo la aplicación en modo debug.

Ciclo de desarrollo

1. Escribir el comportamiento deseado (requerimiento) a manera de prueba unitaria, Este paso fuerza al programador a tomar la perspectiva de un cliente considerando el código a través de sus interfaces. Obviamente la prueba fallará ya que aun no está escrito el código para satisfacerla.

2. Escribir solo el código necesario para que la prueba pase.

3. Correr las pruebas para confirmar que efectivamente el código escrito cumple con los establecido por las pruebas.

4. Refactorizar el código, para eliminar código duplicado y dejarlo de tal forma que sea fácil hacerle modificaciones en el futuro.

5. Verifica que las pruebas no fallen después de la refactorización.

Conclusión

El desarrollo guiado por pruebas no es parte del proceso de calidad de un sistema en desarrollo (aunque si aumenta la calidad del código escrito) sino que forma parte del diseño de cada clase; define el comportamiento esperado de la clase desde el punto de vista del cliente que la usará.

Los espero en la reunión 30 de la comunidad TjNet.

miércoles, junio 17, 2009

Agile Programming Coffee Camp



Tijuana Agile Programming Coffee Camp en el devolada de otay (el lugar de costumbre) el sábado 4 de Julio, a partir de las 9:00am.

La idea surgió de una conversación en twitter entre @mario_chavez y @fcastellanos a la que nos sumamos @gabo y yo. Y como 140 caracteres no es suficiente y varios queremos participar; pues mejor platiquemos sobre el tema tomando café.

Cualquiera puede participar, es una platica entre colegas, el costo es según el tamaño del café que pidas.

Si conoces del tema asiste a compartir tus experiencias, y si no, es una buena oportunidad para aprender.

Mas información (mapas, fotos y Dilbert) en:
TjNet
blog de Mario Chavez
blog de Fernando Castellanos

sábado, mayo 30, 2009

ASP.NET MVC Routing Validations

Ayer asistí a la reunión de la comunidad TjNet fue la presentación sobre ASP.Net MVC que dieron Mario Chavez y Fernando Castellanos. Hubo pocos asistentes sin embargo hubo varias preguntas interesantes, incluso falto tiempo para seguir con las preguntas.

Algo que se preguntó ahí en la reunión fue: ¿Como restringir el routing en ASP.NET MVC con expresiones regulares? Mencioné que si se podía pero en ese momento no me acorde como hacerlo ;), bueno pues aquí muestro un ejemplo.

Una vez creado un nuevo proyecto de asp.net mvc desde visual studio, voy a agregar dos métodos al HomeController
public ActionResult WelcomeByName(string name)
{
ViewData["Message"] = "Bienvenido, tu nombre es: " + name;
return View("Index");
}

public ActionResult WelcomeById(int id)
{
ViewData["Message"] = "Welcome, your user ID is " + id;
return View("Index");
}
mi idea es que si el usuario pone la dirección /Welcome/{cadena}
se ejecutará el metodo WelcomeByName (mensaje en español) y si el usuario escribe /Welcome/{entero} se ejecutará el metodo WelcomeById (mensaje en ingles).

Para ello debo de agregar entradas a mi tabla de rutas que se crea en el archivo Global.asax
routes.MapRoute(
"Welcome int",
"Welcome/{id}",
new { controller = "Home", action = "WelcomeById" },
new { id = @"^\d+$" }
);

routes.MapRoute(
"Welcome s",
"Welcome/{name}",
new { controller = "Home", action = "WelcomeByName", name = "anonimo" }
);
Estas dos entradas las puse antes de la entrada "Default". La primera ruta indica que espera una dirección con la forma "Welcome/{id}" para ejecutar la acción WelcomeById del HomeController, pero ademas agrega una restricción (usando expresiones regulares) que indica que solo se usara esta ruta si el parámetro "id" es entero positivo (o cero). Entonces si el valor de id no cumple con la condición no se usará esa ruta y se buscará otra para ser utilizada.

La segunda ruta es similar a la primera solo que no tiene restricción alguna, por lo tanto si es entero el valor se ejecuta la primera, para las demás (que inicien con "Welcome") se ejecuta la segunda.

Corro la aplicación y escribo la dirección en el navegador y compruebo que las rutas funcionen















Pueden encontrar mas información sobre esto en la grabación de la VAN de Alt.Net Hispano. Sobre ASP.NET MVC Avanzado explicada por Hadi Hariri.

miércoles, abril 29, 2009

ASP.NET MVC MyTwitter (Parte 2)

Como parte de la serie sobre el desarrollo de una aplicación similar a Twitter para aprender ASP.NET MVC. Voy a seguir con la parte de registro (Signup). Esta semana he estado ocupado con trabajo, por eso agregaré poca funcionalidad en este post, la cual consiste en que una vez que el usuario se registre debemos de iniciar sesión con ese usuario, es decir identificarlo como un usuario Autentificado.

Para ello es necesario escribir una clase (y una Interfaz) que me ayude en esta funcionalidad, para después hacer un mock de ella en el test del AccountController.

namespace MyTwitter.Models
{
public interface IFormsAuthenticationTasks
{
void SetAuthCookie(string username, bool createPersistentCookie);
void SignOut();
}
}
using System.Web.Security;
namespace MyTwitter.Models
{
public class FormsAuthenticationTasks
: MyTwitter.Models.IFormsAuthenticationTasks
{
public void SetAuthCookie(string username,
bool
createPersistentCookie)
{
FormsAuthentication.SetAuthCookie(
username, createPersistentCookie);
}

public void SignOut()
{
FormsAuthentication.SignOut();
}
}
}

agrego una variable de tipo IFormsAuthenticationTasks al AccountController y al constructor usado para las pruebas

public class AccountController : Controller
{
private IUserRepository userRepository;
private IFormsAuthenticationTasks formsAuthenticationTasks;

public AccountController(IUserRepository userRepository,
IFormsAuthenticationTasks formsAuthenticationTasks)
{
this.userRepository = userRepository ?? new UserRepository();
this.formsAuthenticationTasks =
formsAuthenticationTasks ?? new FormsAuthenticationTasks();
}

public AccountController()
: this(null, null)
{
}
...

escribo el test para la nueva funcionalidad

[TestMethod]
public void Login_User_After_Signup()
{
formsAuthenticationMock.Setup(
fa => fa.SetAuthCookie(It.IsAny<string>(), It.IsAny<bool>()));
controller.Signup(null);
formsAuthenticationMock.VerifyAll();
}

implemento la funcionalidad en el método Signup del Account Controller para que la prueba pase, es solo agregar una línea antes del redirect

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Signup(FormCollection formValues)
{
var user = new User();
UpdateModel(user);
userRepository.Add(user);
userRepository.Save();

formsAuthenticationTasks.SetAuthCookie(user.Username, true);

return Redirect("/invitations");
}
Hasta aquí con esta parte. Estuve tentado a llamarla “Parte 1.5” en lugar de “Parte 2”, por el poco contenido, pero se iba a empezar a complicar la numeración, continuaré con la serie en siguientes posts.

viernes, abril 24, 2009

ASP.NET MVC MyTwitter (Parte 1)

En la parte 0 de esta serie sobre ASP MVC MyTwitter hice la base de datos (la cual cambie un poco desde el pasado post) para iniciar el desarrollo de la aplicación. Ahora voy a generar el modelo de datos (LINQ to SQL classes) para eso en el folder de Models agrego el modelo de datos.

image

Le doy clic con el botón derecho en el folder de Models y le hago clic en “Add/New Item” y selecciono “LINQ to SQL Classes” y le pongo el nombre MyTwitter.dbml

image Arrastro desde el server explorer las tabla de la base de datos al modelo de datos, para que mi modelo quede así.

Cambie el nombre de la tabla UserFriends por Friends y la columna Friend por FriendName. Esto fue con el propósito de que quedaran con mejor nombre las clases generadas por LinqToSql.

Ahora empiezo con la pagina de inicio, esta seria solo un formulario para iniciar sesión y un link para registrarse si es que no se tiene una cuenta. Para ello abro la página Views/Home/Index.aspx y escribo el la forma en el HTML

<form action="/account/login" method="post">
<
fieldset>
<
p>
<
label for="username">Usuario</label>
<%= Html.TextBox("username") %>
<%= Html.ValidationMessage("username", "*") %>
</p>
<
p>
<
label for="password">Contrase&ntilde;a</label>
<%= Html.Password("password") %>
<%= Html.ValidationMessage("password", "*") %>
</p>
<
p>
<
input type="submit" value="Ingresar" />
</
p>
<
p>Si no tienes una cuenta registrate <a href="/signup">aqui</a>.</p>
</
fieldset>
</
form>

De igual manera también modifico la prueba unitaria que viene por omisión en nuestro proyecto de test (MyTwitter.Tests) dentro del archivo HomeControllerTest.cs.

[TestMethod]
public void Index()
{
HomeController controller = new HomeController();
var result = controller.Index();
Assert.IsNotNull(result);
}

Solo pruebo que el método Index() del HomeController regrese un View que no sea null.

Para realizar el registro de usuario voy a agregar un controller llamado AccountController, similar al que venia por default al crear al proyecto.


image



Hago clic en el folder Controllers y selecciono la opción “add controller”. y le pongo el nombre de “AccountController”.

Esto me agrega una nueva clase AccountController que hereda de la clase Controller con un método llamado Index. por el momento elimino el método Index, ya que todavía no lo voy a usar (casi no me gusta tener código que no hace nada) y agrego un método llamado Signup().

namespace MyTwitter.Controllers
{
public class AccountController : Controller
{
public ActionResult Signup()
{
throw new NotImplementedException();
}
}
}
Ahora el unit test

image



En el folder Controllers del proyecto MyTwitter.Tests selecciono “Add > New Test..” y selecciono “Unit Test” y le doy el nombre de AccountControllerTest. Esto me agrega una clase con código de ejemplo.

Quito el código que genera para quedar con una prueba sencilla que verifica que se regrese un view para realizar el registro.

[TestMethod]
public void Return_A_View_For_Signup()
{
var result = controller.Signup();
Assert.IsNotNull(result);
}
Implemento el método de Signup en el AccountController, este simplemente (como el test lo indica) regresará el view necesario para que el usuario ingrese sus datos de registro
public ActionResult Signup()
{
return View();
}
Para agregar el View abro el menú contextual sobre el método Signup y selecciono “Add View”
image

image



Aparece un dialogo para agregar un view. aquí habilito la opción “Create a strongly-typed view” y selecciono (en “View data class:”) la clase MyTwitter.Models.User, En “View content” selecciono la opción Create. Esto para que VisualStudio escriba la mayoría del HTML por mi. Esto agrega un archivo (Views\Account\Signup.aspx) , lo abre en el editor y lo modifico para que quede en español.

El view queda así:

<% using (Html.BeginForm()) {%>

<fieldset>
<
legend>Fields</legend>
<
p>
<
label for="Username">Usuario:</label>
<%= Html.TextBox("Username") %>
<%= Html.ValidationMessage("Username", "*") %>
</p>
<
p>
<
label for="FullName">Nombre Completo:</label>
<%= Html.TextBox("FullName") %>
<%= Html.ValidationMessage("FullName", "*") %>
</p>
<
p>
<
label for="Password">Contraseña:</label>
<%= Html.Password("Password") %>
<%= Html.ValidationMessage("Password", "*") %>
</p>
<
p>
<
label for="ConfirmPassword">Confirmar Contraseña:</label>
<%= Html.Password("ConfirmPassword") %>
<%= Html.ValidationMessage("ConfirmPassword", "*") %>
</p>
<
p>
<
label for="Email">Correo Electronico:</label>
<%= Html.TextBox("Email") %>
<%= Html.ValidationMessage("Email", "*") %>
</p>
<
p>
<
label for="Url">Sitio Web:</label>
<%= Html.TextBox("Url") %>
<%= Html.ValidationMessage("Url", "*") %>
</p>
<
p>
<
label for="Bio">Biografia:</label>
<%= Html.TextBox("Bio") %>
<%= Html.ValidationMessage("Bio", "*") %>
</p>
<
p>
<
label for="Location">Ubicacion:</label>
<%= Html.TextBox("Location") %>
<%= Html.ValidationMessage("Location", "*") %>
</p>
<
p>
<
input type="submit" value="Create" />
</
p>
</
fieldset>

<% } %>
Las funciones de la clase Html me ayuda a no tener que escribir todo el HTML a pie (o mejor dicho a mano), no explicare como funcionan (creo que el nombre de cada método describe bien lo que hace) basta decir que escriben HTML. Uso Html.BeginForm sin parámetros en lugar de escribir el HTML para un <form> y así el action es al mismo url actual pero con el method=”post”. en este caso seria equivalente a escribir <form action=”/signup” method=”post”> y al cerrar el using (<% } %>) se escribe el </form>.

Ahora voy a implementar el código que se ejecutará cuando el usuario haga submit a esta forma, para esto creo un nuevo método en el AccountController llamando también Signup la diferencia con el otro son los parámetros que recibe (en este caso los valores de la forma) y un atributo para que solo reciba peticiones de tipo POST.

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Signup(FormCollection formValues)
{
throw new NotImplementedException();
}

Antes de implementar la función quiero escribir un test para probar que el método Signup trate de guardar la información en la base de datos, pero sin hacer una llamada real a la base de datos, para esto voy a crear una clase UserReposotory que será la que se encargue de cargar los datos. También voy a crear una Interfaz para poder hacer un mock del repository. Esta clase e interfaz las pongo dentro del folder Models cada una en su propio archivo.

namespace MyTwitter.Models
{
public interface IUserRepository
{
void Add(User user);
User GetUser(string username);
void Save();
}
}
namespace MyTwitter.Models
{
public class UserRepository : IUserRepository
{
private MyTwitterDataContext db;

public UserRepository()
{
db = new MyTwitterDataContext();
}

public User GetUser(string username)
{
return db.Users.FirstOrDefault(u => u.Username == username);
}

public void Add(User user)
{
db.Users.InsertOnSubmit(user);
}

public void Save()
{
db.SubmitChanges();
}
}
}
Ahora sí, ya puedo empezar a escribir mi prueba en AccountControllerTest. Para no tener que hacer uso de la base de datos en las pruebas unitarias utilizo Moq, el cual es un mock framework que se puede descargar aquí.

image Una vez descargado moq, agrego una reference al moq.dll en el proyecto MyTwitter.Tests y agrego el using Moq; en el AccountControllerTest.cs. Con la ayuda de este framework vamos probar que el AccountController llame el metodo Add y Save del UserRepository.

Para no repetir la inicialización del repositoryMock y del controller, voy a agregarlos como variables de clase e inicializarlos en un método SetUp

[TestClass]
public class AccountControllerTest
{
private AccountController controller;
private Mock<IUserRepository> repositoryMock;

[TestInitialize]
public void Setup()
{
repositoryMock = new Mock<IUserRepository>();
controller = new AccountController(repositoryMock.Object);
}
...
Ahora agrego los métodos de prueba
[TestMethod]
public void Add_And_Save_User_To_Repository()
{
repositoryMock.Setup(r => r.Add(It.IsAny<User>()));
repositoryMock.Setup(r => r.Save());

controller.Signup(new FormCollection());

repositoryMock.VerifyAll();
}

Además voy a agregar un test para probar que, una vez que se realice el registro del usuario, se envíe al usario a la pagina “/invitations” para que ahí el usuario pueda agregar amigos (friends) a quien seguir.

[TestMethod]
public void Redirect_To_Invitations_After_Signup()
{
var result = controller.Signup(new FormCollection()) as RedirectResult;

Assert.AreEqual("/invitations", result.Url);
}
Si corro el test ahora fallará porque no lo he implementado, entonces ahora lo que debo de hacer es que el test pase. Para ello declaro un IUserRepository en la clase AccountController y 2 constructores, uno que usare en el código en producción y el otro para las pruebas.
public class AccountController : Controller
{
private IUserRepository userRepository;

public AccountController(IUserRepository userRepository)
{
this.userRepository = userRepository ?? new UserRepository();
}

public AccountController()
: this(null)
{
}
...
ahora si, por fin implemento el método Signup en el AccountController
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Signup(FormCollection formValues)
{
var user = new User();
UpdateModel(user);
userRepository.Add(user);
userRepository.Save();

return Redirect("/invitations");
}
[tanto blog post para este método tan sencillo :) ]

image Ejecuto las pruebas unitarias y verifico que el código hace lo que se supone debe hacer.

Si ejecuto la aplicación y en la pagina de inicio hago clic en el enlace para registrase me manda a “/signup” y me marca error por que no encuentra el recurso. Para que al buscar el recurso /signup se ejecute el action Signup del AccountController, debo de agregar un Route en el archivo Global.asax ( lo agrego antes del Route “Default”).

public class MvcApplication : System.Web.HttpApplication
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

routes.MapRoute(
"Signup",
"signup",
new { controller = "Account", action = "Signup" });

routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = "" }
);

}

protected void Application_Start()
{
RegisterRoutes(RouteTable.Routes);
}

con esto ya puedo hacer una pequeña prueba de integración.En la siguiente parte continuare con el desarrollo de esta aplicación.