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.