domingo, 12 de octubre de 2008

Unit Test y Rhino Mocks

Introducción.
Normalmente en las pruebas unitarias no es factible usar objetos reales porque es muy dificil o imposible incorporarlos al test unitario, por ejemplo que el estado de un objeto sea dificil de reproducir como un error de red, o que sea lento como es el caso de una base de datos que primero tiene que ser inicializada para realizar el test, o que desde donde se ejecuta el juego de pruebas no haya acceso. En estos casos es mejor utilizar "Mocks Objects", los Mocks son objetos que simulan el comportamiento de objetos reales. Un objeto mock tiene la misma interfaz que un objeto real, así que un objeto puede usar de forma indistinta un objeto mock o un objeto real. La importancia de utilizar mocks es poder probar un trozo de código aisladamente, que lo que se pretende con un test unitario. Rhino Mocks es un Mock Object Framework, que permite realizar estos objetos simulados de una forma automática y crear test unitarios más efectivos.
Creado Test Unitarios.
Supogamos el siguiente ejemplo, la clase de negocio CutomerBusiness de la que queremos realizar el test unitario del método HasSpecialFare, que devuelve cierto en el caso que el cliente que le pasamos poseea una tarifa especial. Este método utiliza objeto de acceso a datos (customerDao) que se conecta con la base de datos y obtiene la tarifa del cliente, y una vez obtenida la tarifa compara el código de la tarifa con el código de tarifa especial.
public class CustomerBusiness { private const string SPECIAL_FARE = "SPC"; private readonly ICustomerDao customerDao; public CustomerBusiness(ICustomerDao customerDao) { this.customerDao = customerDao; } public bool HasSpecialFare(Customer customer) { return customerDao.GetFareType(customer).Type == SPECIAL_FARE; } } public interface ICustomerDao { FareType GetFareType(Customer customer); } ...
Queremos realizar el test unitario de este método aisladamente, no nos interesa saber si el objeto CustomerDao es capaz de extraer correctamente o no la tarifa, de eso ya se encargará el test unitario que hagamos sobre el objeto dao, lo único que en este caso nos interesa es saber si este código es capaz de funcionar o no. Por esto ni siquiera nos hemos molestado en codificar el objeto CustomerDao, tan solo hemos codificado la parte de la interfaz necesaria para este ejemplo. Si no utilizamos RhinoMocks deberiamos primero implementar el objeto CustomerDao para que se conectara a la base de datos, comprobar que se conecta correctamente y que obtenga los datos correctos, además de tener acceso a la base de datos desde donde se ejecuten los test. Por otro lado si lo hacemos así, primero se debería pasar las pruebas de los DAO antes de pasar las pruebas de los business, esto implica que hay pruebas que dependen las unas de las otras, esto no debería pasar nunca ya que la definición de test unitario pierde todo su sentido. Para evitar este problema podriamos crear un objeto Dao dummy, que devolviera unos resultados ficticios en función de las necesidades, esta solución sería muy costosa ya que deberíamos hacerlo para todos los objetos DAO de nuestra solución, y la realización de test unitarios sería interminable, y esto es justamente lo que hace RhinoMocks. Además de la creación automática de objeto dummy, RhinoMocks nos permite llegar a testear si se llama a ciertos métodos o no. Ahora crearemos un test unitario con RhinoMocks para probar el método HasSpecialFareType de la clase CustomerBusiness. Para realización de los test unitarios utilizaremos NUnit.
[TestFixture] public class CustomerBusinessTest { [Test] public void HasSpecialFareTest() { //Creación de los objetos mocks var mockRepository = new MockRepository(); var customerDaoMock = mockRepository.CreateMock<ICustomerDao>(); //Establecimiento de las llamadas esperadas //y de los objetos de retorno var customer = new Customer(); var fareType = new FareType {Type = CustomerBusiness.SPECIAL_FARE}; Expect.Call(customerDaoMock.GetFareType(customer)) .Return(fareType); mockRepository.ReplayAll(); var customerBusiness = new CustomerBusiness(customerDaoMock); var specialFare = customerBusiness.HasSpecialFare(customer); mockRepository.VerifyAll(); Assert.IsTrue(specialFare); } }
Lo primero que hacemos es crear el repositorio de Mocks, a este objeto repositorio le pedimos que cree un Mock que implemente la interfaz ICustomerDao, el repositorio nos devolverá un objeto dummy que de momento lo único que hará será implementar esa interfaz sin ninguna acción en particular. Ahora le diremos al mock que esperamos que se realice una llamada al método GetFareType, al que se le pasará un objeto customer y queremos que devuelva el objeto fareType especificado. Si tuvieramos más llamadas lo hariamos con cada una de ellas, como si fuera una macro en la que no importa el orden. Una vez hecho esto, le decimos al repositorio que guarde la macro que hasta ahora hemos introducido, y codificamos lo que vamos a probar, creamos un objeto customerBusiness y llamamos al metodo HasSpecialFare, que a su vez llamara al metodo GetFareType del dao, y le devolverá el objeto fareType. Por último, le decimos al repositorio que verifique todas que todo ha ido bien, si durante la ejecución del test no se hubiera llamado al método GetFareType, o se hubiera llamado a otro hubiera dado error. Pulsa aquí para descargar el ejemplo.

jueves, 18 de septiembre de 2008

Windsor Container Part. II

Introducción.
En este artículo veremos como afectan los cambios en el código a la configuración de Windsor Container y veremos como afectarían en el caso de no utilizar WC.
Interfaces.
Las primeras modificaciones que haremos será crear una interfaz para la clase sumador.
public interface ISumador { decimal Suma(decimal num1, decimal num2); } public class Sumador : ISumador { public decimal Suma(decimal num1, decimal num2) { return num1 + num2; } } ... public static WindsorContainer miContenedor; public static void Main(string[] args) { //Creamos el contenedor miContenedor = new WindsorContainer (new XmlInterpreter("MiContenedor.xml")); //Pedimos un objeto de tipo sumador al contenedor var sumador = miContenedor.Resolve<ISumador>(); var result = sumador.Suma(1,1); } ...
Al generar la interfaz en vez pedirle al contenedor que nos devuelva un objeto de tipo Sumador, le pedimos un objeto que implemente la interfaz sumador. Para esto debemos modificar el fichero de configuración de WC, añadiendo el atributo service al componente Sumador.
<configuration> <components> <component id="Sumador" type="IoC.Tutoriales.Part2.Sumador, IoC.Tutoriales.Part2" service="IoC.Tutoriales.Part2.ISumador, IoC.Tutoriales.Part2" /> </components> </configuration>
Con esto al pedir un objeto que implemente la interfaz ISumador, el contendor buscará el primer componente que implemente esta interfaz y devolverá un objeto de la clase de ese componente. En nuestro caso buscará un componente que implemente la interfaz ISumador, el componente con el id "Sumador", y generará un objeto del tipo "IoC.Tutoriales.Part2.Sumador". Al realizar estos cambios si ahora al contendor le pedimos un objeto Sumador se producirá un error ya que los tipos de service prevalecen sobre los tipos de atributo type.
Dependencias entre componentes.
Supogamos que cada vez que realizamos una suma queremos que se guarde un log en un fichero. Primero escribiremos nuestra clase que graba el log:
public interface ILogger { void Debug(string text); } public class FileLogger : ILogger { private string fileName; public FileLogger(string fileName) { this.fileName = fileName; } public void Debug(string text) { //Graba el texto en el fichero fileName .... } }
Los cambios en la clase Sumador serían de tipo:
public class Sumador { private ILogger logger; public Sumador(ILogger logger) { this.logger = logger; } public decimal Suma(decimal num1, decimal num2) { logger.Debug(string.Format("Sumando {0} + {1}", num1, num2)); return num1 + num2; } }
Para una arquitectura de este tipo sin utilizar WC deberimos pirmero crear un objeto de tipo FileLogger y pasarlo al constructor del objeto Sumador.
... public static void Main(string[] args) { var logger = new FileLogger("log.txt"); var sumador = new Sumador(logger); var result = sumador.Suma(1,1); }
Al utilizar Windsor Container lo único que tenemos que cambiar es el fichero de configuración de la siguiente forma:
<configuration> <components> <component id="Sumador" type="IoC.Tutoriales.Part2.Sumador, IoC.Tutoriales.Part2" service="IoC.Tutoriales.Part2.ISumador, IoC.Tutoriales.Part2" /> <component id="Logger" type="IoC.Tutoriales.Part2.FileLogger, IoC.Tutoriales.Part2" service="IoC.Tutoriales.Part2.ILogger, IoC.Tutoriales.Part2"> <parameters> <fileName>C:\log.txt</fileName> </parameters> </component> </components> </configuration>
El metodo Main no habría que modificarlo y quedaría como lo teniamos:
... public static void Main(string[] args) { //Creamos el contenedor miContenedor = new WindsorContainer( new XmlInterpreter("MiContenedor.xml")); //Pedimos un objeto de tipo sumador al contenedor var sumador = miContenedor.Resolve<ISumador>(); var result = sumador.Suma(1,1); }
Al realizar una petición, Windsor Container busca en el contendor el componente que cumpla esa petición, una vez encontrado buscará las dependencias de ese componente en el contenedor, y así sucesivamente, si alguna de las dependencias no pueden ser satisfechas Windsor Container devolverá una excepción. En nuestro caso encontaría el componente con id "Sumador" que es del tipo "IoC.Tutoriales.Part2.Sumador", este clase tiene una dependecia con la clase "IoC.Tutoriales.Part2.ILogger", así que buscará dentro del contenedor si existe algún componente que satisfaga esta dependecia, y encontraría el comoponente con id "Logger". De forma que para crear un objeto de tipo "IoC.Tutoriales.Part2.Sumador" primero creará un objecto de tipo "IoC.Tutoriales.Part2.FileLogger".
Un pequeño refactoring.
Supongamos ahora que en vez de guardar el log en un fichero lo debemos guardar en base de datos. Lo primero sería generar las clases necesarias para guardar el log en base de datos:
public class DBLogger : ILogger { private string connectionString; public DBLogger(string connectionString) { this.connectionString = connectionString; } public void Debug(string text) { //Graba el texto en la base de datos .... } }
Ahora lo único que tenemos que hacer es cambiar el fichero de configuración de Windsor Container para que tenga en cuenta el nuevo logger:
<configuration> <components> <component id="Sumador" type="IoC.Tutoriales.Part2.Sumador, IoC.Tutoriales.Part2" service="IoC.Tutoriales.Part2.ISumador, IoC.Tutoriales.Part2" /> <component id="Logger" type="IoC.Tutoriales.Part2.DBLogger, IoC.Tutoriales.Part2" service="IoC.Tutoriales.Part2.ILogger, IoC.Tutoriales.Part2"> <parameters> <connectionString>...</connectionString> </parameters> </component> </components> </configuration>
Con estos pequeños cambios todo lo demás funcionaría de la misma forma, lo único que cambiará sería que al satisfacer la interfaz ILogger en vez de crear un objeto FileLogger creará un objeto DBLogger. Como podemos ver el hecho de trabajar contra interfaces y utilizar Windsor Container facilita enormemente cualquier tipo de refactoring. El código del ejemplo se encuentra en este link.

sábado, 30 de agosto de 2008

Windsor Container Part. I

Introducción.
Windsor Container es un "Inversion of Control Container" que forma parte del conjunto de soluciones open source de Castle Project junto a Monorail, Active Record y MicroKernel.

Pero vayamos por partes. Antes de explicar que es Windsor Container deberiamos saber que es la "Inversion of Control" (IoC). Este es un concepto muy poco extendido en el mundo .NET, y a aún menos utilizado, debido principalmente a la elevada curva de aprendizaje para su utilización en un desarrollo.
La idea básica de la IoC es que sea el framework quien realice la invocación a los objectos hechos por los programadores y no al revés, lo que implica que es el framework quien tiene el control de los objectos que se invocan y no nosotros. Windsor Container es un contenedor de objectos que utiliza el principio IoC para crear los objectos de las clases y sus dependencias. Esto reduce el acoplamiento del sistema, facilita el reuso de los objetos, el testeo y la realización de refactoring.

Como utilizar Windsor Container.
Como toma de contacto con Windsor Container escribiremos una clase muy sencilla, la clase Sumador, que como su nombre indica su objectivo es sumar dos numeros:
public class Sumador { public decimal Suma(decimal num1, decimal num2) { return num1 + num2; } }
Sin Windsor Container(WC) para utilizar esta clase escribiriamos código de este tipo:
... public static void Main(string[] args) { var sumador = new Sumador(); var result = sumador.Suma(1,1); } ...
Al utilizar WC ya que los objetos se encuentran en un contenedor, no debemos instanciar nosotros los objectos sino que debemos pedirselos al contenedor. Primero debemos construir el contenedor, este se creará a partir de un fichero xml de configuración, que explicaremos más abajo. Una vez nuestro contenedor ya ha sido creado, pedimos al contenedor un objeto de tipo Sumador y por último lo utilizamos.
... public static WindsorContainer miContenedor; public static void Main(string[] args) { //Creamos el contenedor miContenedor = new WindsorContainer (new XmlInterpreter("MiContenedor.xml")); //Pedimos un objeto de tipo sumador al contenedor var sumador = miContenedor.Resolve<Sumador>(); var result = sumador.Suma(1,1); } ...
Como vemos el contenedor debe ser una variable estática con el fin de utilizar siempre el mismo contenedor durante todo el ciclo de vida de la aplición. No tendría sentido ir creado un contenedor cada vez que tenemos que crear un objeto. El fichero de configuración de WC es un fichero xml en el que se especifican todos los objetos que están incluidos en el contenedor. Este fichero tiene el siguiente formato:
<configuration> <components> <component id="Sumador" type="IoC.Tutoriales.Part1.Sumador, IoC.Tutoriales.Part1" /> </components> </configuration>
Los objectos a incluir se especifican mediante el tag "component" (incluido dentro de components), para cada componente hay que definir un identificador único (id) dentro del contenedor y el tipo de clase del objeto a la que pertenece (type ="nombre de clase, nombre del assembly"). Esta configuración se puede incluir dentro del App.config, aunque mi consejo es que esté en un fichero a parte, ya que lo ficheros de configuración xml de windsor son suceptibles de hacerse muy grandes y de ser modifcados habitualmente. Espero que os sirva de ayuda esta pequeña introducción al mundo IoC, en los siguientes artículos sobre IoC explicaremos cosas más interesantes que seguro serán de más utilidad. El código de ejemplo de este tutorial se encuentra en este link.