Sei sulla pagina 1di 82

Testing Unitario con Microsoft Fakes - Prlogo

Page 1 of 82

Testing Unitario con Microsoft Fakes - Prlogo


La informacin contenida en este documento representa la visin Microsoft Corporation sobre los asuntos analizados a la fecha de publicacin. Dado que Microsoft debe responder a las condiciones cambiantes del mercado, no debe interpretarse como un compromiso por parte de Microsoft, y Microsoft no puede garantizar la exactitud de la informacin presentada despus de la fecha de publicacin. Este documento es slo para fines informativos. MICROSOFT NO OFRECE NINGUNA GARANTA, EXPRESA, IMPLCITA O LEGAL, EN CUANTO A LA INFORMACIN CONTENIDA EN ESTE DOCUMENTO. Microsoft publica este documento bajo los trminos de la licencia Creative Commons Attribution 3.0 License. Todos los dems derechos estn reservados. 2013 Microsoft Corporation. Microsoft, Active Directory, Excel, Internet Explorer, SQL Server, Visual Studio, and Windows son marcas comerciales del grupo de compaas de Microsoft. Todas las dems marcas son propiedad de sus respectivos dueos

The information contained in this document represents the current view of Microsoft Corporation on the issues discussed as of the date of publication. Because Microsoft must respond to changing market conditions, it should not be interpreted to be a commitment on the part of Microsoft, and Microsoft cannot guarantee the accuracy of any information presented after the date of publication. This document is for informational purposes only. MICROSOFT MAKES NO WARRANTIES, EXPRESS, IMPLIED OR STATUTORY, AS TO THE INFORMATION IN THIS DOCUMENT. Microsoft grants you a license to this document under the terms of the Creative Commons Attribution 3.0 License. All other rights are reserved. 2013 Microsoft Corporation. Microsoft, Active Directory, Excel, Internet Explorer, SQL Server, Visual Studio, and Windows are trademarks of the Microsoft group of companies. All other trademarks are property of their respective owners.

Page 2 of 82

Testing Unitario con Microsoft Fakes - Prlogo

ndice
Prlogo........................................................................................................................................................................................................................................6 Introduccin ..............................................................................................................................................................................................................................7

Audiencia ............................................................................................................................................................................................. 7 Visual Studio ALM Rangers .......................................................................................................................................................... 7 Autores ................................................................................................................................................................................................. 8 Uso del cdigo fuente, erratas y soporte ............................................................................................................................... 8
Captulo 1: Breve teora sobre Testing Unitario .........................................................................................................................................................9

Testing de software .............................................................................................................................................................................. 9 Estrategias de testing ..................................................................................................................................................................... 9 Tipos de tests ..................................................................................................................................................................................... 9 La delgada lnea entre buen y mal test unitario ..................................................................................................................... 11 Porqu son importantes los test unitarios ........................................................................................................................... 11 Define un paradigma de desarrollo guiado por tests unitarios................................................................................... 11 Checklist de un test unitario ...................................................................................................................................................... 13
Captulo 2: Introduccin a Microsoft Fakes .............................................................................................................................................................. 14

Stubs ......................................................................................................................................................................................................... 14 Qu testeamos? ............................................................................................................................................................................ 14 Por qu usar Stubs? ..................................................................................................................................................................... 15 Shims ........................................................................................................................................................................................................ 16 Elegir entre un stub o un shim ....................................................................................................................................................... 18
Captulo 3: Migrando a Microsoft Fakes .................................................................................................................................................................... 19

Migrando de Moles a Microsoft Fakes ....................................................................................................................................... 19 Cambiando referencias ................................................................................................................................................................ 19 La forma de hacer fakes ............................................................................................................................................................... 19 Definiendo comportamientos ................................................................................................................................................... 20 Moles y Microsoft SharePoint ................................................................................................................................................... 20 Migrando de frameworks comerciales y open source ......................................................................................................... 20 Introduccin ..................................................................................................................................................................................... 20 Diferencias entre estos productos y Microsoft Fakes ...................................................................................................... 21 Migrando desde Moq ................................................................................................................................................................... 22 Migrando desde RhinoMocks ................................................................................................................................................... 32 Conclusin ......................................................................................................................................................................................... 35
Captulo 4: FAQ ..................................................................................................................................................................................................................... 36

Trabajando con .NET Framework 4 .............................................................................................................................................. 36


Page 3 of 82

Testing Unitario con Microsoft Fakes - Prlogo


Adoptando Microsoft Fakes en un equipo ............................................................................................................................... 36 No se pueden hacer fakes de todo! ........................................................................................................................................... 36 Logging detallado ............................................................................................................................................................................... 37 Trabajando con assemblies con strong names ....................................................................................................................... 37 Optimizando la generacin de Fakes .......................................................................................................................................... 38 Mirando bajo las sbanas ................................................................................................................................................................ 39 Refactorizando cdigo bajo test ................................................................................................................................................... 40 Eliminar Fakes de un proyecto ....................................................................................................................................................... 40 Uso de Fakes con el control de versiones de Team Foundation ...................................................................................... 42 Excluir Fakes usando Team Explorer ................................................................................................................................... 43 Excluir Fakes con .tfignore ....................................................................................................................................................... 43 Uso de Microsoft Fakes con ASP.NET MVC .............................................................................................................................. 44 Uso de Stubs con ASP.NET MVC .............................................................................................................................................. 44 Usando Shims con ASP.NET MVC ............................................................................................................................................ 45
Captulo 5: Tcnicas avanzadas ...................................................................................................................................................................................... 47

Tratando con servicios Windows Communication Foundation (WCF) ........................................................................... 47 Rompiendo la dependencia ....................................................................................................................................................... 47 Mejorando la situacin ................................................................................................................................................................ 48 Tratando con clculos no deterministas .................................................................................................................................... 49 Operaciones basadas en Timer ................................................................................................................................................. 49 Datos no repetibles ....................................................................................................................................................................... 49 Recopilacin de casos de uso y ms informacin analtica. ............................................................................................... 50 Validar detalles de implementacin ....................................................................................................................................... 50 Analizando el estado interno .......................................................................................................................................................... 51 Evitando la duplicacin de estructuras de testing ................................................................................................................. 52
Captulo 6: Hands-on Lab................................................................................................................................................................................................. 53

Ejercicio 1: Usando Stubs para aislarnos del acceso a la base de datos (20 30 min) ........................................... 53 Dependencias del entorno ......................................................................................................................................................... 53 Patrn de implementacin ......................................................................................................................................................... 53 Pasos a realizar ................................................................................................................................................................................ 54 Paso 1 Revisar la solucin de inicio ..................................................................................................................................... 54 Paso 2 Prepara el proyecto de test para Microsoft Fakes Stubs .............................................................................. 55 Paso 3 Aade el assembly de Fake al proyecto de test............................................................................................... 55 Paso 4 Revisa y actualiza el archivo xml de Fakes ......................................................................................................... 56 Paso 5 Revisa las clases del modelo y del controlador del MainWeb ................................................................... 56 Paso 6 Crear un mtodo de test unitario .......................................................................................................................... 57
Page 4 of 82

Testing Unitario con Microsoft Fakes - Prlogo


Paso 7 Ordena el mtodo y crea un Stub para la interfaz Repository .................................................................. 58 Paso 8 Llama al mtodo de accin del controlador y comprueba los resultados ............................................ 60 Paso 9 Completar la implementacin de la accin del controlador ...................................................................... 61 Paso 10 Ejectuar el test unitario ............................................................................................................................................ 62 Ejercicio 2: Usando Shims para aislarnos del sistema de archivos y de la fecha (20 30 min) ............................ 63 Escenario ............................................................................................................................................................................................ 63 Paso 1 Revisar la clase LogAggregator .............................................................................................................................. 63 Paso 2 Crea un proyecto de test........................................................................................................................................... 64 Paso 3 Crea el primer test ....................................................................................................................................................... 64 Paso 4 Aadir shims como fake del sistema de archivos ........................................................................................... 65 Paso 5 Aadir un Shim para aislarnos de la fecha del sistema ................................................................................ 68 Paso 6 (Opcional) Ejectua el test con el debugger para entender el flujo de ejecucin. .............................. 68 Ejercicio 3: Usando Microsoft Fakes con SharePoint (20 30 min)................................................................................. 69 Escenario ............................................................................................................................................................................................ 69 Preparacin ....................................................................................................................................................................................... 69 Paso 1 Crear una caracterstica de ejemplo de SharePoint ....................................................................................... 69 Paso 2 Crear un test ................................................................................................................................................................... 70 Ejercicio 4: Haciendo testable cdigo heredado (20 30 min) ........................................................................................ 74 Escenario ............................................................................................................................................................................................ 74 Paso 1 Crear un proyecto de test para el componente Traffic.Core ...................................................................... 74 Paso 2 Crear un test para la propiedad City.Run ........................................................................................................... 74 Paso 3 Aadir las referencias de Fakes al assembly Traffic.Core ............................................................................. 75 Paso 4 Modificar el test unitario para la propiedad Run ............................................................................................ 75 Paso 5 Aadir una referencia Fake de la clase System.Timer .................................................................................... 77 Paso 6 Crear un test unitario para el constructor de Car............................................................................................ 78 Paso 7 Aade un test para la propiedad Car.ShouldMove ........................................................................................ 79 Paso 8 Intentando hacer un shim de la clase DiscoveredRoutes ............................................................................ 81
Conclusin .............................................................................................................................................................................................................................. 82

Page 5 of 82

Testing Unitario con Microsoft Fakes - Prlogo

Prlogo
En equipos de desarrollo modernos, el valor de un testeo unitario efectivo y eficiente es algo en lo que estn todos de acuerdo. Tests rpidos, seguros y automticos que permiten a los desarrolladores comprobar que su cdigo hace lo que ellos piensan que debe hacer, incrementan significativamente la calidad general del cdigo. Sin embargo, crear test unitarios buenos y efectivos es ms difcil de lo que parece. Un buen test unitario es como un buen experimento cientfico: asla tantas variables como sea posible (llamadas variables de control) y valida o rechaza una hiptesis sobre lo que ocurre cuando una variable (la independiente) cambia. Para poder crear cdigo con este tipo de aislamiento hay que prestar especial atencin al diseo y a los patrones que usan los desarrolladores. En algunos casos, el cdigo est diseado de manera que aislar un componente de otro es fcil. Sin embargo, en la mayora de los casos, conseguir este nivel de aislamiento es muy difcil. De hecho, es tan difcil que para algunos desarrolladores es algo imposible. Incluido por primera vez en Visual Studio 2012, Microsoft Fakes nos ayuda a mitigar esta dificultad. Hace ms fcil y rpido crear test unitarios bien aislados cuando ya tenemos sistemas que son testables, permite centrarnos en escribir los test adecuados sin escribir test acoplados. Tambin nos permite aislar y testear cdigo que tradicionalmente no es fcil de testear, usando la tecnologa conocida como Shims. Shims permite eliminar las dependencias complejas y reemplazarlas por algo que podemos controlar. Como ya hemos comentado, ser capaz de crear estas variables de control es muy importante cuando creamos test unitarios rpidos y de calidad. Shims ofrece una forma de evitar muchas de las dificultades que nos encontramos cuando queremos hacer tests unitarios de nuestro cdigo. Como con todas las herramientas de este tipo, hay algunos patrones, tcnicas y conceptos que pueden llevar tiempo aprender. Este documento no es ms que un empujn para adquirir ese conocimiento compartiendo numerosos ejemplos y tcnicas que permitan usar de manera adecuada Microsoft Fakes en vuestros proyectos. Nos alegra escribir el prlogo de esta gua producida por los Visual Studio ALM Rangers. Estamos seguros de que os ayudar a entender el poder y las capacidades que Microsoft Fakes ofrece a la hora de crear mejores test unitarios y mejor cdigo. Peter Provost- Program Manager Lead, Visual Studio ALM Tools Joshua Weber Program Manager, Visual Studio ALM Tools

Page 6 of 82

Testing Unitario con Microsoft Fakes - Introduccin

Introduccin
Bienvenidos a Mejor Unit Testing con Microsoft Fakes 1 en el que, los ALM Rangers, os acompaaremos en un viaje fascinante para descubrir una nueva, excitante y poderosa caracterstica introducida en Visual Studio 2012.
NOTE
Esta gua se basa en Visual Studio 2012 Update 1

Audiencia
Developers! Developers! Developers! Esperamos que nuestra audiencia sean mayoritariamente desarrolladores. Veremos algunos conceptos bsicos sobre tests unitarios pero esperamos que la mayora de nuestros lectores ya tengan alguna experiencia en escribir tests unitarios. Sin duda, cierta experiencia previa con algn otro framework de mocking sera muy positiva. Sin embargo, si ests valorando adoptar Microsoft Fakes como tu primera solucin de mocking, creemos que esta gua te ayudar a implementar una solucin de mocking con Microsoft Fakes de manera adecuada. Si es la primera vez que oyes trminos como test unitarios y mocking, esta gua tambin es muy buena para introducirte en estos conceptos que todos los desarrolladores necesitan en su caja de herramientas.

Qu necesitas?
Las siguientes ediciones de Visual Studio soportan Microsoft Fakes y son las que se han usado en esta gua: Visual Studio Ultimate 2012 Visual Studio Premium 2012 (Es necesario Visual Studio 2012 Update 2)

Para escribir test unitarios con Microsoft Fakes necesitars una edicin soportada de Visual Studio. La ejecucin de estos test en un servidor de builds tambin requiere una edicin soportada. Es posible ejecutar los test con Team Foundation Server 2010 y quizs con versiones anteriores. Sin embargo, para una buena experiencia, recomendamos usar Team Foundation Server 2012. Los test de Microsoft Fakes se pueden compartir y ensear a otros miembros del equipo que no estn usando una edicin soportada de Visual Studio, pero no podrn ejecutarlos.

Visual Studio ALM Rangers


Los Visual Studio ALM Rangers son un grupo especial compuesto por miembros del grupo de producto de Visual Studio, de Microsoft Services, de Microsoft Most Valuable Professionals (MVP) y de Visual Studio Community Leads. Su misin es ayudar a la comunidad. La lista de Rangers est creciendo y la podis ver online 2.

1 2

http://msdn.microsoft.com/en-us/library/hh549175.aspx http://blogs.msdn.com/b/willy-peter_schaub/archive/2012/06/22/introducing-the-visual-studio-alm-rangers.aspx

Page 7 of 82

Testing Unitario con Microsoft Fakes - Introduccin

Autores
Brian Blackman, Carsten Duellmann, Dan Marzolini, Darren Rich, David V. Corbin, Hamid Shahid, Hosam Kamel, Jakob Ehn, Joshua Weber, Mehmet Aras, Mike Fourie, Patricia Wagner, Richard Albrecht, Richard Fennell, Rob Jarratt, Shawn Cicoria, Waldyr Felix, Willy-Peter Schaub

Uso del cdigo fuente, erratas y soporte


Todo el cdigo fuente de la gua est disponible en la pgina de Visual Studio Test Tooling Guidance 3 con las ltimas correcciones y actualizaciones. Los Hands-on Labs usan la propiedad Nuget Package Restore 4. Tendris que habilitarlo en las opciones de Visual Studio si an no lo habis hecho:

Expresiones Lambda
Una expresin lambda es una forma concisa de escribir funciones annimas que podemos usar para crear delegados o expresiones arbreas. Con las expresiones lambda, podemos escribir funciones locales que se pueden pasar como parmetros o que se pueden devolver como valor de retorno de una funcin. Haremos un uso extenso de estas expresiones en el cdigo de ejemplo. Si eres nuevo en el tema de las expresiones lambda, te recomendamos que leas la seccin de MSDN dedicada a ello Lambda Expresions (C# Programming Guide) 5

3 4 5

http://vsartesttoolingguide.codeplex.com http://blog.nuget.org/20120518/package-restore-and-consent.html http://msdn.microsoft.com/en-us/library/bb397687.aspx

Page 8 of 82

Testing Unitario con Microsoft Fakes - Captulo 1: Breve teora sobre Testing Unitario

Captulo 1: Breve teora sobre Testing Unitario


Antes de entrar en los detalles tcnicos de Microsoft Fakes, veremos un poco de teora para recordar cosas a los lectores experimentados y para hacer que los nuevos aprendan las nociones bsicas y necesarias sobre Testing Unitario. Hay dos grandes corrientes de pensamiento entre los que se dedican al Testing Unitario: Debera o no debera cambiar el diseo del cdigo existente para que sea ms testable? La respuesta a esta pregunta tiene un impacto directo sobre cmo un framework de aislamiento como Microsoft Fakes es usado por el equipo, todo gira en cmo se asla el cdigo que se va a testear de sus dependencias. Unos dicen que cambiar el diseo para que el cdigo sea ms testable es bueno ya que ayuda a conseguir un diseo ms desacoplado y cohesivo. En este caso, se usan tcnicas para sustituir clases concretas para testear cdigo heredado (legacy code) que no fue diseado para que fuese testable (en Microsoft Fakes esto se hace a travs de Shims). Otros dicen que los tests no deben comprometer el diseo. En este caso, el cdigo nuevo tiende a hacer uso de clases ms concretas; para testarlo se requieren tcnicas especiales para sustituir las clases concretas en los test unitarios. La vertiente que elijas es cosa tuya y est fuera del alcance de esta gua. Por lo tanto, no vamos a valorar ni recomendar ninguna de las dos. Independientemente de tu eleccin, est generalmente aceptado que los tests unitarios deben ser pequeos y rpidos.

Testing de software
El testing de software es el arte de medir y mantener la calidad del software para asegurar que las expectativas y requerimientos del usuario, el valor de negocio, los requisitos no funcionales como la seguridad, confiabilidad y tolerancia a fallos, y las polticas operacionales se cumplan. El testing es un esfuerzo del equipo para conseguir un mnimo de calidad y tener una definicin de hecho entendida y aceptada por todos.
Definicin de hecho es una definicin del equipo y un compromiso de calidad que se aplicar a la solucin en cada iteracin (tambin se puede definir en el nivel de Tareas o de Historias de Usuario). Ten en cuenta el diseo, las revisiones de cdigo, refactorizacin y testing cuando discutis y definis vuestra definicin de hecho.

Estrategias de testing
Las estrategias de testing se dividen tradicionalmente en testing de caja negra, blanca y gris.
Estrategia Caja Negra Caja Blanca Caja Gris Descripcin El contenido de la caja (la implementacin de la solucin) es oscura. Los testers slo se centran en la entrada y en la salida, normalmente cuando se realizan test de sistema o de aceptacin de usuario. El contenido de la caja es visible y se puede analizar como parte del testing. Es una combinacin de caja blanca y negra que se usa normalmente en casos especiales, en los que se requiere una comprensin de cmo funciona por dentro y cmo debe comportarse.

Tipos de tests
No hay una bala de plata cuando hablamos de testing (ver Figura 2). Hay veces que se necesita de interaccin humana y feedback, otras es necesario automatizar la ejecucin de estos test y otras hay que ejecutarlos manualmente.
Page 9 of 82

NOTA

Testing Unitario con Microsoft Fakes - Captulo 1: Breve teora sobre Testing Unitario

Estrategia Test exploratorio

Descripcin El tester imagina posibles escenarios que no se hayan cubierto por otros test. Es muy til cuando se observa al usuario usando el sistema. NO hay test predefinidos Testear diferentes componentes de la solucin trabajando como si fueran uno Testear cmo se comporta el sistema con cargas de trabajo en un entorno controlado Asegura que el sistema mantiene los mnimos de calidad despus de hacer cambios como corregir bugs. Usa una mezcla de tests unitarios y de sistema Se usa para testear una nueva caracterstica o idea antes de subir al repositorio de cdigo los cambios Testea el sistema completo con caractersticas fijas y comprueba los requerimientos no funcionales Un test de la unidad ms pequea de cdigo (mtodo, clase, etc.) que puede ser testeada de manera aislada del sistema. Ver Verifying Code By Using Unit Test y La delgada lnea entre buen y mal test unitario en esta gua para ms informacin Cuando se acerca el final de los ciclos del producto, se invita a los usuarios a que realicen test de aceptacin en escenarios reales, normalmente basados en casos de tests

Herramienta de Visual Studio Testing exploratorio con Microsoft Test Manager (MTM) Visual Studio Unit Test Visual Studio Load Test Agent Testing automtico con MTM

Test de integracin Test de carga Test de Regresin

Smoke Test Test de sistema Test unitario

Visual Studio Lab Management Visual Studio Test Explorer. Frameworks de test unitarios

Test de aceptacin de usuario

Test automtico con MTM

Page 10 of 82

Testing Unitario con Microsoft Fakes - Captulo 1: Breve teora sobre Testing Unitario

La delgada lnea entre buen y mal test unitario


En lugar de centrarnos en los defectos de los test unitarios, hemos decidido presentar una lista muy concisa con los puntos clave que te ayudarn a disear, desarrollar e implementar buenos test unitarios.
Advertencia: El testeo unitario se basa en pequeas porciones de cdigo, de manera que slo usando test unitarios es probable que no se incremente la calidad del producto. Recomendamos su uso junto a otras tcnicas de tests descritas en esta gua. En otras palabras, los test unitarios no son una bala de plata, pero es un ingrediente muy importante en una estrategia de test completa.

Porqu son importantes los test unitarios


Cuando alguien pregunta por qu son importantes los tests unitarios, le solemos preguntar si creen que es importante que un avin comercial que usan habitualmente para cruzar ocanos sea testeado meticulosamente antes de cada vuelo. Es importante que el avin sea testeado? Si, ok, ahora, es importante que el altmetro sea testeado aparte? Por supuesto. Eso es el test unitario testear el altmetro. No garantiza que el avin vaya a volar, pero no puede volar de forma segura sin l. La importancia de los test unitarios, empieza con el proyecto y contina durante todo el ciclo de vida de la aplicacin, tambin depende de si estamos buscando: NOTA

AVISO

Cdigo de calidad desde el principio Menos bugs Cdigo auto explicativo Reducir el coste de corregir errores encontrndolos lo antes posible Sentido de responsabilidad del cdigo, estar orgullosos de nuestro cdigo
El valor principal del desarrollo guiado por test y de los test unitarios es asegurarse de que el equipo piensa e incluso suea con el cdigo, estudia los requerimientos, y evitan posibles problemas proactivamente. Automatiza tus tests: Plantate automatizar tus test. Esto te permitir testear rpida y automticamente cambios de cdigo incluso a medida que se escribe. Sin embargo, la automatizacin de los tests tiene sus desafos y te recomendamos que investigues sobre el tema (http://blogs.msdn.com/b/steverowe/archive/2008/02/26/when-to-test-manually-andwhen-to-automate.aspx)

Define un paradigma de desarrollo guiado por tests unitarios


Recomendamos una estrategia de tests unitarios conocida como RED (fallo) GREEN (xito), es especialmente til en equipos de desarrollo gil. Una vez que entendamos la lgica y la intencin de un test unitario, hay que seguir estos pasos:

Page 11 of 82

Testing Unitario con Microsoft Fakes - Captulo 1: Breve teora sobre Testing Unitario

1.

2.

3.

Escribe el cdigo del test (Stub) para que compile (pase de RED a GEEN) a. Inicialmente la compilacin fallar RED debido a que falta cdigo b. Implementa slo el cdigo necesario para que compile GREEN (an no hay implementacin real). Escribe el cdigo del test para que se ejecute (pase de RED a GREEN) a. Inicialmente el test fallar RED ya que no existe funcionalidad. b. Implementa la funcionalidad que va a probar el test hasta que se ejecute adecuadamente GREEN. Refactoriza el test y el cdigo una vez que este todo GREEN y la solucin vaya evolucionando.
Echad un vistazo a Guidelines for Test-Driven Development(http://msdn.microsoft.com/enus/library/aa730844(v=vs.80).aspx) y Testing for Countinous Delivery with Visual Studio (http://msdn.microsoft.com/en-us/library/jj159345.aspx) para conocer ms buenas prcticas a la hora de escribir tests unitarios

NOTA

Page 12 of 82

Testing Unitario con Microsoft Fakes - Captulo 1: Breve teora sobre Testing Unitario

Checklist de un test unitario


Check Convencin de nombres descriptivos Descripcin Establece una convencin de nombres ClassName_Purpose_ExpectedResult, consiguiendo consistente y con una intencin clara. Ejemplos: Documenta los tests Credentials_UserNameLength_Succeed() Credentials_UserNameLength_Fail() descriptivos como un estilo de tests Check

Los test unitarios prueban y validan caractersticas de negocio. El propio cdigo es una documentacin viva del sistema. Recomendamos el uso combinado de cdigo conciso y documentacin mnima, para conseguir un test unitario entendible, mantenible y autodescriptivo. Usa mensajes descriptivos para mejorar la lectura del cdigo y el log de compilacin. Ejemplos: Content submission failed due to too many spelling errors. La interfaz define el contrato, permitiendo el uso de stubs y tests de caja negra. El test unitario debe ser tan simple, limpio y conciso como sea posible. Los tests simples son un valor aadido, si no, se ignorarn y no se mantendrn Un test unitario est centrado en la mnima unidad de cdigo (mtodo, clase, etc.) que puede ser probada de manera aislada, no a nivel de sistema o de integracin. Para mantener un test y su infraestructura asociada centrada, evita probar cosas entre los diferentes niveles de aplicacin o de sistema. Cada test debe tener slo una asercin lgica. Debe validar el test, como se indica en su nombre. Una asercin lgica puede contener una o varias sentencias de assert. El cdigo debe ser organizado y mantenido como si fuese una clase de primer nivel, al igual que el resto de la solucin. Su cdigo debe basarse en los mismos requisitos de buenas prcticas, calidad y estilo de cdigo de la compaa. El test unitario debe cubrir todos los posibles escenarios y busca siempre una amplia cobertura de cdigo. Testear los casos lmites y las excepciones es responsabilidad del testador, no del usuario final!

Errores y aserciones descriptivas Adopta el desarrollo contra interfaces Mantenlo simple Mantenlo centrado

Tiene una sola asercin lgica Organiza y mantn los tests

Testea los casos buenos, malos y los lmite

Page 13 of 82

Testing Unitario con Microsoft Fakes - Captulo 2: Introduccin a Microsoft Fakes

Captulo 2: Introduccin a Microsoft Fakes


Microsoft Fakes es un nuevo framework de aislamiento que nos permite aislar el cdigo a testear reemplazando otras partes de la aplicacin con stubs o shims. Nos permite testear partes de nuestra solucin incluso si otras partes de nuestra aplicacin no han sido implementadas o an no funcionan. Microsoft Fakes viene con dos sabores: Stubs reemplaza una clase con un sustituto (stub) que implementa la misma interfaz. Shims modifica el cdigo compilado en tiempo de ejecucin, para inyectar y ejecutar un sustituto (shim).

Como vemos en la figura, los stubs son usados normalmente para llamadas en nuestro sistema que podemos desacoplar usando interfaces; los shims se usan para llamadas a assemblies que no estn bajo nuestro control:

Stubs
Vamos a ver ms detenidamente a los Stubs para poder empezar a integrarlo en nuestro ciclo de vida.

Qu testeamos?
En la siguiente figura, tenemos un ejemplo tpico de una aplicacin en N-capas. La complejidad de cualquier solucin depende del nivel de caractersticas que dicha solucin ofrece, y para nuestro ejemplo, lo vamos a simplificar. Vamos a centrarnos en el aislamiento dentro de lo razonable de los componentes que forman la base de nuestro sistema:

Page 14 of 82

Testing Unitario con Microsoft Fakes - Captulo 2: Introduccin a Microsoft Fakes

Para el aislamiento, vamos a los componentes individuales que se ejecutan en la capa de lgica de negocio (puede que tu terminologa vare). Nuestra estrategia de testeo no es testear la lgica en la base de datos, ni la lgica de los servicios WCF/Rest, ni el navegador, etc.

Test dobles
Los tests dobles nos permiten aislar el cdigo bajo test de sus dependencias. Juegan un papel muy importante en la evolucin de la calidad de nuestro cdigo y aumenta su testabilidad. Los stubs, un tipo de test doble, requieren que el cdigo que se va a probar est diseado de manera que permita separar y desacoplar dependencias. Es importante diferenciar qu es un Stub de qu no lo es. El artculo de Martin Fowler Moks arent Stubs (http://martinfowler.com/articles/mocksArentStubs.html) compara y contrasta los principios que hay debajo de los stubs y los mocks. Como se entiende del artculo, un stub conserva un estado esttico que permite la verificacin de estados (http://xunitpatterns.com/State%20Verification.html ) del sistema en prueba, mientras que un mock ofrece una verificacin de comportamiento (http://xunitpatterns.com/Behavior%20Verification.html) del sistema en prueba. En esta seccin, nos centraremos en los stubs y veremos el valor que aportan a nuestro proceso de desarrollo. Podis leer el artculo de Martin Fowler para conocer ms detalles de los principios y contrastar las dos aproximaciones. Con stubs, podemos aislar el cdigo que se quiere probar junto a unos casos de pruebas que validarn nuestro cdigo de negocio esto es, objetos con un estado especfico que podemos reproducir bajo unas condiciones que podemos controlar y ejecutar. Es importante saber que Microsoft Fakes no ofrece herramientas de verificacin de estado como otros frameworks de mocking como NMoq o Rhino Mocks.

Por qu usar Stubs?


Los procesos de desarrollo y pruebas que incorpora el testing unitario junto al anlisis de cobertura de cdigo nos permite mitigar problemas derivados de la falta de cobertura en otros escenarios de testing ms integrados. Una integracin completa y tests de caja blanca pueden dejar partes significantes del cdigo sin probar, incrementando as las posibilidades de introducir defectos funcionales.

Page 15 of 82

Testing Unitario con Microsoft Fakes - Captulo 2: Introduccin a Microsoft Fakes Aumentando la cobertura de cdigo
Con stubs identificamos condiciones especficas a nivel de unidad e incorporamos este estado en un test o conjunto de test simples. Incrementando as la cobertura de nuestro cdigo. Los problemas y los errores son detectados con los test de integracin, errores de produccin, etc. Estas reas son partes del cdigo que no estn cubiertas por los test unitarios necesarios. En este punto, aadir stubs que simulen estas condiciones o reas de cdigo, ayuda tanto a la cobertura como a la calidad de la cobertura del sistema en test. Adems, estos stubs se convierten en parte de los test unitarios, con la idea de as no volver a pasar. Durante el desarrollo guiado por pruebas (TDD), los problemas se suelen detectar muy pronto, cuando se escriben los primeros tests (test de sistema o aceptacin, o incluso de produccin). Los stubs hacen ms fcil crear test unitarios que aseguren la cobertura de ciertas condiciones aislndolas de sus dependencias. Adems, una buena aproximacin TDD es no escribir el cambio en el cdigo funcional inmediatamente. En lugar de ello, primero se escribe un test unitario que sea capaz de reproducir el estado de los componentes aislados. Primero se incluye dicho test que representa las condiciones de error. El test falla, y luego es el desarrollador, no el tester, el que corrige el defecto en el cdigo funcional que hace que el test pase.

Aislamiento y granularidad
Como vimos en la seccin Qu testeamos?, el aislamiento nos permite centrarnos en una parte del sistema que se est probando sin ninguna dependencia externa. En el diagrama anterior, esto eran los componentes de la capa de la Lgica de Negocio (BLL). El aislamiento reduce la cantidad de cdigo en el que el test se tiene que centrar. Esto reduce, en muchos casos, el cdigo necesario para configurar el test tambin. Son necesarios tambin componentes o entornos aislados si queremos desacoplarnos de estados fsicos y condiciones especiales para reproducir el error, por ejemplo, una base de datos con datos o un archivo con datos de ejemplo.

Mejores componentes hacen mejores sistemas


El principio que se esconde detrs del aislamiento y los stubs en el testing unitario est directamente relacionado con el desarrollo de componentes. En muchas ocasiones, creamos aplicaciones y soluciones dividindolas en componentes. A medida que descomponemos la solucin en partes ms granulares, y si nos preocupamos en aumentar la calidad de esas partes, podemos incrementar considerablemente la calidad de nuestro sistema. Vamos a suponer que estamos de acuerdo en que si creamos algo con componentes de poca calidad reducimos las posibilidades de conseguir una solucin de calidad; y el corolario de que si creamos algo con componentes de calidad aumentamos las posibilidades de conseguir una solucin de calidad. Fijaos que hemos dicho posibilidades. Una solucin de xito no implica que sus componentes sean buenos; lo que estamos intentando decir es que podemos mitigar los riesgos de fallos (que puede traducirse como mala calidad a ojos del cliente) aplicando estas tcnicas.

Shims
Los Shims son una caracterstica de Microsoft Fakes que permiten crear tests unitarios para cdigo que de otra manera no puede ser probado de manera aislada. Al contrario que los Stubs, los Shims no requieren que el cdigo a testear sea diseado de ninguna manera. Para poder usar Stubs, tienen que ser inyectados* en el cdigo que se prueba de alguna manera, as las llamadas a las dependencias no las manejaran componentes reales (cdigo de produccin), sino que ser el Stub. De esta manera, los valores y objetos de test se les pueden pasar al cdigo que se est probando:

Page 16 of 82

Testing Unitario con Microsoft Fakes - Captulo 2: Introduccin a Microsoft Fakes

Pero hay ocasiones en las que el cdigo que se quiere probar no est diseado de manera que permita cambiar sus dependencias, por ejemplo, cuando se llaman a mtodos estticos o cuando se basa en frameworks de terceros. En estos casos, los Shims ofrecen la posibilidad de reemplazar las dependencias interceptando las llamadas a las dependencias en tiempo de ejecucin desvindolas a un cdigo especial con los valores de prueba deseados para el test.
*La tcnica de Inyeccin de Dependencias (DI) se usa en la programacin orientada a objetos para desacoplar clases de sus dependencias o al menos de la implementacin concreta a travs de interfaces. Esta tcnica se puede usar para inyectar stubs para motivos de testing. Esta inyeccin se puede realizar a travs de frameworks (como Spring.NET o Unity) o manualmente inyectando implementaciones concretas con clases. Por ejemplo, creando una instancia de la clase dependiente y pasarla como parmetro en el constructor de la clase que queremos testar (Inyeccin por Constructor). Para que la inyeccin por constructor funcione, el componente dependiente debe tener un constructor apropiado. Para una clase que oculte completamente sus dependencias, DI no funcionar y no se podrn inyectar stubs tampoco

NOTA

Page 17 of 82

Testing Unitario con Microsoft Fakes - Captulo 2: Introduccin a Microsoft Fakes

Elegir entre un stub o un shim


Como ya hemos visto, los Stubs ofrecen implementaciones de interfaces y clases, y son ms efectivos cuando el cdigo a probar est diseado pensando en tests. Los Shims tambin soportan aquellas situaciones en las que el cdigo dependiente, por ejemplo cdigo heredado o externo, no puede cambiarse ofreciendo una forma de interceptar y desviar las llamadas consiguiendo que se ejecute el cdigo que queramos.
NOTA
Cuando sea posible, usad Stubs. Vuestros tests se ejecutarn ms rpido.

Objetivo | Consideracin Buscas el mejor rendimiento? Mtodos abstractos y virtuales Interfaces Tipos internos Mtodos estticos Tipos sellados Mtodos privados

Stub X X X X

Shim X(ms lento)

X X X x

Leed Isolating Code under Test with Microsoft Fakes(http://msdn.microsoft.com/en-us/library/hh549175.aspx) en MSDN para ms informacin.

Page 18 of 82

Testing Unitario con Microsoft Fakes - Captulo 3: Migrando a Microsoft Fakes

Captulo 3: Migrando a Microsoft Fakes


Esperamos que algunos de los que estis leyendo esta gua tengis alguna experiencia con Microsoft Moles (http://research.microsoft.com/en-us/projects/moles/) o con cualquier otro framework de aislamiento comercial u open source. En este captulo, veremos algunos de los pasos y problemas que nos podemos encontrar cuando migremos a Microsoft Fakes. Recordad que podis usar Microsoft Fakes con otros frameworks y podis migrar a vuestro propio ritmo.

Migrando de Moles a Microsoft Fakes


Moles es el proyecto de Microsoft Research en el que se basa Microsoft Fakes, por lo tanto, tiene muchas similitudes en la sintaxis. Sin embargo, no hay un mecanismo automtico para convertir un proyecto que use Moles a otro que use Microsoft Fakes. Con la publicacin de Microsoft Fakes, Moles se ha quedado atrasado y es recomendable que los proyectos basados en Moles se migren a Microsoft Fakes cuando sea posible. La razn principal de esta recomendacin, a parte del soporte, es que cuando instalamos Visual Studio 2012 se instala .NET 4.5, y esto es un problema para Moles porque Moles intenta generar stubs o moles para tipos que slo existen en .NET 4.5. El propio Moles est escrito en .NET 4 y esto gen era errores. El nico workaround es usar filtros en el archivo .moles para evitar la carga de esos tipos (y los tipos dependientes). Este proceso est detallado en el sitio de Microsoft Research (http://research.microsoft.com/enus/projects/moles/molesdev11.aspx). Como es un proceso potencialmente complejo y tendiente a errores, os recomendamos que no intentis ejecutar Moles y Fakes a la vez. Cualquier migracin de Moles a Fakes requiere cambios en el cdigo; sin embargo, dado que las bases de Microsoft Fakes es Moles, dichos cambios no son muchos.
NOTA
Microsoft Fakes no reemplaza a PEX(http://research.microsoft.com/en-us/projects/pex/) y no ofrece la generacin automtica de test unitarios que si ofrece PEX

Cambiando referencias
El primer paso en cualquier migracin de Moles a Fakes es eliminar las referencias a los assemblies y borrar los archivos .moles. El siguiente paso es generar un nuevo assembly de Fake. Esto aadir las referencias necesarias y los archivos .fakes al proyecto de test.

La forma de hacer fakes


La principal diferencia entre Moles y Shims es la forma en la que se define la operacin de fake. En Moles, se coloca un atributo HostType en el mtodo de test: [TestMethod] [HostType("Moles")] public void TestMethod() { //... } En Microsoft Fakes, se hace con una sentencia using que contiene un objeto ShimsContext: [TestMethod] public void FakesTestMethod()
Page 19 of 82

Testing Unitario con Microsoft Fakes - Captulo 3: Migrando a Microsoft Fakes


{ using (ShimsContext.Create()) { //... } Este cambio tiene que hacerse en todos los test basados en moles que vayan a hacer uso de los Shims en Microsoft Fakes. Recordad que no es necesario el using si usamos Stubs.

Definiendo comportamientos
En los test, la forma bsica en la que se definen los comportamientos de los Stubs o Shims no ha cambiado entre Moles y Fakes. Sin embargo, s ha cambiado el nombre de las clases que los definen. Por ejemplo, en Moles, el comportamiento de una llamada a la propiedad Now de DateTime se declarara as:
MDateTime.NowGet = () => { return new DateTime(1, 1, 1); };

Con Microsoft Fakes sera as:


System.Fakes.ShimDateTime.NowGet = () => { return new DateTime(1, 1, 1); };

Como slo es un cambio de namespaces, bastara con una operacin de reemplazar con cualquier editor de texto

Moles y Microsoft SharePoint


Microsoft Research public unas libreras de behaviors para ayudar al testing de SharePoint (http://research.microsoft.com/en-us/projects/pex/pexsharepointbehaviors.pdf). Esta librera es un mock de la API de SharePoint. Cuando migremos a Microsoft Fakes, podemos usar los emuladores de SharePoint, que son una versin de los comportamientos de Moles SharePoint. Podis leer ms sobre estos emuladores en el blog Introducing SharePoint Emulators (http://blogs.msdn.com/b/visualstudioalm/archive/2012/11/26/introducingsharepoint-emulators.aspx) y estn disponibles en Nuget.

Migrando de frameworks comerciales y open source


Introduccin
Hay un gran nmero de proyectos open source, como RhinoMocks, Moq, etc, que ofrecen tecnologas equivalentes para hacer stubs como en Microsoft Fakes. Sin embargo, no ofrecen una tecnologa similar a los shims de Microsoft Fakes. Slo los productos comerciales ofrecen la posibilidad de mockear objetos de clases privadas y selladas. Adems de Microsoft Fakes, este tipo de mocking tambin lo ofrece Telerik en JustMock(http://www.telerik.com/products/mocking.aspx ) y Typemock en el producto que tienen llamado Isolator (http://www.typemock.com/isolator-product-page). En ambos casos se ofrece la misma funcionalidad de stubs que Microsoft Fakes, permitiendo mockear interfaces, etc., en una versin gratuita y recortada. Tambin tienen productos Premium que ofrecen herramientas como los shims para mockear objetos que no son mockeables.

Page 20 of 82

Testing Unitario con Microsoft Fakes - Captulo 3: Migrando a Microsoft Fakes

Diferencias entre estos productos y Microsoft Fakes


Creando assemblies fake
La principal diferencia entre estos productos y Microsoft Fakes es el proceso que un desarrollador tiene que hacer para generar el shim. En Microsoft Fakes, simplemente hacemos clic derecho en la referencia del assembly que queremos mockear y seleccionamos Add Fake Assembly. Esto generar un nuevo assembly que debemos referenciar para crear objetos shims. En los productos de Telerik y Typemock, no es necesaria esta pre-generacin, se encarga el propio framework en tiempo de ejecucin.

Usando Microsoft Fakes


Para crear tests unitarios tanto Telerik como Typemock usan expresiones lambda para definir el comportamiento, igual que Microsoft Fakes. El formato es un poco diferente en cada uno, pero la intencin que se expresa es siempre la misma. Telerik JustMock En este ejemplo vemos cmo mockear una llamada a DateTime.Now usando JustMock de Telerik:
[TestClass] public class MsCorlibFixture { static MsCorlibFixture() { Mock.Replace(() => DateTime.Now).In<MsCorlibFixture> (x => x.ShouldAssertCustomValueForDateTime()); } [TestMethod] public void DateTimeTest() { Mock.Arrange(() => DateTime.Now).Returns(new DateTime(2016, 2, 29)); // Act int result = MyCode.DoSomethingSpecialOnALeapYear(); // Assert Assert.AreEqual(100, result); } }

Typemock isolator Ahora vamos a ver cmo se mockea la misma llamada a DateTime.Now usando Typemock Isolator
[TestMethod, Isolated] public void DateTimeTest() { // Arrange Isolate.WhenCalled(() => DateTime.Now).WillReturn(new DateTime(2016, 2, 29)); // Act int result = MyCode.DoSomethingSpecialOnALeapYear();

Page 21 of 82

Testing Unitario con Microsoft Fakes - Captulo 3: Migrando a Microsoft Fakes


// Assert Assert.AreEqual(100, result); }

Microsoft Fakes Ahora vamos a ver cmo se mockea esa misma llamada con Microsoft Fakes
[TestMethod] public void DateTimeTes() { using (ShimsContext.Create()) { // Arrange: System.Fakes.ShimDateTime.NowGet = () => { return new DateTime(2016, 2, 29); }; // Act int result = MyCode.DoSomethingSpecialOnALeapYear(); // Assert Assert.AreEqual(100, result); } }

Migrando desde Moq


En esta seccin veremos cmo migrar algunas de las caractersticas ms usadas de Moq, Stubs a Microsoft Fakes. La principal diferencia entre Moq y Stubs es que Moq usa interfaces genricas y clases abstractas que tienen que ser stubbeadas y Stubs usa la generacin de cdigo para implementar clases que se derivan de las interfaces y de las clases abstractas. Las clases stub generadas por Microsoft Fakes ofrecen miembros extra que son llamados por las propiedades y mtodos que estn siendo stubbeados para ofrecer valores de retorno o para ejecutar cualquier cdigo que sea necesario. Una de las pequeas diferencias que veremos en el cdigo de los test unitarios es que cuando se accede a los objetos no tendremos que usar el miembro Object de la instancia Mock<T>, ya que el stub de Microsoft Fakes implementa directamente la interfaz o deriva de la clase abstracta que ha sido stubbeada. En el resto de esta seccin veremos algunos ejemplos de cdigo, veremos los escenarios que ofrece Moq y luego veremos cmo cambiarlo para que funcione con Stubs.

Cdigo de ejemplo
En el resto de la seccin, haremos referencia a un cdigo de ejemplo. Usaremos una clase muy simple de cuenta de banco como la que vemos en el siguiente trozo de cdigo
public enum TransactionType { Credit, Debit }; public interface ITransaction { decimal Amount { get; } TransactionType TransactionType { get; } } public interface ITransactionManager { int GetAccountTransactionCount(DateTime date); ITransaction GetTransaction(DateTime date, int transactionNumber);

Page 22 of 82

Testing Unitario con Microsoft Fakes - Captulo 3: Migrando a Microsoft Fakes


void PostTransaction(decimal amount, TransactionType transactionType); event TransactionEventHandler TransactionEvent; } public delegate void TransactionEventHandler(DateTime date, decimal amount, TransactionType tran sactionType);

Cambiando el Setup con Returns


En Moq, el mtodo Setup se usa para crear un stub que responder al conjunto de parmetros de entrada con una salida que le indiquemos. Por ejemplo, si queremos que el objeto ITransactionManager que se pasa al cdigo a testear devuelva un valor concreto cuando se le pase una fecha al mtodo GetAccountTransactionCount, deberemos usar el siguiente cdigo:
DateTime testDate = new DateTime(2012, 1, 1); Mock<ITransactionManager> mockTM = new Mock<ITransactionManager>(); mockTM.Setup(tm => tm.GetAccountTransactionCount(testDate)).Returns(8);

Esto mismo se consigue con Stubs de la siguiente manera:


DateTime testDate = new DateTime(2012, 1, 1); StubITransactionManager stubTM = new StubITransactionManager(); stubTM.GetAccountTransactionCountDateTime = (date) => (date == testDate) ? 8 : default(int);

La clase Stub que crea Fakes ofrece un miembro llamado GetAccountTransactionCountDateTime, que podemos asignar a una expresin Lambda que nos devolver el valor que queremos. Fijaos que esta expresin lambda comprueba el parmetro de entrada de la misma manera que hara Moq. Si se le pasa un valor diferente al indicado, devolver el valor por defecto del tipo. Moq tambin nos permite llamar al mtodo Setup varias veces para devolver valores diferentes para diferentes entradas. Aqu tenis un ejemplo:
// txn1 and txn2 previously set up as mock transactions mockTM.Setup(tm => tm.GetTransaction(testDate, 0)).Returns(txn1.Object); mockTM.Setup(tm => tm.GetTransaction(testDate, 1)).Returns(txn2.Object);

Esto lo podemos conseguir con una expresin lambda algo ms compleja como la siguiente:
// txn1 and txn2 previously set up as stub transactions ITransaction[] stubTransactions = new ITransaction[] { txn1, txn2 }; stubTM.GetTransactionDateTimeInt32 = (date, index) => (index >= 0 || index < stubTransactions.Le ngth) ? stubTransactions[index] : null;

Estamos usando un array para poder corresponder los valores de entrada, de manera que la expresin lambda las busca. En este caso estamos ignorando el parmetro de fecha. Algunas veces, los valores que queremos no se conocen con antelacin y usar una coleccin nos permite inicializar el diccionario dinmicamente, incluso despus de que el stub se halla pasado al cdigo que queremos testear. Un escenario en el que necesitamos hacer esto es cuando queremos centralizar la creacin del cdigo bajo test en un cdigo de inicializacin, pero cada test se ejecuta con diferentes conjuntos de valores. Podramos hacer algo como esto en Stubs:
private StubITransactionManager stubTM = new StubITransactionManager(); private List<ITransaction> transactions = new List<ITransaction>(); private DateTime testDate = new DateTime(2012, 1, 1); private Account cut; [TestInitialize] public void InitializeTest() { this.stubTM.GetTransactionDateTimeInt32 = (date, index) =>

Page 23 of 82

Testing Unitario con Microsoft Fakes - Captulo 3: Migrando a Microsoft Fakes


(date == testDate && index >= 0 || index < this. transactions.Count) ? this.transactions[index] : null; this.stubTM.GetAccountTransactionCountDateTime = (date) => (date == testDate) ? this.transactions.C ount : default(int); this.cut = new Account(stubTM); } [TestMethod] public void StubCreditsSumToPositiveBalance() { // Arrange this.AddTransaction(10m, TransactionType.Credit); this.AddTransaction(20m, TransactionType.Credit); // Act decimal result = this.cut.CalculateBalance(this.testDate); // Assert Assert.AreEqual<decimal>(30m, result); } [TestMethod] public void StubDebitsAndCreditsSum() { // Arrange this.AddTransaction(10m, TransactionType.Credit); this.AddTransaction(20m, TransactionType.Debit); // Act decimal result = this.cut.CalculateBalance(this.testDate); // Assert Assert.AreEqual<decimal>(-10m, result); } private void AddTransaction(decimal amount, TransactionType transactionType) { this.transactions.Add(new StubITransaction { AmountGet = () => amount, TransactionTypeGet = () => transactionType }); }

Por supuesto, Moq tambin es capaz de tratar la configuracin de mtodos con varios parmetros. Pero en Stub es un poco ms complejo crear expresiones lambda que comprueban cada uno de los parmetros. Cuando tenemos varios parmetros de entrada en varios Setups, lo ms simple suele ser usar un diccionario que usa como key un Tuple<T,R>. Aqu tenis un ejemplo:
private StubITransactionManager stubTM = new StubITransactionManager(); private Dictionary<Tuple<DateTime, int>, ITransaction> transactions = new Dictionary<Tuple<DateT ime, int>, ITransaction>(); private DateTime testDate = new DateTime(2012, 1, 1); private Account cut;

Page 24 of 82

Testing Unitario con Microsoft Fakes - Captulo 3: Migrando a Microsoft Fakes


[TestInitialize] public void InitializeTest() { this.stubTM.GetTransactionDateTimeInt32 = (date, index) => { ITransaction txn; if (!this.transactions.TryGetValue(new Tuple<DateTime, int>(date, index), out txn)) { txn = null; } return txn; }; stubTM.GetAccountTransactionCountDateTime = (date) => this.cut = new Account(stubTM); } [TestMethod] public void StubCreditsSumToPositiveBalance() { // Arrange this.AddTransaction(testDate, 0, 10m, TransactionType.Credit); this.AddTransaction(testDate, 1, 20m, TransactionType.Credit); // Act decimal result = this.cut.CalculateBalance(this.testDate); // Assert Assert.AreEqual<decimal>(30m, result); } [TestMethod] public void StubDebitsAndCreditsSum() { // Arrange this.AddTransaction(testDate, 0, 10m, TransactionType.Credit); this.AddTransaction(testDate, 1, 20m, TransactionType.Debit); // Act decimal result = this.cut.CalculateBalance(this.testDate); // Assert Assert.AreEqual<decimal>(-10m, result); } private void AddTransaction(DateTime date, int index, decimal amount, TransactionType transactio nType) { ITransaction txn = new StubITransaction { AmountGet = () => amount, TransactionTypeGet = () => transactionType };

Page 25 of 82

Testing Unitario con Microsoft Fakes - Captulo 3: Migrando a Microsoft Fakes


this.transactions.Add(new Tuple<DateTime, int>(date, index), txn); }

Moq tambin nos ofrece varias formas de enlazar varios valores de entrada en una sola llamada al Setup. Por ejemplo:
mockTM.Setup(tm => tm.GetTransaction(It.IsAny<DateTime>(), It.IsAny<int>())) .Returns(txn.Object);

En este caso, un Stub podra simplemente ignorar el parmetro apropiado en la lambda. En el caso anterior podra ser algo as:
stubTM.GetTransactionDateTimeInt32 = (date, index) => txn1;

Migrando los Callbacks


Los Callbacks nos permiten registrar un mtodo que se ejecutar cuando ocurra otra accin. Tanto Moq como Stubs nos permiten especificar mtodos de callback en el propio test unitario. Si por ejemplo, queremos llamar a un mtodo como este en nuestra clase de test:
bool callBackCalled = false; public void CallBackMethod(decimal param) { callBackCalled = true; }

En Moq, usaremos el mtodo .Setup como en el ejemplo anterior. Sin embargo, en lugar de la llamada al .Returns llamaremos al .Callback para indicar cul es el mtodo que se tiene que ejecutar, pasando los parmetros que hagan falta de manera similar a como lo haramos en el mtodo Returns:
[TestMethod] public void MoqCallback() { // arrange Mock<ITransactionManager> mockTM = new Mock<ITransactionManager>(); mockTM.Setup(tm => tm.PostTransaction(It.IsAny<decimal>(), It.IsAny<TransactionType>())) .Callback<decimal, TransactionType> ((amount, transType) => CallBackMethod(amount)); Account cut = new Account(mockTM.Object); // act cut.AddCredit(9.99m); // assert Assert.AreEqual(true, callBackCalled); }

Con Stubs, el callback se declara como un delegado:


[TestMethod] public void StubCallback() { // arrange StubITransactionManager stubTM = new StubITransactionManager(); stubTM.PostTransactionDecimalTransactionType = (amount, transType) => CallBackMethod(amount); Account cut = new Account(stubTM); // act cut.AddCredit(9.99m); // assert Assert.AreEqual(true, callBackCalled); }

Migrando los verify


El Verify se usa en Moq para verificar comportamientos, para asegurarse de que el cdigo que se est testeando ha hecho ciertas llamadas con ciertos parmetros o que se han hecho ciertas llamadas un cierto nmero de veces.
Page 26 of 82

Testing Unitario con Microsoft Fakes - Captulo 3: Migrando a Microsoft Fakes


Sin embargo estas capacidades de comprobacin de comportamiento son limitadas. Por ejemplo, no se puede comprobar que los mtodos se ejecutan en cierto orden. Los Stubs en Microsoft Fakes no estn pensados para usarlos de esa manera; sin embargo, pueden hacer verificaciones de comportamiento si es necesario. En el siguiente ejemplo, queremos testear que se ha lanzado una transaccin al balance de apertura cuando se abre una cuenta. Esto puede testearse de otras maneras, pero este ejemplo muestra cmo testear un comportamiento. En Moq, el test podra ser algo parecido a esto:
[TestMethod] public void MoqAccountOpenPostsInitialBalanceCreditTransaction() { // Arrange Mock<ITransactionManager> mockTM = new Mock<ITransactionManager>(); Account cut = new Account(mockTM.Object); // Act cut.Open(10m); // Assert mockTM.Verify(tm => tm.PostTransaction(10m, TransactionType.Credit), Times.Once()); }

Usando Stubs en Microsoft Fakes esto necesita un poco ms de cdigo en la expresin lambda para grabar las llamadas:
[TestMethod] public void StubAccountOpenPostsInitialBalanceCreditTransaction() { // Arrange int callCount = 0; StubITransactionManager stubTM = new StubITransactionManager { PostTransactionDecimalTransactionType = (amount, type) => { if (amount == 10m && type == TransactionType.Credit) { callCount++; } } }; Account cut = new Account(stubTM); // Act cut.Open(10m); // Assert Assert.AreEqual<int>(1, callCount); }

Con Moq, el desarrollador tiene que llamar al mtodo de verificacin adecuado, dependiendo del elemento que quiera verificar: .Verify para los mtodos .VerifyGet para los Get de las propiedades. .VerifySet para los Set de las propiedades.

Como Stubs no ofrece mtodos de verificacin, tendremos que crearnos los nuestros, de manera que no hay diferencia entre verificaciones de mtodos o propiedades; es todo cdigo personalizado. Obviamente, algunas veces hacen falta verificaciones ms complejas. Por ejemplo, pueden ser necesarias diferentes combinaciones de parmetros. Esto se puede realizar con la tcnica del Diccionario de Tuplas, similar al que vimos en la seccin de Setup, para contar el nmero de llamadas por cada combinacin de parmetros.

Migrando los eventos


En arquitecturas guiadas por eventos, es muy importante ser capaz de lanzar eventos en los tests. Esto se puede hacer tanto en Moq como en Stubs con una sintaxis muy parecida.
Page 27 of 82

Testing Unitario con Microsoft Fakes - Captulo 3: Migrando a Microsoft Fakes


En ambos casos, tendremos que registrar un delegado que ser llamado cuando se lance el evento. En este ejemplo, slo setearemos un flag booleano para indicar que el evento se ha lanzado. Sin embargo, las tcnicas de verificacin, como vimos antes, pueden usarse para comprobar cualquier parmetro que se halla pasado. En la seccin de act, lanzamos el evento que queremos testear pasando los parmetros adecuados. Aqu es donde la sintaxis se diferencia. Con Stubs el evento se lanza con una llamada a un mtodo:
[TestMethod] public void StubsraiseEvent() { // arrange bool delegateCalled = false; DateTime testDate = new DateTime(2012, 1, 1); StubITransactionManager stubTM = new StubITransactionManager(); stubTM.TransactionEventEvent = (date, amount, transType) => { delegateCalled = true; }; // act // Raise passing the custom arguments expected by the event delegate stubTM.TransactionEventEvent(testDate, 9.99m, TransactionType.Credit); // assert Assert.AreEqual(true, delegateCalled); }

Mientras que en Moq tenemos que usar una expresin lambda para pasar los parmetros:
[TestMethod] public void MoqRaiseEvent() { // arrange bool delegateCalled = false; DateTime testDate = new DateTime(2012, 1, 1); Mock<ITransactionManager> mockTM = new Mock<ITransactionManager>(); mockTM.Object.TransactionEvent += delegate { delegateCalled = true; }; // act // Raise passing the custom arguments expected by the event delegate mockTM.Raise(tm => tm.TransactionEvent += null, testDate, 9.99m, TransactionType.Credit); // assert Assert.AreEqual(true, delegateCalled); }

Fakes recursivos
Cuando tenemos un rbol de objetos complejo que tiene que ser fakeado, se tarda mucho tiempo en setear todas las propiedades y suele ser innecesario. En muchos casos lo nico necesario es que el objeto que se ha fakeado no lance excepciones del tipo NullReferenceException. Moq ofrece una manera de conseguir que esto no ocurra, seteando todas las referencias/propiedades en el rbol de objetos. Esto se hace usando las opciones de DefaultValue.Mock para los objetos mockeados y el mtodo SetupAllProperties para las propiedades. De esta manera, el test no lanzar ningn NullReferenceException. Se devolver el valor por defecto de cualquier objeto que haya que devolver. Por ejemplo, los enteros devolvern un 0, y los strings devolvern un String.Empty. Si hace falta algn otro valor, tendremos que indicarlo de manera explcita:
Mock<ITransaction> mockTr = new Mock<ITransaction>() { DefaultValue = DefaultValue.Mock };

Page 28 of 82

Testing Unitario con Microsoft Fakes - Captulo 3: Migrando a Microsoft Fakes


mockTr.SetupAllProperties();

Con Stubs, se usa una sintaxis muy similar. Simplemente seteando la propiedad InstanceBehavior al comportamiento deseado cuando se acceda a cualquier propiedad o mtodo:
StubITransaction stubTr = new StubITransaction(); stubTr.InstanceBehavior = StubBehaviors.DefaultValue;

Ejemplo adicional
Por motivos ilustrativos, vamos a ver otro ejemplo para terminar de migrar de Moq a Microsoft Fakes. Este ejemplo est basado en el post Mocking HttpWebRequest using Microsoft Fakes, tenemos un objeto WebServiceClient que usa un HttpWebReques, que nos gustara testear:
public class WebServiceClient { /// <summary> /// Calls a web service with the given URL /// </summary> /// <param name="url">The web service's URL</param> /// <returns>True if the services responds with an OK status code (200). False Otherwise</re turns> public bool CallWebService(string url) { var request = CreateWebRequest(url); var isValid = true; try { var response = request.GetResponse() as HttpWebResponse; isValid = HttpStatusCode.OK == response.StatusCode; } catch (Exception ex) { isValid = false; } return isValid; } /// <summary> /// Creates an HttpWebRequest object /// </summary> /// <param name="url">The URL to be called.</param> /// <returns>An HttpWebRequest.</returns> private static HttpWebRequest CreateWebRequest(string url) { var request = WebRequest.Create(url) as HttpWebRequest; request.ContentType = "text/xml;charset=\"utf-8\""; request.Method = "GET"; request.Timeout = 1000; request.Credentials = CredentialCache.DefaultNetworkCredentials; return request; } }

Para usar Moq necesitamos crear un objeto CustomWebRequestCreate que implemente la interfaz IWebRequestCreate. Esto nos permite mockear el HttpWebResponse usando RegisterPrefix:
/// <summary> /// A custom implementation of IWebRequestCreate for Web Requests.

Page 29 of 82

Testing Unitario con Microsoft Fakes - Captulo 3: Migrando a Microsoft Fakes


/// </summary> /// <summary>A web request creator for unit testing</summary> public class CustomWebRequestCreate : IWebRequestCreate { /// <summary> /// The web request. /// </summary> private static WebRequest nextRequest; /// <summary> /// Internally held lock object for multi-threading support. /// </summary> private static object lockObject = new object(); /// <summary> /// Gets or sets the next request object. /// </summary> public static WebRequest NextRequest { get { return nextRequest; } set { lock (lockObject) { nextRequest = value; } } } /// <summary> /// Creates a Mock Http Web request /// </summary> /// <param name="httpStatusCode"></param> /// <returns>The mocked HttpRequest object</returns> public static HttpWebRequest CreateMockHttpWebRequestWithGivenResponseCode(HttpStatusCode httpStatusCode) { var response = new Mock<HttpWebResponse>(MockBehavior.Loose); response.Setup(c => c.StatusCode).Returns(httpStatusCode); var request = new Mock<HttpWebRequest>(); request.Setup(s => s.GetResponse()).Returns(response.Object); NextRequest = request.Object; return request.Object; } /// <summary> /// Creates the new instance of the CustomWebRequest. /// </summary> /// <param name="uri">The given Uri</param> /// <returns>An instantiated web request object requesting from the given Uri.</returns>

Page 30 of 82

Testing Unitario con Microsoft Fakes - Captulo 3: Migrando a Microsoft Fakes


public WebRequest Create(Uri uri) { return nextRequest; } }

Nuestro test en Moq sera algo as:


[TestMethod] public void TestThatServiceReturnsAForbiddenStatuscode() { // Arrange var url = "http://testService"; var expectedResult = false; WebRequest.RegisterPrefix(url, new CustomWebRequestCreate()); CustomWebRequestCreate.CreateMockHttpWebRequestWithGivenResponseCode(HttpStatusCode.Forbidden) ; var client = new WebServiceClient(); //Act bool actualresult = client.CallWebService(url); //Assert Assert.AreEqual(expectedResult, actualresult); }

Con Microsoft Fakes, podemos falisificar el objeto HttpWebRequest sin tener que implementar la interfaz IWebRequestCreate. El test sera algo as:
[TestMethod] public void TestThatServiceReturnsAForbiddenStatuscode() { using (ShimsContext.Create()) { // Arrange var requestShim = new ShimHttpWebRequest(); ShimWebRequest.CreateString = (uri) => requestShim.Instance; requestShim.GetResponse = () => { return new ShimHttpWebResponse() { StatusCodeGet = () => { return HttpStatusCode.Forbidden; } }; }; var client = new WebServiceClient(); var url = "testService"; var expectedResult = false; // Act bool actualresult = client.CallWebService(url); // Assert Assert.AreEqual(expectedResult, actualresult); } }

Referencias
Para ver ms ejemplos de Moq visitad: http://code.google.com/p/moq/wiki/QuickStart

Page 31 of 82

Testing Unitario con Microsoft Fakes - Captulo 3: Migrando a Microsoft Fakes

Migrando desde RhinoMocks


En esta seccin veremos cmo migrar alguna de las caractersticas ms usadas en la API de RhinoMocks a Microsoft Fakes. RhinoMocks es uno de muchos proyectos open source que ofrecen formas de stubbear el cdigo bajo test. Rhinomocks usa una API para crear stubs de interfaces y clases abstractas a travs de reflexin. Microsoft Fakes lo que hace es generar cdigo para interfaces y clases abstractas. La sintaxis usada para RhinoMocks en este documento corresponde a la versin 3.5 o superior. Para los desarrolladores que quieran usar Microsoft Fakes, tan slo tienen que hacer click derecho en el assembly para el que se quieren crear stubs y seleccionar la opcin Add Fake Assembly del men. Slo veremos ejemplos de migracin para las APIs que ms se usan en RhinoMocks. El conjunto de APIs de RhinoMocks es muy amplio y no est en el alcance de este documento. Para ver detalles que no se ven aqu, ved los documentos de RhinoMocks o de Microsoft Fakes para obtener ayuda en la migracin.

Cdigo de ejemplo
La mayora de ejemplos que vamos a ver se basan en la siguiente interfaz y clase para testear:
public interface IDetermineTempWithWindChill { double WhatisCurrentTemp(double airTemp, double airSpeed); double CalcSpecialCurrentTemp(double airTemp, double airSpeed, double aboveSeaLevel); } public interface IClassUnderTest { double WhatIsTheTempToday(double currentAirTemp, double currentWindSpeed, double currentFeetAboveSeaLevel); } public class ClassUnderTest : IClassUnderTest { private readonly IDetermineTempWithWindChill _determineTempWithWindChill; public ClassUnderTest(IDetermineTempWithWindChill determineTempWithWindChill) { _determineTempWithWindChill = determineTempWithWindChill; } public double WhatIsTheTempToday(double currentAirTemp, double currentWindSpeed, double currentFeetAboveSeaLevel) { return currentFeetAboveSeaLevel >= 5000.0 ? _determineTempWithWindChill.WhatisCurrentTemp (currentAirTemp, currentWindSpeed) * 0.1 : _determineTempWithWindChill.WhatisCurrentTemp (currentAirTemp, currentWindSpeed); } }

Migrando lo setups y returns


RhinoMocks usa dos tipos de mockeo: el estricto y el dinmico. El mockeo estricto (Code 31) consiste en que el desarrollador tiene que definir las salidas de todos los mtodos que se van a llamar en el test. Si no se ha definido una salida para algn mtodo, se lanzar una excepcin para ese mtodo cuando sea llamado. Por defecto, Microsoft Fakes usa el valor por defecto para el mocking. El desarrollador puede sobrescribir este comportamiento
Page 32 of 82

Testing Unitario con Microsoft Fakes - Captulo 3: Migrando a Microsoft Fakes


definiendo el InstanceBehavior (Code 32) en la interfaz stubbeada o en la clase abstracta. Si no se ha configurado un Stub con un valor de salida y se le llama, lanzar una excepcin cuando se ejecute el test:
IDetermineTempWithWindChill determineTempWithWindChill = MockRepository.GenerateStrictMock<IDetermineTempWithWindChill>(); Code 1 RhinoMock Strict Mocking stubIDetermineTempWithWindChill.InstanceBehavior = StubBehaviors.NotImplemented; Code 2 InstanceBehavior NotImplemented sample

Usando el stubIDetermineTempWithWindChill.InstanceBehavior = StubBehaviors.NotImplemented; si el desarrollador no define un valor de salida para el mtodo WhatIsCurrentTemp se lanzar una excepcin cuando se ejecute el test. Para la mayora de ejemplos, usaremos el mocking dinmico. Los stubs de RhinoMocks (setup y returns) son llamadas simples a la API, como vemos en el siguiente cdigo:
[TestMethod] public void TestSetupAndReturn() { //Arrange double airTemp = 35; double airSpeed = 5.0; IDetermineTempWithWindChill determineTempWithWindChill = MockRepository.GenerateStub<IDetermineTempWithWindChill>(); determineTempWithWindChill.Stub(x => x.WhatisCurrentTemp(airTemp, airSpeed)) .Return(airTemp * airSpeed); IClassUnderTest classUnderTest = new ClassUnderTest(determineTempWithWindChill); //Act var results = classUnderTest.WhatIsTheTempToday(airTemp, airSpeed, 2000.0); //Assert Assert.AreEqual(airTemp * airSpeed, results); }

En Microsoft Fakes el stub del setup y del return es un poco ms evolucionado con expresiones lambda, como vemos aqu:
[TestMethod] public void TestSetupAndReturn() { //Arrange double airTemp = 35; double airSpeed = 5.0; StubIDetermineTempWithWindChill stubIDetermineTempWithWindChill = new StubIDetermineTempWithWindChill(); stubIDetermineTempWithWindChill.WhatisCurrentTempDoubleDouble = (x, y) => x * y; IClassUnderTest classUnderTest = new ClassUnderTest(stubIDetermineTempWithWindChill); //Act var results = classUnderTest.WhatIsTheTempToday(airTemp, airSpeed, 2000.0); //Assert Assert.AreEqual(airTemp * airSpeed, results); }

Page 33 of 82

Testing Unitario con Microsoft Fakes - Captulo 3: Migrando a Microsoft Fakes Migrando las sentencias Expect y AssertWasCalled
RhinoMocks usa las sentencias Expect, AssertWasCalled o AssertWasNotCalled para verificar algoritmos o llamadas a mtodos. La API de RhinoMocks ofrece varias opciones para administrar el nivel de detalle de cuantas veces se llam a un mtodo. Durante el testing, si alguna de las sentencias de verificacin no se corresponde con las llamadas esperadas, se lanza una excepcin:
determineTempWithWindChill.Expect(x => x.WhatisCurrentTemp(airTemp, airSpeed)).Repeat.Times(2); determineTempWithWindChill.AssertWasCalled(x => x.WhatisCurrentTemp(airTemp, airSpeed), options => options.Repeat.Times(1)); determineTempWithWindChill.AssertWasNotCalled(x => x.CalcSpecialCurrentTemp(airTemp, airSpeed, aboveSeaLevel));

Con Microsoft Fakes, se usan expresiones lambda para realizar las mismas verificaciones. El siguiente cdigo muestra una verificacin para ver que WhatIsCurrentTemp y CalcSpecialCurrentTemp se han llamado una vez. En el caso de CalcSpecialCurrentTemp se lanzar una excepcin si no se llama al mtodo. Con un poco ms de trabajo en la expresin lambda, Microsoft Fakes ofrece el mismo nivel de opciones para verificar algoritmos y llamadas a mtodos que RhinoMocks:
[TestMethod] public void TestSetupAndReturn() { //Arrange int WhatIsCurrentTempCalled = 0; int CalcSpecialCurrentTempCalled = 0; double airTemp = 35; double airSpeed = 5.0; StubIDetermineTempWithWindChill stubIDetermineTempWithWindChill = new StubIDetermineTempWithWindChill(); stubIDetermineTempWithWindChill.WhatisCurrentTempDoubleDouble = (x, y) => { WhatIsCurrentTempCalled++; return x * y; }; stubIDetermineTempWithWindChill.CalcSpecialCurrentTempDoubleDoubleDouble = (x, y, z) => { CalcSpecialCurrentTempCalled++; throw new Exception("CalcSpecialCurrentTemp should not have been " + "called"); }; IClassUnderTest classUnderTest = new ClassUnderTest(stubIDetermineTempWithWindChill); //Act var results = classUnderTest.WhatIsTheTempToday(airTemp, airSpeed, 2000.0); //Assert Assert.AreEqual(airTemp * airSpeed, results); Assert.AreEqual(1, WhatIsCurrentTempCalled); Assert.AreEqual(0, CalcSpecialCurrentTempCalled); }

Page 34 of 82

Testing Unitario con Microsoft Fakes - Captulo 3: Migrando a Microsoft Fakes

Conclusin
Cualquier migracin de test de Telerik, RhinoMocks o Typemock requerir un gran esfuerzo. Las migraciones basadas en Buscar y Reemplazar no funcionarn bien ya que las definiciones de los comportamientos son muy diferentes. Los detalles para cada uno de esos frameworks estn fuera del alcance de este documento. Para ver cmo migrar de estos productos a Microsoft Fakes, ser mejor leer la documentacin de cada producto.

Page 35 of 82

Testing Unitario con Microsoft Fakes - Captulo 4: FAQ

Captulo 4: FAQ
En este captulo, veremos algunas preguntas frecuentes, algunas creemos que son avanzadas, - algunos casos extremos aunque creemos que son lo suficientemente importantes como para cubrirlos en esta gua.

Trabajando con .NET Framework 4


Slo porque Microsoft Fakes sea una nueva caracterstica no quiere decir que est restringido a .NET 4.5. Podemos usar Microsoft Fakes con cualquier versin de .NET que sea soportada por Visual Studio 2012. Por ejemplo, podemos usar los tipos Shims para hacer test de legacy code escrito en .NET 2.0.

Adoptando Microsoft Fakes en un equipo


Para ejecutar un test unitario o para compilar un proyecto que use Microsoft Fakes se requiere una versin soportada de Visual Studio. Esto aplica tanto a otros desarrolladores que ejecuten sus test como a cualquier agente de Team Foundation Build. Esto es as ya que cuando se usa Microsoft Fakes se crea una referencia a la dll Microsoft.QualityTools.Testing.Fakes.dll. Este archivo no se incluye en las versiones de Visual Studio que no soportan Microsoft Fakes. Si aadimos el assembly Microsoft.QualityTools.Testing.Fakes.dll a nuestro propio proyecto y hacemos check in, los dems podrn compilarlo. Sin embargo, se lanzar la excepcin NotSupportedException si estn ejecutando una edicin de Visual Studio que no soporte Microsoft Fakes. Para evitar estas excepciones deberemos colocar a los test en una categora de test que no se ejecute cuando los desarrolladores o los servidores de builds no estn ejecutando una versin adecuada de Visual Studio. Por ejemplo:
[TestCategory("FakesRequired"), TestMethod()] public Void DebitTest() { }

Si optamos por no aadir la dll Microsoft.QualityTools.Testing.Fakes.dll de manera local, podemos aislar el uso de Fakes aadindolos a un proyecto a parte y compilar ese proyecto slo en con una configuracin de build especfica. Es importante ver que si nuestros servidores de builds estn ejecutando una versin adecuada de Visual Studio debemos tener tambin Team Foundation Server 2012 para que esos tests se ejecuten de la misma manera en una build. Si estamos usando Team Foundation Server 2010, tendremos que editar nuestra plantilla de build para que ejecute los test que usen Fakes con vstest.console.exe. Si estamos usando la versin RTM de Visual Studio 2012, tendremos que generar y publicar nuestro archivo TRX. Visual Studio 2012 Update 1 incluye una actualizacin de vstest.console.exe que soporta la publicacin como parte de la llamada.

No se pueden hacer fakes de todo!


Por defecto, a la mayora de las clases de System o no se les hacen fakes o no se puede debido a decisiones de diseo. Las clases de System se tratan de una forma especial ya que son usadas por el propio motor, y conllevara a un comportamiento impredecible. Por ejemplo, los siguientes namespaces no estn soportados en proyectos para .NET 4: System.Globalization System.IO System.Security.Principal System.Threading
Page 36 of 82

Testing Unitario con Microsoft Fakes - Captulo 4: FAQ


No hay una lista definitiva de los tipos que no se soportan ya que depende de las diferentes combinaciones posibles de versin del Framework, del proyecto de test y de las versiones de .NET. La lista de tipos no soportados ser diferente entre alguien que est creando un proyecto para .NET 3.0 con el framework 3.5 instalado que otro que est creando un proyecto para .NET 3.0 con el framework 3.0 instalado.
AVISO NOTA
Cuidado con hacer un fake de una llamada que use el motor. Puede derivar en comportamientos impredecibles

Podemos sobrescribir el comportamiento de algunas clases de System como cualquier otro assembly configurando la generacin de los tipos de stub y filtrarlo en un archivo xml con la extensin .fakes:

Para eliminar los tipos que Microsoft Fakes no soporta, como CancellationToken y CancellationTokenSource, tendremos que refactorizar nuestro cdigo para cambiar las interfaces y las dependencias de los componentes que queramos testear. Cuando se haya hecho un fake de un tipo no soportado al compilar el .fakes se ver en el resultado de compilacin como un Warning.

Psate por Code generation, compilation, and naming conventions in Microsoft Fakes para ms informacin

Logging detallado
Debido a varias razones, Fakes puede decidir saltarse una clase cuando genera los shims. Con Visual Studio 2012 Update 1, podemos obtener ms informacin sobre el porqu de esa decisin cambiando el atributo Diagnostic de Fakes a true; esto har que Fakes nos muestre las clases que se ha saltado como warnings. Cuando Fakes decide saltarse un elemento de un tipo, escribe mensajes de diagnstico en el log de MSBuild. Podemos habilitarlo seteando la propiedad Verbosity del elemento .fakes e incrementar el nivel de detalle de MSBuild:

Trabajando con assemblies con strong names


Cuando generamos Fakes para assemblies con nombres fuertes (strong names), el nombrado de los assemblies fakes los administra el propio Framework por nosotros. Por defecto, el framework usar la misma clave que para
Page 37 of 82

Testing Unitario con Microsoft Fakes - Captulo 4: FAQ


el assembly shimeado. Podemos especificar una clave pblica diferente para el assembly de Fakes, una que hayamos creado nosotros para el assembly de Fakes, indicando el path completo al archivo .snk que contiene la clave en la propiedad KeyFile en el elemento Fakes\Compilation del archivo .fakes:
<Fakes xmlns="http://schemas.microsoft.com/fakes/2011/"> <Assembly Name="ClassLibrary1" Version="1.0.0.0"/> <Compilation KeyFile="MyKeyFile.snk" /> </Fakes>

Si tenemos acceso al cdigo del assembly al que estamos haciendo el fake, podemos exponer los tipos internos usando la propiedad InternalsVisibleTo. Cuando hacemos esto en un assembly con nombre fuerte, tendremos que indicar el nombre y la clave pblica tanto para el assembly fake como para el assembly de test. Por ejemplo:
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("SimpleLibrary.Test, PublicKey=0028b")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("SimpleLibrary.Fakes, PublicKey=0028b")]

Fijaos que necesitaremos la clave pblica y no el key token pblico del assembly, que es lo que normalmente vemos. Para obtener la clave pblica de un assembly firmado, necesitaremos la herramienta sn.exe que est incluida en Visual Studio. Por ejemplo:
C:\sn -Tp ClassLibrary1.Fakes.dll Microsoft (R) .NET Framework Strong Name Utility Version 4.0.30319.17929 Copyright (c) Microsoft Corporation. All rights reserved. Public key (hash algorithm: sha1): 0024000004800000940000000602000000240000525341310004000001000100172b76875201e1 5855757bb1d6cbbf8e943367d5d94eb7f2b5e229e90677c649758c6a24186f6a0c79ba23f2221a 6ae7139b8ae3a6e09cb1fde7ce90d1a303a325719c2033e4097fd1aa49bb6e25768fa37bee3954 29883062ab47270f78828d2d2dbe00ae137604808713a28fce85dd7426fded78e1d1675ee3a1e8 0cdcd3be Public key token is 28030c10971c279e

Para ello, el atributo InternalsVisibleTo debera ser:


[assembly: InternalsVisibleTo("ClassLibrary1.Fakes, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e92decb949446f688ab9f6973436c53 5bf50acd1fd580495aae3f875aa4e4f663ca77908c63b7f0996977cb98fcfdb35e05aa2c842002703cad835473caac5ef14107e3a 7fae01120a96558785f48319f66daabc862872b2c53f5ac11fa335c0165e202b4c011334c7bc8f4c4e570cf255190f4e3e2cbc913 7ca57cb687947bc")]

Optimizando la generacin de Fakes


Por defecto, cuando aadimos un Fakes assembly, el framework de Fakes crea un archivo XML que intenta generar Stubs y Shims, incluso genera tipos que es posible que nunca usemos en nuestros tests unitarios y que afectan negativamente a los tiempos de compilacin. Si tenemos un cdigo base testable y no necesitamos Shims, desactvalos. Si slo necesitas que se incluya un subconjunto de las clases de tu solucin, identifcalos con el filtrado de Tipos. (Pasate por Code generation, compilation, and naming conventions in Microsoft Fakes).
Antes de que indiques los filtros de tipo, pon siempre un <Clear/> Deshabilita la generacin con un solo <Clear/> o con el atributo Disable=true en el elemento StubGeneration o ShimGeneration

En el siguiente cdigo se desactiva el ShimGeneration, y genera Stubs slo para los tipos que contengan Contoso.MainWeb.Repository en el nombre:
<Fakes xmlns="http://schemas.microsoft.com/fakes/2011/"> <Assembly Name=" Contoso.MainWeb"/> <StubGeneration> <Clear/> <Add Namespace="Contoso.MainWeb.Repository" />

NOTA

Page 38 of 82

Testing Unitario con Microsoft Fakes - Captulo 4: FAQ


</StubGeneration> <ShimGeneration Disable="true"/> </Fakes>

Tenemos que saber que la generacin restringida tiene un efecto en el tiempo de compilacin y que la mejor optimizacin que podemos hacer es evitar la re-generacin de los fakes. Si ests haciendo un fake de un assembly que no suele cambiar deberas compilar los assemblies fakes una sola vez en un proyecto a parte y aadir esos assemblies como referencias al control de cdigo. Y referenciar desde ah tus proyectos de test unitarios.

Mirando bajo las sbanas


Vamos a ver qu ocurre si modificamos la configuracin del archivo .fakes. En el ejemplo vamos a hacer un fake de System.dll. Esta dll es un candidato perfecto para generarlo una vez y aadirlo a nuestros assemblies referenciados en el control de versiones, ya que no va a cambiar. En este ejemplo, usamos ILSpy para desensamblar el assembly que se ha generado y vamos a ver qu tipos se han generado:
Desc Sin Fakes Por defecto Configuracin NA <Fakes xmlns="http://schemas.microsoft.com/fak es/2011/"> <Assembly Name="System" Version="4.0.0.0"/> </Fakes> (La imagen pequea es intencionada lo tienes todo) Fjate en el tiempo de compilacin, por eso es la recomendacin de generarlo una vez y hacer check in de los assemblies que no vayan a cambiarse Sin Stubs <Fakes xmlns="http://schemas.microsoft.com/fak es/2011/"> <Assembly Name="System" Version="4.0.0.0"/> <StubGeneration Disable="true"/> </Fakes> Este cambio al archivo de configuracin tiene un gran impacto en la salida, pero muy poco en el tiempo de compilacin Sin Stubs ni Shims <Fakes xmlns="http://schemas.microsoft.com/fak es/2011/"> <Assembly Name="System" Version="4.0.0.0"/> <StubGeneration Disable="true"/> <ShimGeneration Disable="true"/> </Fakes> 18.8s 18.6s Tiempo de compilacin 1s 19.5s Estructura del Assembly y comentarios NA

Esto es slo un ejemplo. No tendra sentido no generar stubs ni shims

Page 39 of 82

Testing Unitario con Microsoft Fakes - Captulo 4: FAQ


Desc Limitado Configuracin <Fakes xmlns="http://schemas.microsoft.com/fak es/2011/"> <Assembly Name="System" Version="4.0.0.0"/> <StubGeneration> <Clear /> <Add Namespace="System!" /> <Add Namespace="System.IO!"/> </StubGeneration> </Fakes> Fijaos en el uso de <Clear/>. Sin el clear, tendramos la misma salida que en el comportamiento por defecto Tiempo de compilacin 18.6s Estructura del Assembly y comentarios

Refactorizando cdigo bajo test


Las convenciones de nombrado usados en Microsoft Fakes puede hacer que el refactoring del cdigo que se testea sea algo parecido a una aventura. Los prefijos de las clases Shim Fakes.Shim al nombre original. Por ejemplo, si vamos a hacer un fake de la clase System.DateTime, el Shim sera System.Fakes.ShimDateTime. El nombre de una clase stub se deriva del nombre de la interfaz, con el prefijo Fakes.Stub, y aadindole el nombre del tipo. El nombre del tipo stub se deriva de los nombres del mtodo y los parmetros. Si refactorizamos el cdigo bajo test, el test unitario que hemos escrito usando Shims y Stubs de la versin generada con Fakes assemblies no compilar. Ahora mismo, no hay una solucin fcil a este problema a parte de la de crear una expresin regular a medida para actualizar nuestros test unitarios. Ten esto en cuenta cuando estimes cualquier refactorizacin del cdigo que ha sido testeado unitariamente. Puede suponer un coste alto.

Eliminar Fakes de un proyecto


Aadir y, especialmente, eliminar los Fakes de tu solucin puede que no sea trivial. Te pedimos que evales y hagas pruebas funcionales y pruebas de concepto antes de introducir Fakes o cualquier otra herramienta de este tipo. Para eliminar Fakes de tu proyecto, haz lo siguiente:

Page 40 of 82

Testing Unitario con Microsoft Fakes - Captulo 4: FAQ

1. 2. 3.

Borra el directorio Fakes y los archivos asociados de tu proyecto Borra las referencias a los assemblies .Fakes de tu proyecto Borra el directorio FakesAssemblies del directorio de tu proyecto.

4.

Edita manualmente el archivo de tu proyecto de test. Busca los warnings para eliminar los using que hacan referencia a los Fakes:

Page 41 of 82

Testing Unitario con Microsoft Fakes - Captulo 4: FAQ

Uso de Fakes con el control de versiones de Team Foundation


Cuando aadimos Fakes, nos daremos cuenta de que se crean los directorios 1 Fakes y 2 FakesAssemblies. Contienen una serie de archivos de configuracin y assemblies:

Team Explorer nos indica estos cambios cuando trabajamos en local:

Los assemblies Fakes son auto-generados y estn en el directorio FakesAssemblies bajo el proyecto que los referencia. Estos archivos se crean en cada compilacin. Por lo que no deberan ser considerados como elementos
Page 42 of 82

Testing Unitario con Microsoft Fakes - Captulo 4: FAQ


configurables ni deberan aadirse al control de cdigo. Sin embargo, los archivos de configuracin de Fakes del tipo assemblyName.fakes que se crean en el directorio Fakes en el proyecto son elementos configurables y deberan incluirse en el control de cdigo. 1. 2. Selecciona los cambios del directorio Fakes Adelos al control de cdigo.

Excluir Fakes usando Team Explorer


Para excluir Fakes: 1. 2. Selecciona los cambios del directorio Fakes y haz clic derecho. Selecciona Ignore:

Otra forma sera seleccionar cada cambio de manera separada, lo que permite ms opciones para ignorarlo (como por extensin, nombre de archivo o directorio).

Excluir Fakes con .tfignore


Con Team Explorer, estamos actualizando indirectamente el archivo .tfignore, que nos asegura que los archivos que cumplan las reglas definidas no se incluirn en el control de cdigo:

Estas reglas se aplican a un archivo .tfignore: # para indicar que una lnea es un comentario. Se soportan los caracteres especiales * y ? Una definicin es recursiva a menos que se prefije con el carcter \ El smbolo de exclamacin, !, niega una definicin (los archivos que cumplan el patrn NO son ignorados).

El archivo .tfignore puede editarse con cualquier editor e texto y debe aadirse al control de cdigo. Podemos configurar qu tipos de archivos se ignorarn poniendo un archivo llamado .tfignore en el directorio que queremos que se apliquen las reglas. Los efectos del .tfignore son recursivos. Sin embargo, podemos crear .tfignore en los subdirectorios para sobrescribir los efectos de un .tfignore que haya en un directorio padre. http://msdn.microsoft.com/en-us/library/ms245454.aspx#tfignore
Page 43 of 82

Testing Unitario con Microsoft Fakes - Captulo 4: FAQ

Uso de Microsoft Fakes con ASP.NET MVC


ASP.NET MVC se cre encima de ASP.NET, que tiene muchas clases muy acopladas y algunas veces son difciles de testear. Microsoft Fakes nos puede ayudar a aislar el SUT (System Under Test) de los otros componentes de ASP.NET MVC. La idea principal es centrarnos en testear lo que realmente importa sin que las dependencias nos estorben.

Uso de Stubs con ASP.NET MVC


Con Microsoft Fakes, podemos aislar el controlador MVC de la aplicacin y testear slo la funcionalidad que es parte del controlador. Para ello, tenemos que inyectarle la dependencia al controlador, normalmente por el constructor a travs de interfaces:
public class CustomersController : Controller { private readonly ICustomerRepository customerRepository; public CustomersController(ICustomerRepository customerRepository) { this.customerRepository = customerRepository; } [HttpPost] public ActionResult Create(Customer customer) { if (ModelState.IsValid) { this.customerRepository.InsertOrUpdate(customer); this.customerRepository.Save(); return RedirectToAction("Index"); } return this.View(); } }

Es posible crear stubs con Microsoft Fakes para aislar esa dependencia. En el siguiente cdigo vemos cmo crear un stub para inyectrselo al constructor del controlador:
[TestClass] public class CustomersControllerTest { private StubICustomerRepository stubCustomerRepository; private CustomersController controller; [TestInitialize] public void SetupController() { stubCustomerRepository = new StubICustomerRepository(); controller = new CustomersController(stubCustomerRepository); } [TestMethod] public void CreateInsertsCustomerAndSaves() { // arrange bool isInsertOrUpdateCalled = false; bool isSaveCalled = false; stubCustomerRepository.InsertOrUpdateCustomer = customer =>

Page 44 of 82

Testing Unitario con Microsoft Fakes - Captulo 4: FAQ


isInsertOrUpdateCalled = true; stubCustomerRepository.Save = () => isSaveCalled = true; // act controller.Create(new Customer()); // assert Assert.IsTrue(isInsertOrUpdateCalled); Assert.IsTrue(isSaveCalled); } }

Usando Shims con ASP.NET MVC


Algunas veces no podemos inyectar interfaces o crear una nueva para hacer que los tests sean ms fciles. Para ese escenario, podemos usar Shims. Con Shims, podemos cambiar el comportamiento de un objeto, configurando el resultado esperado en un mtodo o propiedad. En este cdigo vemos cmo se puede hacer con shims:
public class AccountController : Controller { [HttpPost] public ActionResult Login(LogOnModel model, string returnUrl) { if (ModelState.IsValid) { if (Membership.ValidateUser(model.UserName, model.Password)) { FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe); return Redirect(returnUrl); } ModelState.AddModelError("", "The user name or password incorrect."); } return View(model); } }

Para testear esta accin, tenemos que usar tipos Shim para aislar las clases Membership y FormsAuthentication:
[TestMethod] public void Login_with_valid_model_and_valid_user_authenticate_and_redirect() { // arrange var model=new LogOnModel{Password = "123", UserName = "usrtest", RememberMe = true}; var returnUrl = "/home/index"; bool isAuthenticationCalled = false; bool isValidateUserCalled = false; RedirectResult redirectResult; using (ShimsContext.Create()) { ShimMembership.ValidateUserStringString = (usr, pwd) => isValidateUserCalled = true; ShimFormsAuthentication.SetAuthCookieStringBoolean = (username, rememberme) => { Assert.AreEqual(model.UserName, username); Assert.AreEqual(model.RememberMe, rememberme); isAuthenticationCalled = true;

Page 45 of 82

Testing Unitario con Microsoft Fakes - Captulo 4: FAQ


}; // act redirectResult = controller.Login(model, returnUrl) as RedirectResult; } // assert Assert.IsTrue(isValidateUserCalled, "Membership.ValidateUser not invoked"); Assert.IsTrue(isAuthenticationCalled, "FormsAuthentication.SetAuthCookie not invoked"); Assert.AreEqual(returnUrl, redirectResult.Url); }

Page 46 of 82

Testing Unitario con Microsoft Fakes - Captulo 5: Tcnicas avanzadas

Captulo 5: Tcnicas avanzadas


El tema central de este captulo no es estrictamente el testing unitario, sino algunos escenarios en los que podemos aprovechar ms de lo que ofrece Fakes. Siguiendo los principios establecidos del Test-Driven Development suele ser la mejor opcin cuando empezamos un nuevo proyecto. Sin embargo, cuando tratamos con cdigo heredado (legacy code) que no fue diseado para que fuese testable, se presentan muchas situaciones que tenemos que solventar. En muchos casos, el equipo de desarrollo original no est ya disponible, y los detalles de la implementacin, como la documentacin, no est accesible. En estas circunstancias, suele ser necesario entender el comportamiento de la implementacin. El framework de testing unitario que ofrece Visual Studio, junto a Microsoft Fakes, son un conjunto de herramientas perfectas para esto. Adems, la habilidad inherente de ejecutar los tests de manera selectiva para obtener informacin adicional sobre lo que est ocurriendo en diferentes condiciones, aumenta el proceso de aprendizaje. Como tambin veremos, el cdigo resultante generar muchos artefactos, normalmente conocidos como Emuladores, que pueden ser muy tiles para un testing unitario convencional y se podrn usar en des arrollos posteriores, lo que incluir un refactoring para mejorar la testabilidad. El cdigo de referencia es el simulador de trfico que usamos en el Ejercicio 4. Tiene un nmero de elementos que estn altamente acoplados. Vamos a trabajar sobre algunos de los retos que nos ofrece esta aplicacin.

Tratando con servicios Windows Communication Foundation (WCF)


Muchas aplicaciones se implementan como varios procesos que se comunican a travs de servicios. Podemos configurar un entorno para el sistema completo usando Microsoft Test Manager y la caracterstica de Lab Management, pero esto no es recomendable para tareas de testing unitario. Si en el lado del cliente tenemos una instancia de la interfaz (contrato) que se obtiene de una clase factora, es un problema sencillo el hacer que la factora devuelva un objeto que queramos. Sin embargo, si el cliente WCF se instancia internamente, no hay forma de reemplazar las llamadas:
private void UpdateRoadwork() { var client = new RoadworkServiceReference.RoadworkServiceClient(); var locations = new List<RoadworkServiceReference.Block>(); // Initialization of locations removed for clarity var impediments = client.RetrieveCurrent(locations.ToArray());

Si queremos testear este cdigo, o cualquier otro cdigo que llame al mtodo UpdateRoadword(), tendremos que tratar con esta situacin. La mejor opcin sera refactorizar para tratarlo bien, pero hay ocasiones en las que eso no es posible o deseable.

Rompiendo la dependencia
La solucin ms simple es hacer un Shim del cliente WCF y ofrecer nuestra propia implementacin. Esto no requiere ejecutar una instancia del servicio:
using (ShimsContext.Create()) { RoadworkServiceReference.Fakes.ShimRoadworkServiceClient.Constructor = (real) => { }; var intercept = new

Page 47 of 82

Testing Unitario con Microsoft Fakes - Captulo 5: Tcnicas avanzadas


FakesDelegates.Func<RoadworkServiceReference.RoadworkServiceClient, RoadworkServiceReference.Block[], RoadworkServiceReference.Impediment[]>( (instance, blocks) => { // Body of Shim removed for brevity }); RoadworkServiceReference.Fakes.ShimRoadworkServiceClient.AllInstances.RetrieveCurrentBlockAr ray = intercept;

Es importante ver que adems de crear un shim en la operacin especificada, tambin se ha creado uno para el constructor. Esto es debido a que un constructor real de WCF lanzar una excepcin debido a la falta de configuracin del servidor. Esta aproximacin puede tener algunas limitaciones. Primero, la lgica requerida para ofrecer una implementacin simple puede que no lo sea tanto, y segundo, esta aproximacin enmascarar cualquier problema de serializacin que pueda ocurrir, como pasar una clase derivada que no sea reconocida como un tipo vlido (known type).

Mejorando la situacin
Si el assembly con la implementacin actual del servicio es parte de la solucin, hay una solucin que solventar ambas limitaciones. Crearemos una instancia actual y usaremos la lgica real, pero evitaremos los aspectos del servicio actual. Como el cliente y el servidor no comparten la implementacin de los tipos, tendremos que transformar los parmetros y la salida. Usando un DataContractSerializer, tambin veremos cualquier problema de serializacin:
var services = new Dictionary<RoadworkServiceReference.RoadworkServiceClient, RoadworkService.RoadworkService>(); using (ShimsContext.Create()) { RoadworkServiceReference.Fakes.ShimRoadworkServiceClient.Constructor = real => { services.Add(real, new RoadworkService.RoadworkService()); }; var intercept = new FakesDelegates.Func<RoadworkServiceReference.RoadworkServiceClient, RoadworkServiceReference.Block[], RoadworkServiceReference.Impediment[]>( (instance, blocks) => { // ============================================================================= // The following (commented out) code uses explicit transforms, see docs for // reasons this may rapidly become difficult, and other potential issues.. // ============================================================================= // var realBlocks = new List<Models.Block>(); // foreach (RoadworkServiceReference.Block item in blocks) // { // // // } var realBlock = Transform(item); realBlocks.Add(realBlock);

Models.Block[] dataContractTransform = DataContractTransform<RoadworkServiceReference.Block[], Models.Block[]>(blocks); var realBlocks = new List<Models.Block>(dataContractTransform); var service = services[instance]; var results = service.RetrieveCurrent(realBlocks); var impediments = new List<RoadworkServiceReference.Impediment>(); foreach (var result in results)

Page 48 of 82

Testing Unitario con Microsoft Fakes - Captulo 5: Tcnicas avanzadas


{ var clientImpediment = new RoadworkServiceReference.Impediment(); clientImpediment.location = Transform(result.Location); impediments.Add(clientImpediment); } return impediments.ToArray(); });

RoadworkServiceReference.Fakes.ShimRoadworkServiceClient.AllInstances.RetrieveCurrentBlockAr ray = intercept;

La implementacin completa est disponible en el cdigo del Hands-on Lab en: Exercise 4\Traffic.AdvancedTechniques\Examples\BreakingServiceBoudnaryTechniques.cs

Tratando con clculos no deterministas


En este ejemplo, el simulador hace clculos basados en el intervalo de tiempo que hay entre llamadas sucesivas. Como esto no se puede controlar de una manera acotada, usaremos un shim para interceptar los valores que se le pasan al cdigo, y haremos que nuestros test los usen para las comparaciones.

Operaciones basadas en Timer


Tratar con elementos de cdigo que se invocan dependiendo del tiempo suele presentar algunos retos: Si el tiempo es muy rpido, puede que sea imposible saber exactamente cuntas llamadas se hacen. Si el tiempo es muy lento, el tiempo de test necesario para invocar las llamadas puede ser excesivo.

Para afrontar ambos escenarios, podemos generar un shim sobre el timer y permitir la invocacin manual del cdigo:
TimerCallback applicationCallback = null; object state = null; TimeSpan interval = TimeSpan.Zero; System.Threading.Fakes.ShimTimer.ConstructorTimerCallbackObjectTimeSpanTimeSpan = (timer, callba ck, arg3, arg4, arg5) => { applicationCallback = callback; state = arg3; interval = arg5; };

Shim del timer para capturar los parmetros.


const int IterationCount = 10; for (int i = 1; i <= IterationCount; ++i) { applicationCallback(state); Thread.Sleep(interval); }

Invocacin el cdigo deseado de manera determinista

Datos no repetibles
Otra situacin que se suele dar es cuando la lgica se basa en una distribucin creada por un generador de nmeros aleatorios. Esto hace imposible saber exactamente qu datos se generarn. La forma ms simple es hacer un Shim de la clase Random, y ofrecer unos valores deterministas.
Page 49 of 82

Testing Unitario con Microsoft Fakes - Captulo 5: Tcnicas avanzadas


En este ejemplo vamos a asegurarnos que los coches son todos con la orientacin oeste, en lugar de que sea algo aleatorio en el cdigo:
System.Fakes.ShimRandom.Constructor = (real) => { }; System.Fakes.ShimRandom.AllInstances.NextDouble = this.NextDouble; System.Fakes.ShimRandom.AllInstances.NextInt32Int32 = this.NextInt32Int32; private int NextInt32Int32(Random random, int i, int arg3) { return (i + arg3) / 2; } private double NextDouble(Random random) { return 0.5; }

La implementacin completa est disponible en el Hands-on Lab: Exercise 4\Traffic.AdvancedTechniques\Examples\NonDeterministicBehaviorTechniques.cs Como estamos tratando con generadores de nmero aleatorios, hay un detalle que se suele pasar por alto: Varias instancias con la misma semilla generar la misma secuencia de nmeros! Si intentis realizar operaciones con conjuntos independientes de nmeros aleatorios y estis usando varias instancias de Random para conseguirlo, debis aseguraros de que tienen la misma semilla. Esto lo veremos en la prxima seccin.

Recopilacin de casos de uso y ms informacin analtica.


En este ejemplo, el cdigo que se testea realiza un nmero de operaciones matemticas, sin embargo no se encuentra un rango y combinaciones posibles como valore de entrada. Usaremos test unitarios para recopilar los valores que se van a presentar con varias condiciones.

Validar detalles de implementacin


Aqu nos enfrentamos a un problema potencial cuando trabajamos con generacin de nmero aleatorios. Cada vez que se crea una instancia de la clase Random, se usa una semilla; diferentes instancias con la misma semilla generarn la misma secuencia de nmeros. Nos queremos asegurar de que cada instancia de Random nos devuelve una secuencia nica para evitar que varias instancias se queden en un estado de bloqueo. Adems, como queremos que este test recopile datos sin tener que alterar el comportamiento del cdigo que se testea, debemos asegurarnos de que el generador de nmeros aleatorios contina trabajando.
NOTA

Nota: El constructor sin parmetros usa Environment.TickCount, con lo que varias instancias creadas en un tiempo pequeo podran tener la misma semilla.
System.Fakes.ShimRandom.Constructor = delegate(Random random) { ShimsContext.ExecuteWithoutShims(delegate { var constructor = typeof(Random).GetConstructor(new Type[] { }); constructor.Invoke(random, new object[] { }); }); }; System.Fakes.ShimRandom.ConstructorInt32 = delegate(Random random, int i) { ShimsContext.ExecuteWithoutShims(delegate

Page 50 of 82

Testing Unitario con Microsoft Fakes - Captulo 5: Tcnicas avanzadas


{ var constructor = typeof(Random).GetConstructor(new[] { typeof(int) }); constructor.Invoke(random, new object[] { i }); }); if (this.values.Contains(i)) { passed = false; Assert.Fail("Multiple Random instances Created with identical seed Value={0}", i); } this.values.Add(i); };

La implementacin completa est en el Hands-on Lab: Excercise 4\Traffic.AdvancedTechnices\Examples\DataGatheringTechniques.cs

Analizando el estado interno


En este ejemplo comprobaremos algunas operaciones del motor de simulador de rutas. El objetivo es comprobar que se han considerado todas las rutas vlidas cuando seleccionamos la mejor ruta para un coche. La lgica para esto est contenida en las clases ShortestTime y ShortestDistance. Desafortunadamente, la lista de rutas vlidas est en una variable local:
List<Route> consideredRoutes = new List<Route>(); MethodInfo mi = typeof(ShortestTime).GetMethod("SelectBestRoute", BindingFlags.Instance | Bindin gFlags.NonPublic); System.Collections.Generic.Fakes.ShimList<Route>.AllInstances.AddT0 = (collection, route) => ShimsContext.ExecuteWithoutShims(() => { if (this.IsArmed) { consideredRoutes.Add(route); } collection.Add(route); }); // TODO: We can Shim the protected method, but without using reflection, there is no way to invo ke it from within the shim // FYI: ExecuteWithoutShims disables ALL Shims, thereby breaking the capture of "consideredRoute s", but setting the individual shim to null works. FakesDelegates.Func<ShortestTime, Car, Route> shim = null; shim = (time, car) => { Route route = null; IsArmed = true; ShimShortestTime.AllInstances.SelectBestRouteCar = null; var result = mi.Invoke(time, new object[] { car }); ShimShortestTime.AllInstances.SelectBestRouteCar = shim; route = (Route)result; IsArmed = false; Assert.IsTrue(consideredRoutes.Count > 0, String.Format("Failed to Find Any Considered Route s from {0} to {1}", car.Routing.StartTrip.Name, car.Routing.EndTrip.Name)); return route; };

Page 51 of 82

Testing Unitario con Microsoft Fakes - Captulo 5: Tcnicas avanzadas


ShimShortestTime.AllInstances.SelectBestRouteCar = shim;

La implementacin completa del cdigo est en el Hands-on Lab: Excersie 4\Traffic.AdvancedTechniques\Examples\DataGatheringTechniques.cs

Evitando la duplicacin de estructuras de testing


En muchos ejemplos, todo el trabajo necesario para hacer stubs o shims ha sido realizado desde dentro del test unitario. Esto puede llevar a muchas duplicaciones debido a que diferentes elementos pueden requerir una infraestructura similar o idntica. Una solucin comn puede ser crear clases que combinan el shim con la funcionalidad asociada y simplemente la activan en un ShimsContext. Aunque sea til para reducir duplicaciones, puede que no sirva de mucho en escenarios ms complejos en los que o las relaciones entre las clases estn muy acopladas o si queremos llamar a implementaciones reales para alguna o todas las funcionalidades. Una solucin ms sensata es crear un Doppelgnger. Para conseguirlo, extenderemos el concepto de emulador para que la nueva clase tenga la instancia actual de la clase real, junto con los shims necesarios y funciones de ayuda e incluso alguna conversin entre los dos. Como Doppelgnger es largo y difcil de decir, simplemente nos referiremos a l como clase Testable y ser fcilmente identificable ya que tendr el mismo nombre que la clase real pero con el prefijo Testable, por ejemplo Testabl eCar para Car. Tenemos un conjunto de clases Testable que se corresponden con las clases del assembly que queremos testear. Os pedimos que miris estas clases. Nos permiten, por ejemplo, hacer un shim de una llamada a un servicio y hacer un shim de la inicializacin de una ruta, evitando los largos tiempos de inicializacin en una TestableCity, o hacer un shim del constructor Random para conseguir el control de un TestableCar La implementacin completa est disponible en el Hands-on Lab: Exercise 4\Traffic.AdvancedTechniques\Examples\AvoidingDuplicationTechniques.cs

Page 52 of 82

Testing Unitario con Microsoft Fakes - Captulo 6: Hands-on Lab

Captulo 6: Hands-on Lab


NOTA
Psate por la seccin Uso del cdigo fuente, erratas y soporte antes de comenzar este laboratorio.

Ejercicio 1: Usando Stubs para aislarnos del acceso a la base de datos (20 30 min)
Para este ejercicio vamos a usar una aplicacin ASP.NET MVC 4 muy simple. La solucin IntroToStubs.sln en el directorio Hands-on Lab\Excercies 1\start tenemos slo la clase Controller. No contiene vistas (est configurado para que use Razor), y, para este ejercicio, no requeriremos ninguna vista. Nuestro trabajo consistir en implementar un aspecto funcional: Obtener un resumen de compra y un clculo del precio total de esa orden de compra. Es importante ver que no hay definida ninguna base de datos, ni siquiera necesitamos crearnos una para empezar a hacer tests unitarios y validar nuestros componentes. Sin Stubs, la primera aproximacin que tendramos se nos ocurrira sera: 1. 2. 3. Crear una base de datos de ejemplo Rellenarla con datos de ejemplo. Crear los test aadiendo los datos de ejemplo que requieran para ejecutarse.
En este ejercicio, veremos cmo con Microsoft Fakes podemos aislar la dependencia existente entre la base de datos y nuestra clase controladora para testear la implementacin funcional.

Dependencias del entorno


Qu es lo que est mal en esta primera aproximacin? Bueno, qu pasa si la base de datos es una base de datos relacional? Recordad, los test unitarios tienen que ser pequeos y rpidos. Adems, para que todo el equipo de desarrollo pueda ejecutar estos test en sus mquinas habra que hacer algo para que esa base de datos de ejemplo est disponible para ellos. Otra cosa que agrava el problema es que los equipos de desarrollo maduros usan Servidores de Builds (mquinas o instancias configuradas con componentes conocidos, libreras, compiladores, scripts.) Estas mquinas puede que no tengan acceso a todas las dependencias externas como una base de datos o un servicio web. Las dependencias de los entornos pueden suponer un componente bloqueante para el desarrollo. Esta es una de las razones por las que el aislamiento, junto con lo de centrarnos en lo que queremos testear, Microsoft Fakes aporta valor.

Patrn de implementacin
En la siguiente figura podemos ver la interaccin normal entre varias clases. Adems, podemos ver que el acoplamiento entre el Repositorio y la base de datos es lo que queremos aislar. Nuestra intencin es centrar nuestros test en la lgica de negocio que, para este ejemplo, estar en los mtodos Action de la clase Controller.
Page 53 of 82

OBJETIVO

Testing Unitario con Microsoft Fakes - Captulo 6: Hands-on Lab

Cuando aislamos, desacoplamos nuestra implementacin del repositorio con la base de datos, al mismo tiempo, ofrecemos un estado conocido al test y el comportamiento que se usar luego por los componentes que se testan. En la siguiente figura, se usa el Fake Stub en lugar del repositorio real, y el cdigo del propio test indicar cules son los valores necesarios para testear:

Pasos a realizar
1. 2. 3. 4. 5. 6. Aadir un assembly fake por cada assembly del que queramos un fake Revisar y ajustar la configuracin de los archivos Fakes [avanzado] Ajustar los usings (C#) o los Import (VB) a los namespaces de los Stubs necesarios. Aadir la implementacin necesaria para los stubs en aquellas clases y mtodos necesarios para los tests que se vayan a hacer. Dar el cdigo para el Act del objeto o mtodo que se va a testear. Poner los Assert necesarios con los datos esperados.

Paso 1 Revisar la solucin de inicio


Primero, chale un vistazo a la solucin con la que empezamos, IntroToStubs.sln, que se compone de dos proyectos: 1. 2. 1. 2. 3. 4. MainWeb proyecto principal de MVC4 MainWeb.Tests Proyecto del tipo Microsoft Unit Testing Controller -> OrderController Model -> IOrderRepository Model -> Order Model -> OrderRepositoriy (implementacin de IOrderRepository)

An no tenemos ninguna clase de test definida. En MainWeb, trabajaremos con las siguientes clases:

NOTA

El ejemplo es una solucin comn que aprovecha el constructor con parmetros de la clase Controller, junto al patrn Repository

Page 54 of 82

Testing Unitario con Microsoft Fakes - Captulo 6: Hands-on Lab


Para este ejemplo, OrderRepository representa la implementacin concreta con la responsabilidad de obtener datos de una base de datos fsica; sin embargo, en este ejemplo, hemos dejado cada mtodo como Not Implemented ya que haremos Stubs para aquellas implementaciones que necesiten los tests.

Paso 2 Prepara el proyecto de test para Microsoft Fakes Stubs


Empezaremos configurando nuestro proyecto de tests. 1. 2. Seleciona el proyecto MainWeb.Tests y hacemos Add a Project Reference a Main Web En este momento, nos aseguramos de que la solucin compila. Pulsa F6 para compilar la solucin. Con esto el proyecto de test actualiza la referencia y todos los tipos del assembly MainWeb para cuando generemos el Fake.

Paso 3 Aade el assembly de Fake al proyecto de test


1. 2. 3. Ahora que el proyecto compila y que tenemos la referencia, podemos generar el assembly Fake para nuestro SUT (System Under Test) que son las clases Controllers de MainWeb. En el Solution Explorer, navega hasta el proyecto MainWeb.Tests y abre el nodo References. Haz clic con el botn derecho en MainWeb y selecciona la opcin Add Fakes Assembly

NOTA

4.

En este punto, revisa el proyecto MainWeb.Test y la estructura de directorio con el Solution Explorer; deberas ver el siguiente nodo adicional Fakes aadido a la estructura del proyecto MainWeb.Tests con el nombre completo del assembly MainWeb y un .fakes como extensin de archivo:

NOTA

El framework de Fakes ha generado Stubs y Shims para nuestro Assembly y aquellos tipos estn en el Microsoft.ALMRangers.MainWeb.Fakes.

Page 55 of 82

Testing Unitario con Microsoft Fakes - Captulo 6: Hands-on Lab

Paso 4 Revisa y actualiza el archivo xml de Fakes


Vamos a ver un poco el archivo XML que ha sido generado cuando hemos aadido el assembly de Fakes al proyecto de tests. El contenido es escaso pero pronto lo cambiaremos un poco. 1. Abre y revisa el archivo Microsoft.ALMRangers.MainWeb.fakes. Muestra el contenido por defecto:
<Fakes xmlns="http://schemas.microsoft.com/fakes/2011/"> <Assembly Name="Microsoft.ALMRangers.FakesGuide.MainWeb"/> </Fakes>

2. 3.

El Solution Explorer, selecciona el archivo Microsoft.ALMRangers.MainWeb.fakes y mira las propiedades (F4) del archivo. Vers que el Build Action es Fakes. Opcional: Modifica el archivo generado de la siguiente manera para slo crear Stubs (no Shims) y para filtrar los tipos que vamos a necesitar:
<Fakes xmlns="http://schemas.microsoft.com/fakes/2011/"> <Assembly Name="Microsoft.ALMRangers.FakesGuide.MainWeb"/> <StubGeneration> <Clear/> <Add Namespace="Microsoft.ALMRangers.FakesGuide.MainWeb.Models" /> </StubGeneration> <ShimGeneration Disable="true"/> </Fakes>

Estas opciones muestran cmo podemos adelgazar el assembly generado filtrando por tipos especficos. Cuando compilamos, el framework de Microsoft Fkes generar un assembly para nuestro proyecto basndose en estas opciones. Lo hacemos aqu para mostrar los valores reducidos que aparecen en el IntelliSense cuando estemos en el editor de cdigo.

Paso 5 Revisa las clases del modelo y del controlador del MainWeb
1. Revisa las clases del modelo del directorio Model del proyecto MainWeb. Fijate que hemos usado una implementacin Testable, en el que la clase OrderController usa una interfaz (IOrderRepository); esta interfaz nos permite ofrecer tanto un stub del IOrderRepository al OrderController como un comportamiento especfico a nuestras necesidades de testeo. Adems de eso, hay algunas clases bsicas que representan objetos de negocio que van a ser usados por los componentes de negocio durante los tests:
public interface IOrderRepository { IQueryable<Order> All { get; } IQueryable<OrderLines> OrderLines(int id); Order Find(int id); } public class Order { public Order() { this.OrderLines = new HashSet<OrderLines>(); } public int Id { get; set; } public string CustomerName { get; set; } public double TaxRate { get; set; } public ICollection<OrderLines> OrderLines { get; set; } }

NOTA

Page 56 of 82

Testing Unitario con Microsoft Fakes - Captulo 6: Hands-on Lab


public class OrderLines { public int Id { get; set; } public string ProductName { get; set; } public double UnitCost { get; set; } public bool IsTaxable { get; set; } public int Quantity { get; set; } } public class OrderSummaryViewModel { public Order Order { get; set; } public List<OrderLines> OrderLines { get; set; } public double Total { get; set; } } public class OrderRepository : IOrderRepository { public IQueryable<Order> All { get { throw new NotImplementedException(); } } public IQueryable<OrderLines> OrderLines(int id) { throw new NotImplementedException(); } public Order Find(int id) { throw new NotImplementedException(); } }

Paso 6 Crear un mtodo de test unitario


Ya estamos listos para crear nuestros tests unitarios. En este paso, vamos a implementar un listado de artculos que simplemente resumir la cantidad total de la orden. 1. 2. Crea una clase de Test. Selecciona el proyecto de test, en el men Project, elige la opcin Add, Unit Test En el Solution Explorer, renombra el archivo de la clase. Selecciona el archivo OrderControllerTest.cs, pulsa F2 y escribe OrderControllerTests. Esto nos preguntara si queremos renombrar tambin la clase. Elige Si. En el editor, renombra el TestMethod1 a OrderController_orderSumaryTotalCheck_equalSum() La clase de test debera ser algo parecido a esto:
using System; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Microsoft.ALMRangers.MainWeb.Tests { [TestClass] public class OrderControllerTests {

3. 4.

Page 57 of 82

Testing Unitario con Microsoft Fakes - Captulo 6: Hands-on Lab


[TestMethod] public void OrderController_orderSummaryTotalCheck_equalsSum() { } } }

Paso 7 Ordena el mtodo y crea un Stub para la interfaz Repository


Ya estamos listos para escribir nuestro test unitario. Recuerda que estamos testeando el mtodo de accin OrderController en el controlador y que estamos aislando la lgica del OrderController de la implementacin del repositorio. Haremos un Stub del repositorio. 1. Reemplaza los usings que tengamos con los siguientes:
using System.Collections.Generic; using System.Linq; using System.Web.Mvc; using Microsoft.ALMRangers.FakesGuide.MainWeb.Controllers; using Microsoft.ALMRangers.FakesGuide.MainWeb.Models; using Microsoft.VisualStudio.TestTools.UnitTesting; using ModelFakes = Microsoft.ALMRangers.FakesGuide.MainWeb.Models.Fakes;

Estos usings incluyen el namespace Microsoft.ALMRangers.MainWeb.Models.Fakes . En l estn los tipos (stubs y shims) generados por el framework de Fakes durante la compilacin presentes en el assembly. En la compilacin, el objetivo de la generacin son los assemblies y los namespaces. Hemos aadido un alias para ModelFakes para que sea ms fcil leer el cdigo. No es necesario hacerlo, podemos usar el namespace completo si queremos.
NOTA
El alias del using anterior simplemente es para una lectura ms sencilla del cdigo, no es necesario hacerlo.

2.

Crear una instancia de IOrderRepository. Se setear a una implementacin Stub que podremos definir en el contexto de este test:
[TestMethod] public void OrderController_orderSummaryTotalCheck_equalsSum() { // arrange const int TestOrderId = 10; IOrderRepository repository = new ModelFakes.StubIOrderRepository { // lambda code }

3.

De esta manera configuramos una instancia de IOrderRepository que ser un Stub (Fake). No el objeto real. Aqu es donde, a medida que nuestro test lo necesite, deberemos ofrecer una implementacin para cualquier mtodo que haga falta. La implementacin del Stub, generada por el framework de Microsoft Fakes es un tipo estndar de CLR sin ningn comportamiento. Ah es nosotros tenemos que inyectar el cdigo necesario para nuestro test. En este punto, hemos inicializado la instancia para nuestro Stub del repositorio pero an no hemos terminado. Tenemos que implementar dos mtodos del Stub ya que nos va a hacer falta para nuestro test.
Page 58 of 82

Testing Unitario con Microsoft Fakes - Captulo 6: Hands-on Lab


4. Aade el siguiente cdigo, en el que hemos tenido que aadir una expresin lambda para definir el mtodo IOrderRepository.Find(int):
FindInt32 = id => { Order testOrder = new Order { Id = 1, CustomerName = "smith", TaxRate = 5 }; return testOrder; },

El nombre de la propiedad del tipo StubIOrderRepository tiene la signatura de FakesDelegates.Func<int, Order> FindInt32. El framework de Microsoft Fakes nombra a cada mtodo aadiendo el tipo del parmetro como parte del nombre. Asi que como el mtodo Find de IOrderRepository tiene un parmetro del tipo Int32, el nombre del stub es FindInt32.

NOTA

5.

Haz un mtodo de generacin de datos esttico para que nuestro test lo use:
private static IQueryable<OrderLines> GetOrderLines() { var OrderLines = new List<OrderLines> { new OrderLines { Id = 10, IsTaxable = true, ProductName = "widget1", Quantity = 10, UnitCost = 10 }, new OrderLines { Id = 10, IsTaxable = false, ProductName = "widget2", Quantity = 20, UnitCost = 20 }, new OrderLines { Id = 10, IsTaxable = true, ProductName = "widget3", Quantity = 30, UnitCost = 30 }, new OrderLines { Id = 10, IsTaxable = false, ProductName = "widget4", Quantity = 40, UnitCost = 40 }, new OrderLines { Id = 10, IsTaxable = true, ProductName = "widget5", Quantity = 50, UnitCost = 50 }, }; return OrderLines.AsQueryable(); }

6.

Aade el siguiente cdigo para tener un stub del mtodo IOrderRepository.OrderLines(int). Usa el mtodo esttico GetOrderLines
OrderLinesInt32 = id => { var OrderLines = GetOrderLines(); return OrderLines.AsQueryable(); }

7.

Justo despus del } aade el siguiente cdigo para crear la instancia d el OrderController usando el constructor con parmetros:
var controller = new OrderController(repository);

La testabilidad de la solucin y sus componentes influyen a la hora de elegir Stubs o Shims. Nuestro ejemplo funciona bien con Stubs ya que usa interfaces. Las interfaces nos permiten inyectar objetos concretos diferentes para nuestros test, son nuestros Fakes. Las implementaciones testables usan interfaces, clases abstractas, y miembros virtuales que permiten la generacin de Stubs con el framwork de Microsoft Fakes. Lee el ejercicio de Shims para testear lo intesteable. Page 59 of 82

NOTA

Testing Unitario con Microsoft Fakes - Captulo 6: Hands-on Lab

Paso 8 Llama al mtodo de accin del controlador y comprueba los resultados


1. Aade el siguiente cdigo para completar el mtodo OrderController_orderSummaryTotalCheck_equalSum:
// act var result = controller.OrderLines(TestOrderId) as ViewResult; var data = result.Model as OrderSummaryViewModel; // assert Assert.AreEqual(5675, data.Total, "Order summary total not correct");

Aqu tenis el cdigo completo del test OrderController_orerSummaryTotalCheck_equalsSum:


[TestMethod] public void OrderController_orderSummaryTotalCheck_equalsSum() { // arrange const int TestOrderId = 10; IOrderRepository repository = new ModelFakes.StubIOrderRepository { FindInt32 = id => { Order testOrder = new Order { Id = 1, CustomerName = "smith", TaxRate = 5 }; return testOrder; }, OrderLinesInt32 = id => { var OrderLines = GetOrderLines(); return OrderLines.AsQueryable(); } }; var controller = new OrderController(repository); // act var result = controller.OrderLines(TestOrderId) as ViewResult; var data = result.Model as OrderSummaryViewModel; // assert Assert.AreEqual(5675, data.Total, "Order summary total not correct"); } private static IQueryable<OrderLines> GetOrderLines() { var orderLines = new List<OrderLines> {

Page 60 of 82

Testing Unitario con Microsoft Fakes - Captulo 6: Hands-on Lab


new OrderLines { Id = 10, IsTaxable = true, ProductName = "widget1", Quantity = 10, UnitCost = 10 }, new OrderLines { Id = 10, IsTaxable = false, ProductName = "widget2", Quantity = 20, UnitCost = 20 }, new OrderLines { Id = 10, IsTaxable = true, ProductName = "widget3", Quantity = 30, UnitCost = 30 }, new OrderLines { Id = 10, IsTaxable = false, ProductName = "widget4", Quantity = 40, UnitCost = 40 }, new OrderLines { Id = 10, IsTaxable = true, ProductName = "widget5", Quantity = 50, UnitCost = 50 }, }; return orderLines.AsQueryable(); }

Paso 9 Completar la implementacin de la accin del controlador


1. Aade el siguiente using a la clase OrderController:
using System.Linq;

NOTA

En este punto, ya podemos ejecutar el test desde el Test Explorer y veremos que falla. La prxima tarea es modificar la lgica del mtodo para hacer que funcione.

2.

Podemos copiar este cdigo en el mtodo de accin OrderLines del controlador:


public ActionResult OrderLines(int id) { // locate the order by ID via repository var order = this.repository.Find(id); // get the corresponding orderlines var orderLines = this.repository.OrderLines(order.Id); // initialize the calculation values double total = 0d; double taxRate = order.TaxRate / 100; double taxMultiplier = 1 + taxRate; // run through the list and just summarize conditionally if taxable or not foreach (var lineItem in orderLines) { if (lineItem.IsTaxable) { total += lineItem.Quantity * lineItem.UnitCost * taxMultiplier; } else { total += lineItem.Quantity * lineItem.UnitCost; } } // make the view model and set its properties var viewModel = new OrderSummaryViewModel(); viewModel.Order = order; viewModel.OrderLines = orderLines.ToList();

Page 61 of 82

Testing Unitario con Microsoft Fakes - Captulo 6: Hands-on Lab


viewModel.Total = total; return this.View(viewModel); }

Paso 10 Ejectuar el test unitario


1. 2. 3. 4. Abre el Test Explorer y compila la solucin (F6) Una vez que se haya compilado, el Test Explorer debera mostrar un solo test en la solucin: OrderController_orderSummaryTotalCheck_equalsSum bajo la categora Not Run Tests Haz click en Run All para ejecutar todos los test (en esta solucin solo hay 1). Despus de compilar, y ejecutar los test, veremos el indicador de que el test ha pasado en el Test Explorer.

En este punto, hemos validado que el mtodo de accin del OrderController (OrderLines) devuelve un modelo con una propiedad Total que se corresponde con nuestros datos de test, basados en el clculo de tasas.
REVISIN
En este ejercicio, hemos eliminado la dependencia de la base de datos y hemos visto cmo Microsoft Fakes Stubs se puede usar para testear componentes a travs del aislamiento de dependencias. Podis ver el cdigo final en Handson Lab\Excercise 1\end.

Page 62 of 82

Testing Unitario con Microsoft Fakes - Captulo 6: Hands-on Lab

Ejercicio 2: Usando Shims para aislarnos del sistema de archivos y de la fecha (20 30 min)
OBJETIVO
1.
En este ejercicio, veremos cmo usar Shims para aislarnos el cdigo que queremos testear de las dependencias del sistema de archivos y de la fecha del sistema.

Escenario
Eres uno de los desarrolladores de una empresa de software. Tu equipo est a cargo del mantenimiento de un assembly de log que se usa en todas las aplicaciones del departamento. Te han asignado la tarea de aadir una nueva caracterstica a la clase central del sistema: el LogAggregator. Esta clase puede aadir archivos de log a un directorio y filtrar los archivos a slo unos das. No hay test unitarios para ese componente por ahora. Antes de cambiar nada en esa parte del cdigo, quieres asegurarte de que no rompes nada. Desafortunadamente, la clase LogAggregator no ha sido diseada para que se fcil asilarla ni del sistema de archivos ni de la hora del sistema a base de pasarle los valores necesarios. El cdigo no ofrece ninguna forma de inyectarle un stub, est ocultando su implementacin. Por lo tanto, vamos a crear nuestro primer Shim para poder testear la clase LogAggregator.

Paso 1 Revisar la clase LogAggregator


Abre la solucin EnterpriseLoger.sln en Hands-on Lab\Excercies 2\start y abre el archivo LogAggergator.cs. Deberamos ver el siguiente cdigo en el editor:
namespace Microsoft.ALMRangers.FakesGuide.EnterpriseLogger { using System; using System.Collections.Generic; using System.Globalization; using System.IO; public class LogAggregator { public string[] AggregateLogs(string logDirPath, int daysInPast) { var mergedLines = new List<string>(); var filePaths = Directory.GetFiles(logDirPath, "*.log"); foreach (var filePath in filePaths) { if (this.IsInDateRange(filePath, daysInPast)) { mergedLines.AddRange(File.ReadAllLines(filePath)); } } return mergedLines.ToArray(); } private bool IsInDateRange(string filePath, int daysInPast) { string logName = Path.GetFileNameWithoutExtension(filePath);

Page 63 of 82

Testing Unitario con Microsoft Fakes - Captulo 6: Hands-on Lab


if (logName.Length < 8) { return false; } string logDayString = logName.Substring(logName.Length - 8, 8); DateTime logDay; DateTime today = DateTime.Today; if (DateTime.TryParseExact(logDayString, "yyyyMMdd", CultureInfo.InvariantCulture, DateTimeStyles.None, out logDay)) { return logDay.AddDays(daysInPast) >= today; } return false; } }

}
El cdigo que vemos aqu es una clase para para centrarnos. Puedes hacer todos los pasos de este laboratorio basndote en esta clase. Si no tienes acceso a la solucin que tenemos preparada, puedes generarlo creando una class library y copiando y pegando el cdigo en l.

Paso 2 Crea un proyecto de test


1. 2. Aade un proyecto del tipo Visual C# Unit Test Proyect a la solucin llamado EnterpriseLogger.Tests.Unit En el proyecto EnterpriseLogger.Tests.Unit aade una referencia al proyecto EnterpriseLogger.

Paso 3 Crea el primer test


1. 2. Renombra UnitTetst1.cs a LogAggregatorTests.cs Abre LogAggergatorTests.cs y aade el siguiente using:
using Microsoft.ALMRangers.FakesGuide.EnterpriseLogger;

NOTA

3.

Reemplaza el mtodo TestMethod1 por el siguiente:


[TestMethod] public void AggregateLogs_PastThreeDays_ReturnsAllLinesFromPastThreeDaysAndToday() { // Arrange var sut = new LogAggregator(); // Act var result = sut.AggregateLogs(@"C:\SomeLogDir", daysInPast: 3); // Assert Assert.AreEqual(4, result.Length, "Number of aggregated lines incorrect."); Assert.AreEqual("ThreeDaysAgoFirstLine", result[0], "First line incorrect."); Assert.AreEqual("TodayLastLine", result[3], "Last line incorrect."); }

4.

Haz clic derecho en el mtodo y selecciona la opcin Run Tests. El test empezar a ejecutarse y fallar.
Page 64 of 82

Testing Unitario con Microsoft Fakes - Captulo 6: Hands-on Lab


Este test que hemos creado testea el mtodo AggregateLogs y dice lo que prue ba en su propio nombre, que es comprobar que la funcin AggregateLogs cuando se llama con el path de un directorio que contiene archivos de logs y un 3 en la variable daysInPast debera devolver todas las lneas de aquellos logs desde hace tres das hasta ahora. Sin embargo, este test slo funcionara si el directorio C:\SomeLogDir existe y mgicamente contiene archivos con los datos necesarios para este test. Esto se puede hacer escribiendo algn cdigo de configuracin. Sin embargo, el test resultante sera ms un test de integracin ms que un test unitario real, ya que estara usando el sistema de archivos. Para hacer que sea un test unitario de verdad, vamos a aislar el test de los mtodos estticos que llama para acceder a la fecha del sistema y al sistema de archivos. Vamos a revisar el cdigo que queremos testear:
public string[] AggregateLogs(string logDirPath, int daysInPast) { var mergedLines = new List<string>(); var filePaths = Directory.GetFiles(logDirPath, "*.log"); foreach (var filePath in filePaths) { if (this.IsInDateRange(filePath, daysInPast)) { mergedLines.AddRange(File.ReadAllLines(filePath)); } } return mergedLines.ToArray(); }

El mtodo esttico que vemos representa un patrn bastante adecuado para las clases File y Directory del namespace System.IO. Puede haber razones para usar el sistema de archivos de manera diferente, ya lo veremos ms adelante en este ejercicio. Ahora vamos a hacer un shim para las funciones Directory.GetFiles() y File.ReadAllLines().

Paso 4 Aadir shims como fake del sistema de archivos


1. Primero, debemos decirle a Visual Studio para qu dependencias queremos generar Fakes. En el Solution Explorer, en el proyecto EnterpriseLogger.Tests.Unit expande el nodo References, clic derecho en System y elige la opcin Add Fakes assembly:

Los archivos del directorio Fakes le dicen a Visual Studio para qu tipos hay que generar los shims. Puedes usar estos archivos para personalizar para qu tipos generar un shims o un stub. La razn por la que hay dos archivos es porque el namespace System genera ms de un assembly. Como mscorlib.dll no se puede referenciar
Page 65 of 82

NOTA

Visual Studio ha creado un nuevo directorio llamado Fakes que contiene dos archivos XML y ha aadido referencias a dos assemblies recin generados.

Testing Unitario con Microsoft Fakes - Captulo 6: Hands-on Lab


directamente, para seguir usando Fakes, siempre se aade un assembly fake de mscorlib.dll cuando se hace un Fake de System.dll. 2. Por convencin, los tipos Fakes para el namespace System.IO estn en el namespaces System.IO.Fakes. Para usarlos, trabajaremos con una sentencia using. En el Solution Explorer haz doble clic en LogAggregatorTests.cs y aade el siguiente using al principio el archivo:
using System.IO.Fakes;

3.

Cambia el mtodo de test en LogAggregatorTests.cs de la siguiente forma. Los cambios estn en negrita:
// Arrange var sut = new LogAggregator(); ShimDirectory.GetFilesStringString = (dir, pattern) => new string[] { @"C:\someLogDir\Log_20121001.log", @"C:\someLogDir\Log_20121002.log", @"C:\someLogDir\Log_20121005.log", }; ShimFile.ReadAllLinesString = (path) => { switch (path) { case @"C:\someLogDir\Log_20121001.log": return new string[] {"OctFirstLine1", "OctFirstLine2"}; case @"C:\someLogDir\Log_20121002.log": return new string[] {"ThreeDaysAgoFirstLine","OctSecondLine2"}; case @"C:\someLogDir\Log_20121005.log": return new string[] {"OctFifthLine1", "TodayLastLine"}; } return new string[] {}; }; // Act var result = sut.AggregateLogs(@"C:\SomeLogDir", daysInPast: 3); // Assert Assert.AreEqual(4, result.Length, "Number of aggregated lines incorrect."); CollectionAssert.Contains(result, "ThreeDaysAgoFirstLine", "Expected line missing from aggregate d log."); CollectionAssert.Contains(result, "TodayLastLine", "Expected line missing from aggregated log.") ;

Vamos a revisar el cdigo. Hemos aadido dos sentencias:


ShimDirectory.GetFilesStringString = [some delegate]; ShimFile.ReadAllLinesString = [some delegate];

Estas sentencias le dicen al framework de Microsoft Fakes qu mtodos de deben ser interceptados y qu cdigo se debe ejecutar. Los nombres estn puestos por convencin. El nombre de la clase que se usa para acceder al Shim de un tipo concreto es el nombre del tipo, con el prefijo de Shim. El nombre de la propiedad usada para setear el delegado de la interceptacin de la llamada es el nombre del mtodo con el sufijo de los nombres de los tipos de sus parmetros. Esta convencin nos permite setear diferentes delegados para diferentes sobrecargas de un mtodo. El cdigo que hemos asignado a estas propiedades en el cdigo anterior hace que el mtodo GetFiles(string,string) devuelva tres paths (C:\someLogDir\Log_20121001.log, C:\someLogDir\Log_20121002.log, y C:\someLogDir\Log_20121005.log) que tienen fechas codificadas en su nombre. (No nos importa el patrn que
Page 66 of 82

Testing Unitario con Microsoft Fakes - Captulo 6: Hands-on Lab


se use en el nombrado de los directorios). Y ahora, el mtodo ReadAllLines(string) devuelve arrays de strings que representan las lneas de los archivos de logs imaginario, basndose en el parmetro del path. 4. 5. Click derecho en el cuerpo del mtodo de test y seleccionamos Run Tests. El test falla. En el Test Explorer bajo Failed Tests, seleccionamos el test AggregateLogs y vemos la descripcin del error.

El mensaje de error nos dice que usemos un ShimsContext. Esto es necesario para indicar el contexto en el que se usarn los shims. Los shims slo se usaran en ese contexto. Sin ese contexto, los shims podran provocar efectos secundarios en el resto de test. As que vamos a hacerlo
La configuracin de un ShimsContext debera hacerse siempre en una sentencia using, y nunca en un mtodo setup/initialize o en un teardown/cleanup. Esto podra dejar que los shims estn definidos en otras partes de los test que no deberan afectando y alterando los test.

NOTA

6.

En el bloque de usings al principio del LogAggregatorTests.cs aade el siguiente using:


using Microsoft.QualityTools.Testing.Fakes;

7.

Cambia el mtodo de test de la siguiente manera.


using (ShimsContext.Create()) { // Arrange // Act // Assert }

8.

En el Test Explorer haz clic en Run Hay test que no pasan.

Los test fallan esta vez con el mensaje Assert.AreEqual failed. Expected: <4>, Actual <0>. Number of aggregated lines incorrect. Esto es debido a que las fechas codificadas en los nombres de los archivos que pusimos el mtodo shim GetFiles(string,string) son de ms de tres das de antiguiedad y ninguna de ellas entran en el filtro. Vamos a revisar el mtodo LogAggregator.IsInDateRange(string,int) que es el responsable del filtrado de fechas:
private bool IsInDateRange(string filePath, int daysInPast) { string logName = Path.GetFileNameWithoutExtension(filePath); if (logName.Length < 8) { return false; } string logDayString = logName.Substring(logName.Length - 8, 8); DateTime logDay; DateTime today = DateTime.Today; if (DateTime.TryParseExact(logDayString, "yyyyMMdd", CultureInfo.InvariantCulture, DateTimeStyles.None, out logDay)) { return logDay.AddDays(daysInPast) >= today; } return false; }

Page 67 of 82

Testing Unitario con Microsoft Fakes - Captulo 6: Hands-on Lab


Este mtodo se basa en una llamada a la propiedad esttica Today de la clase DateTime. Esto es lo que hace que el test falle cuando se ejecuta en un da que no es el 5 de Octubre de 2012. Para hacer que este test pase, vamos a hacer un shim de esa propiedad

Paso 5 Aadir un Shim para aislarnos de la fecha del sistema


1. En la seccin de usings de LogAggregatorTests.cs aade la lnea:
using System.Fakes;

2.

Aade la siguiente lnea debajo del comentario //Act


ShimDateTime.TodayGet = () => new DateTime(2012, 10, 05);

3.

En el Test Explorer, haz clic en Run Failed Test. Ya pasa el test.

Paso 6 (Opcional) Ejectua el test con el debugger para entender el flujo de ejecucin.
1. 2. Pon el cursor en la primera lnea del cdigo, debajo del comentario //Act En el men principal, ve a Debug, Toggle Breakpoint para aadir un punto de ruptura:

3. 4. 5.

Haz clic derecho en cualquier parte del test y selecciona la opcin Debug Tests. Despus de que lleguemos al breakpoint, pulsa F11. Contina paso a paso la ejecucin del cdigo (usando F11) hasta que llegamos a la primera ejecucin de la expresin lambda:

Cuando usamos un Shim, todo el dominio de la aplicacin es enrutado a travs del contexto del shim; as que si hacemos una llamada al objeto shim cuando debugeamos, veremos los resultados del shim en lugar de los valores reales.

NOTA

6.

Contina la ejecucin hasta que pases por todas las expresiones lambda, y en el men principal elige Debug, Continue.
Hemos conseguido aislar el cdigo de produccin del LogAggregator de sus dependencias con el sistema de archivos y de la fecha del sistema sin tener que cambiarlo. Puedes ver el cdigo final en Hands-on Lab\Exercies2\end

REVISIN

Page 68 of 82

Testing Unitario con Microsoft Fakes - Captulo 6: Hands-on Lab

Ejercicio 3: Usando Microsoft Fakes con SharePoint (20 30 min)


Escenario
SharePoint usa muchas clases privadas y selladas, lo que significa que no tiene constructores que nos permitan escribir tests. La falta de interfaces pblicas y clases virtuales hace que no sea posible usar Stub. Esto es algo comn en cualquier tecnologa que ofrezca un framework para resolver cierto tipo de problemas. Tambin suele ser habitual en sistemas heredados que no siguieron las conocidas buenas prcticas disea tu sistema para que sea fcilmente testable. Estos sistemas heredados no usan el patrn de inyeccin de dependencias. Eso deja al desarrollador con dos opciones si quiere escribir tests. 1. 2. No hacer testing unitario. Confiar en los test de integracin basados en UI dejando el subsistema de SharePoint como una caja negra. Hacer un wrapper de todo el subsistema que llama a los objetos de SharePoint a travs de un interfaz y una implementacin. Esto puede ser una buena solucin en muchos proyectos. Ya que los detalles de implementacin del cdigo de SharePoint se ocultan tras la interfaz, se puede testear unitariamente todo el cdigo que no sea de SharePoint. Los detalles de implementacin de las caractersticas de SharePoint se terminan tratando como una caja negra, as que estamos como en el caso 1. Estas cajas negras, sin embargo, pueden ser testadas usando las tcnicas de Shim que vimos ntes.

Sin embargo, en el caso de SharePoint, la tcnica del warpper no siempre es posible ya que el desarrollador de SharePoint suele crear elementos como recibidores de eventos y web parts que necesitan una infraestructura dentro de SharePoint, no encima. Esto hace que no sea prctico usar este tipo de encapsulacin. Ninguna de esas dos opciones soluciona el problema principal. Tenemos que ser capaces de sustituir las instancias de los objetos de SharePoint. As que necesitamos usar la funcionalidad que los Shims nos ofrecen.

Preparacin
Este ejercicio requiere que el PC tenga una versin soportada de Visual Studio y SharePoint 2010 Foundation (aunque cualquier SKU posterior de SharePoint tambin se puede usar). La instalacin de SharePoint no se ejecutar directamente durante este ejercicio, pero los assemblies que se instalarn en el GAC y en Program Files los usaremos para el proceso e Faking. Otra opcin, si no tenemos estas herramientas disponibles en nuestro PC, es usar la mquina virtual de la demo de Brian Keller de VS2012 ALM con Hyper-V (http://blogs.msdn.com/b/briankel/archive/2011/09/16/visual-studio-11-application-lifecycle-managementvirtual-machine-and-hands-on-labs-demo-scripts.aspx) ya que tiene todos los componentes necesarios.
AVISO
SharePoint debe estar instalado en la mquina de desarrollo, no slo una copia de las dlls, para que todos los assemblies necesarios para crear los fakes estn disponibles

Paso 1 Crear una caracterstica de ejemplo de SharePoint


1. En Visual Studio, aade una class library para .NET 3.5 llamada Samples.SharePoint
Page 69 of 82

OBJETIVO

En este ejercicio, veremos cmo usar Shims para testear que un recibidor de eventos de SharePoint funciona correctamente.

Testing Unitario con Microsoft Fakes - Captulo 6: Hands-on Lab


En cdigo de produccin, podemos usar cualquier plantilla de SharePoint para la caracterstica que vamos a desarrollar, de este modo se seleccionar la versin correcta de .NET. Sin embargo, para que este HOL sea lo ms simple posible, vamos a usar una Class Library bscia. No es necesario crear un sitio de SharePoint. .NET 3.5 debe ser seleccionado como target del proyecto ya que SharePoint 2010 usa .NET 3.5

NOTA

2.

3. 4.

Aade una referencia a la dll Microsoft.SharePoint.dll. Normalmente, este archivo se instala como parte del servidor de SharePoint en c:\Program Files\Common Files\Microsoft Shared\ Web Server Extension\T4\ISAPI. Renombra el archivo Class1.cs a ContentTypeItemEventReceiver.cs En el archivo ContentTypeItemEventReceiver.cs reemplaza el cuerpo de la clase con el siguiente cdigo:
using Microsoft.SharePoint; public class ContentTypeItemEventReceiver : SPItemEventReceiver { public void UpdateTitle(SPItemEventProperties properties) { using (SPWeb web = new SPSite(properties.WebUrl).OpenWeb()) { SPList list = web.Lists[properties.ListId]; SPListItem item = list.GetItemById(properties.ListItemId); item["Title"] = item["ContentType"]; item.SystemUpdate(false); } } public override void ItemAdded(SPItemEventProperties properties) { this.EventFiringEnabled = false; this.UpdateTitle(properties); this.EventFiringEnabled = true; } }

5.

Esta ser nuestra aplicacin de ejemplo. Hemos aadido un recibidor de eventos que se llamar cada vez que se aade un elemento a la lista de SharePoint. Este recibidor edita el ttulo del nuevo elemento con lo que haya en la propiedad ContentType. Compila el proyecto. Debera compilar sin errores. Si hay algn problema, comprueba los errores.

Paso 2 Crear un test


1.
NOTA

Aade un Unit Test Proyect para .NET 4 a la solucin con el nombre Samples.SharePoint.Tests
En este ejercicio, usamos MSTest como framework de testing unitario. Sin embargo, podramos usar otro como nUnit o xUnit gracias a Visual Studio Extension Test Adaptors. Microsoft Fakes no tiene ninguna dependencia con MSTest. Al contrario del proyecto que contiene el cdigo de SharePoint, que necesita el framework 3.5, el proyecto que contiene los test puede usar versiones nuevas del framework. Para el testing de SharePoint se recomienda usar .NET 4.0 para evitar problemas cuando se generen los Fakes.

2. 3. 4.

Aade una referencia al proyecto Samples.SharePoint Aade una referencia a la dll Microsoft.SharePoint.dll, como hiciste en el proyecto de ejemplo. En la seccin de referencias del proyecto de test selecciona la referencia Microsoft.SharePoint y haz clic derecho. Selecciona la opcin Add Fake Assembly. Esto puede tardar unos segundos (incluso algunos
Page 70 of 82

Testing Unitario con Microsoft Fakes - Captulo 6: Hands-on Lab


minutos). Cuando termine, deberas poder ver los archivos que se han creado en el directorio de fakes y aade una referencia a la dll Microsoft.SharePoint.Fakes.dll:

Si por alguna razn la creacin de los fakes falla y tienes que repetir el proceso, asegrate de borrar el archivo del directorio de Fakes. Si no, aparecer un error cuando repitamos el proceso. La razn ms comn para que ocurra un error es que el proyecto de test est configurado para usar .NET 4.5. En este caso, Visual Studio no podr generar los fakes para SharePoint. Tenemos que seleccionar .NET 3.5 o 4 como target del proyecto de test

AVISO

5. 6. 7.

Renombra el archivo UnitTest1.cs a SharePointEventTests.cs Renombra el mtodo TestMethod1 con un nombre significativo Contributor_AddsNewItem_EventFires. Aade las referencias necesarias para el assembly fake de SharePoint y a la librera de faking

como

El cdigo listado al final de esta seccin muestra el test completo. Los siguientes pasos muestran la jerarqua de shims que se han creado: 8. Alrededor de todo el contenido del test, crea un bloque using para el ShimContext. Esto administra el contexto de las operaciones de shimming. Los shims funcionarn dentro de ese bloque. 9. Aade dos variables locales, systemUpdateHasBeenCalled y itemTitleValue. Sern usadas como flags para indicar que se ha llamado al cdigo correcto. 10. Crea un shim para un objeto SPItemEventProperties y configura su comportamiento para las tres propiedades que sern llamadas. 11. Crea un shim para interceptar la llamada al constructor SPSite
NOTA
En el cdigo de ejemplo, los parmetros del constructor shimeado comienzan con el smbolo @. Esto permite el uso de palabras reservadas para el nombre de las variables que, en este caso, hace ms fcil su comprensin.

12. Dentro del bloque SimSPSite configura el mtodo OpenWeb. Esto crea un nuevo objeto shim de SPWeb 13. Dentro del bloque, configura la propiedad List para que devuelva un SPListCollection. 14. Dentro del bloque, configura la propiedad Item para que devuelva un shim de un SPList, el parmetro GUID no se usa. 15. Dentro del bloque, configura el mtodo GetItemById para que devuelva un SPListItem, el parmetro Int no se usa. 16. Dentro del boque, configura la propiedad Item (get y set) y el mtodo SystemUpdate. Fjate que estamos definiendo el valor de la variable local que creamos en lo alto del test para comprobar que se han realizado ciertas llamadas. 17. Finalmente, despus de la creacin de shim anidados, crea una instancia de la clase a testear. 18. Aade una llamada al recibidor de eventos que queremos testear.
Page 71 of 82

Testing Unitario con Microsoft Fakes - Captulo 6: Hands-on Lab


19. Aade dos asserts para comprobar que se han hecho las llamadas que esperamos que se hagan. 20. Compila el proyecto. Debera compilar sin errores. Si hay alguno, comprueba los mensajes (comprueba el cdigo al final de esta seccin). 21. Abre el Test Explorer (en el men Test\Windows\Test Explorer). Deberas ver el nuevo test en la lista, si no lo ves, recompila la solucin. 22. Elige la opcin Run All. 23. El test debera ejecutarse y pasar. El cdigo completo del test sera este:
namespace { using using using using Microsoft.ALMRangers.FakesGuide.Sharepoint.Tests System; Microsoft.QualityTools.Testing.Fakes; Microsoft.SharePoint.Fakes; Microsoft.VisualStudio.TestTools.UnitTesting;

[TestClass] public class SharePointEventTests { [TestMethod] public void The_item_title_is_set_to_the_content_type_when_event_fires() { using (ShimsContext.Create()) { // arrange // create the local variables we will write into to check that the correct methods are called var systemUpdateHasBeenCalled = false; var itemTitleValue = string.Empty; // create the fake properties var fakeProperties = new ShimSPItemEventProperties() { WebUrlGet = () => "http://fake.url", ListIdGet = () => Guid.NewGuid(), ListItemIdGet = () => 1234 }; // create the fake site ShimSPSite.ConstructorString = (@this, @string) => { new ShimSPSite(@this) { OpenWeb = () => new ShimSPWeb() { ListsGet = () => new ShimSPListCollection() { ItemGetGuid = (guid) => new ShimSPList() { GetItemByIdInt32 = (id) => new ShimSPListItem() { ItemGetString = (name) => string.Format("Field is {0}", name), SystemUpdateBoolean = (update) => systemUpdateHasBeenCalled = true, ItemSetStringObject = (name, value) => itemTitleValue = value.ToString() } } } } }; }; // create the instance of the class under test var cut = new ContentTypeItemEventReceiver(); // act cut.ItemAdded(fakeProperties); // assert

Page 72 of 82

Testing Unitario con Microsoft Fakes - Captulo 6: Hands-on Lab


Assert.AreEqual(true, systemUpdateHasBeenCalled); Assert.AreEqual("Field is ContentType", itemTitleValue); } } } }

Microsoft ha publicado SharePoint Emulator, que son una versin de los comportamientos de Moles SharePoint. Muchas de las implementaciones de shim para el core de SharePoint se han empaquetado y se ofrecen como parte de los emuladores de SharePoint. Estos SharePoint Emulator se ven en el blog Introducing SharePoint 6Emulators y estn disponibles como un paquete Nuget

NOTA
6

http://blogs.msdn.com/b/visualstudioalm/archive/2012/11/26/introducing-sharepoint-emulators.aspx

REVISIN

En este ejercicio, hemos visto cmo los Shims de Microsoft Fakes se pueden usar para testear las caractersticas e SharePoint. Puedes ver el cdigo final en Hands-on Lab\Exercise 3\end

Page 73 of 82

Testing Unitario con Microsoft Fakes - Captulo 6: Hands-on Lab

Ejercicio 4: Haciendo testable cdigo heredado (20 30 min)


El cdigo heredado puede plantear problemas para refactorizarlo, especialmente cuando es cdigo altamente acoplado o que hace poco uso de interfaces. Para refactorizar el cdigo, es preferible tener tests unitarios que aseguren el comportamiento del cdigo antes de cambiarlo.

Escenario
En este escenario usaremos la aplicacin Traffic Simulator del directorio Excercise 4. En el componente Traffic Core, hay un conjunto de clases que hacen de Modelo del componente Traffic UI. Comenzaremos el proceso de crear tests para las clases City y Car para asegurar su comportamiento actual. La clase City expone la disposicin de la ciudad para el simulador Traffic. Esta clase consume el servicio WFC Traffic.RoadworkService a travs de una clase proxy que se invoca desde un mtodo privado. Ese mtodo privado se invoca desde un callback a una instancia de System.Thread.Timer que se crea en el setter de la propiedad llamada Run. Usaremos Shims para producir los test de la clase City que nos permitir shimear las referencias al servicio WCF y a la instancia del Timer. La clase Car representa un vehculo en el simulador. Esta clase consume un conjunto de objetos del componente Traffic Core a travs de la propiedad ShouldMove. Usaremos una combinacin de Stubs y Shims para poder testear esta propiedad

Paso 1 Crear un proyecto de test para el componente Traffic.Core


1. 2. 3. En la solucin ComplexDependencies, aade un nuevo proyecto con la plantilla Visual C# Unit Test y llmalo Traffic.Core.Tests Aade una clase llamada CityTests.cs En el nuevo proyecto de test, aade una referencia al proyecto Traffic.Core Despus de haber visto el cdigo a testear, podemos aadir un test unitario para testear la propiedad Run de la clase City. 4. 5. 6. Abre la clase City.cs el directorio Model del proyecto Traffic.Core Busca la propiedad Run y mira el cdigo. Fjate que el setter de la propiedad tiene una dependencia con una instancia de System.Threading.Timer, que invoca al mtodo OnTimer Ve a ese mtodo haciendo clic derecho en la llamada OnTimer y selecciona la opcin Go To Definition.

Vers que este mtodo llama al mtodo UpdateRoadwork(), que contiene una referencia al proxy cliente del servicio. Por lo que, cualquier test que ejecute la propiedad Run tendr una dependencia tando del Timer como del RoadwordServiceClient

Paso 2 Crear un test para la propiedad City.Run


Antes de usar Fakes, intentaremos generar un test unitario que asegure que la propiedad Run se puede setear a true. 1. 2. En el archivo CityTests.cs renombra el mtodo TestMethod1() a City_CanSetRunProperty_True(). Actualiza tus referencias para incluir System.Threading y Traffic.Core.Models y aade los usings necesarios:
using System; using System.Threading;

OBJETIVO

En este ejercicio, usaremos Shims y Stubs para conseguir que el cdigo heredado est testado

Page 74 of 82

Testing Unitario con Microsoft Fakes - Captulo 6: Hands-on Lab


using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.ALMRangers.FakesGuide.ComplexDependencies.Traffic.Core.Models;

3.

Aade el siguiente cdigo al mtodo:


[TestMethod] public void City_CanSetRunProperty_True() { City cityUnderTest = new City(); bool expected = true; cityUnderTest.Run = expected; Thread.Sleep(TimeSpan.FromSeconds(5)); Assert.AreEqual<bool>(expected, cityUnderTest.Run, "City.Run property should be set to true. "); }

Si intentas ejecutar este cdigo, el test fallar ya que las llamadas que hay por debajo al servicio Roadwork no sern invocadas. Adems, podemos decir que este test es frgil ya que es necesaria una llamada a Thread.Sleep para darle tiempo a la propiedad Run para que cree el Timer, registre el evento, e invoque al servicio Roadwork. Ahora vamos a intentar testar esta propiedad usando Shims para aislarla de sus dependencias externas.

Paso 3 Aadir las referencias de Fakes al assembly Traffic.Core


1- Expande el nodo References del proyecto Traffic.Core.Tests, haz clic derecho en Traffic.Core y selecciona la opcin Add Fakes Assembly Esto crea los Stubs y Shims necesarios para el componente Traffic.Core. Ahora queremos asegurarnos de que cuando llamamos al servicio Roadwork, nuestra implementacin de Shim se invocar en lugar del servicio actual y que devolveremos un Stub como resultado. Para asegurarnos de que nuestra implementacin ha sido invocada, usaremos una variable privada booleana que pondremos a true en el mtodo. Adems, tambin queremos ofrecer una implementacin alternativa al constructor del RoadworkServiceClient. Esto asegura que una clase proxy muy bsica se ha habilitado.

Paso 4 Modificar el test unitario para la propiedad Run


1. Aade las siguientes referencias al proyecto Traffic.Core.Tests: System.Runtime.Serialization System.ServiceModel En la clase CitiTests aade estos usings:
using System.Collections.Generic; using System.Linq; using Microsoft.QualityTools.Testing.Fakes; using Microsoft.ALMRangers.FakesGuide.ComplexDependencies.Traffic.Core.RoadworkServiceReference.Fakes

2.

3.

Para usar los mtodos del Shim, tenemos que envolver las llamadas donde invocamos a la propiedad Run en un ShimsContext. Esto asegurar que las llamadas se sustituirn slo en el cdigo que se est testeando. Envuelve el contenido del mtodo con este cdigo:
using (ShimsContext.Create()) { }

4.

Bajo la lnea en la que se declara el booleano expected, aade otra variable local booleana llamada hasServiceBeenInvoked e inicialzala a false.

Page 75 of 82

Testing Unitario con Microsoft Fakes - Captulo 6: Hands-on Lab


5. No queremos que se invoque al constructor actual del RoadworkServiceClient as que usaremos Shims para crear una implementacin alternativa. Justo despus de la variable booleana que acabamos de declarar aade el siguiente cdigo:
ShimRoadworkServiceClient.Constructor = (x) => { };

6.

Ahora, aadamos la implementacin para la operacin RetrieveCurrentBlock a travs de la clase ShimRoadworkServiceClient. Este mtodo devuelve un array de Block; usaremos nuestra propia implementacin para setear nuestra variable local hasServiceBeenInvoked a true y devolveremos un Stub. Justo despus del cdigo que hemos aadido en el paso 4, aade el siguiente cdigo:
ShimRoadworkServiceClient.AllInstances.RetrieveCurrentBlockArray = (instance, blocks) => { hasServiceBeenInvoked = true; return new List<StubImpediment> { new StubImpediment { description = string.Empty, location = blocks.FirstOrDefault(), relativeSpeed = double.MinValue } }.ToArray(); };

7.

Aade otro assert para asegurarnos de que la variable hasServiceBeenInvoked vale lo que debe valer:
Assert.IsTrue(hasServiceBeenInvoked, "City.Run should invoke the Roadwork service");

Aqu tenemos el cdigo completo de la clase CityRun:


[TestMethod] public void City_CanSetRunProperty_True() { using (ShimsContext.Create()) { City cityUnderTest = new City(); bool expected = true; bool hasServiceBeenInvoked = false; ShimRoadworkServiceClient.Constructor = (x) => { }; ShimRoadworkServiceClient.AllInstances.RetrieveCurrentBlockArray = (instance, blocks) => { hasServiceBeenInvoked = true; return new List<StubImpediment> { new StubImpediment { description = string.Empty, location = blocks.FirstOrDefault(), relativeSpeed = double.MinValue } }.ToArray(); }; cityUnderTest.Run = expected; Thread.Sleep(TimeSpan.FromSeconds(5)); Assert.AreEqual<bool>(expected, cityUnderTest.Run, "City.Run property should be set to true."); Assert.IsTrue(hasServiceBeenInvoked, "City.Run should invoke the Roadwork service");

Page 76 of 82

Testing Unitario con Microsoft Fakes - Captulo 6: Hands-on Lab


} }

Ahora podemos ejecutar este test desde el Test Explorer y el test pasar. Hemos usado Shims para aislarnos de la clase RoadworkSericeClient. Nuestro test unitario sigue siendo frgil ya que sigue necesitando la llamada al mtodo Thread.Sleep para que le d tiempo a la propiedad Run a inicializar la clase Timer.

Paso 5 Aadir una referencia Fake de la clase System.Timer


En este momento, queremos eliminar la llamada a Thread.Sleep de nuestro test de la propiedad Run de la clase City para que no sea dependiente del tiempo de inicializacin de la propiedad Run. Para ello, eliminaremos la dependencia del constructor Timer ofreciendo una alternativa a travs de la clase ShimTimer. 1. Expande el nodo References del proyecto Traffic.Core.Tests, clic derecho en System y selecciona la opcin Add Fakes Assembly

Fjate que se han creado las referencias a System.4.0.0.0.Fakes y mscorlib.4.0.0.0.Fakes. Esto es debido a que el namespace System existe tambin en el assembly mscorlib. Expande el directorio Fakes en el proyecto de Test y veremos que se han generado dos archivos correspondientes a las nuevas referencias que se han aadido mscrolib.fakes y System.fakes. 2. Este paso es necesario ya que Shims por defecto no crear un namespace para System.Threading. Abre el archivo mscorlib.fakes y corrgelo para que sea as:

3.

En la clase CityTests.cs, aade un using a System.Threading.Timer.Fakes

Ahora vamos a modificar el test para reemplazar la llamada al constructor de Timer con nuestra propia implementacin. El constructor de Timer usado es uno que recibe algunos parmetros para inicializar; tenemos que encontrar el que se corresponde con la signatura de nuestra clase ShimTimer. De nuevo, usaremos una variable local que nos permita comprobar que se ha llamado a nuestra implementacin. 4. Renombra la variable local hasServiceBeenInvoked a hastimerBeenInvoked y asegrate de que la referencia de esta variable tambin ha sido renombrada. Borra el cdigo que configura el shim del constructor de RoadworkServiceClient y las llamadas a RetrieveCurrentBlockArray. Ahora, aade la implementacin para el constructor del Timer que tiene cuatro parmetros un callback, un object, y dos TimeSpan. En esta implementacin pon la variable hasTimerBeenInvoked a true. El cdigo debera ser algo as:
ShimTimer.ConstructorTimerCallbackObjectTimeSpanTimeSpan = (timer, callback, state, dueTime, per iod) => { // Do nothing else but confirm that our implementation was called hasTimerBeenInvoked = true; };

Nuestro test refactorizado de City.Run debera ser algo as:


/// <summary> /// Test to ensure that the City Run property can be set to true. /// </summary> [TestMethod] public void City_CanSetRunProperty_True() {

Page 77 of 82

Testing Unitario con Microsoft Fakes - Captulo 6: Hands-on Lab


using (ShimsContext.Create()) { City cityUnderTest = new City(); bool expected = true; bool hasTimerBeenInvoked = false; ShimTimer.ConstructorTimerCallbackObjectTimeSpanTimeSpan = (timer, callback, state, dueTime, period) => { // Do nothing else but confirm that our implementation was called here. hasTimerBeenInvoked = true; }; cityUnderTest.Run = expected; Assert.AreEqual<bool>(expected, cityUnderTest.Run, "City.Run property should be set to true."); Assert.IsTrue(hasTimerBeenInvoked,"City.Run should invoke instantiate Timer instance."); } }

Ejecuta el test desde el Test Explorer y debera pasar. Ya podemos decir que la propiedad City.Run est testada.

Paso 6 Crear un test unitario para el constructor de Car


Abre la clase Car.cs del directorio Model del proyecto Traffic.Core. Fjate que el constructor recibe dos parmetros una instancia de Traffic.Core.Algorithms.RoutingAlgorithm y una implementacin de System.Windows.Media.Bursh. Ambos parmetros se usan para inicializar el estado de la instancia de Car setenado el estado de las propiedades Car.VehicleColor y Car.Routing. Adems, tambin se inicializa una propiedad pblica del tipo System.Random llamada RandomGenerator que tiene un setter privado. El primer test unitario que hay que crear es uno que compruebe el comportamiento del actual del constructor de Car 1. 2. Aade una nueva clase de test llamada CarTests.cs al proyecto Traffic.Core.Tests Aade los siguientes usings:
using System.Windows.Media; using Microsoft.ALMRangers.FakesGuide.ComplexDependencies.Traffic.Core.Algorithms.Fakes; using Microsoft.ALMRangers.FakesGuide.ComplexDependencies.Traffic.Core.Models; using Microsoft.ALMRangers.FakesGuide.ComplexDependencies.Traffic.Core.Models.Fakes; using Microsoft.QualityTools.Testing.Fakes; using Microsoft.VisualStudio.TestTools.UnitTesting;

3.

Renombra el TestMethod1() a Car_Constructor_ShouldInitializeDependentPropertiesSuccessfully()

En lugar de una instancia de Traffic.Core.Algorithms.RoutingAlgorithm usaremos un StubRoutingAlgorithm para crear una variable local llamada expectedAlgorithm y seleccionamos un valor System.Windows.Media.Brushes para asignarla a una variable local llamada expectedColor. 4. En el mtodo de test, aade las siguientes lneas de cdigo:
var expectedAlgorithm = new StubRoutingAlgorithm(); var expectedColor = Brushes.Aqua;

5.

Ahora, crea la instancia del Car usando las dos variables locales como parmetros de entrada del constructor:
Car codeUnderTest = new Car(expectedAlgorithm, expectedColor);

Page 78 of 82

Testing Unitario con Microsoft Fakes - Captulo 6: Hands-on Lab


6. Finalmente, comprueba que el estado de la instancia de Car es el que debe ser:
Assert.AreSame(expectedAlgorithm, codeUnderTest.Routing, "The Car constructor should initialize the routing algorithm correctly."); Assert.AreEqual<Brush>(expectedColor, codeUnderTest.VehicleColor, "The Car constructor should in itialize the vehicle color correctly."); Assert.IsNotNull(codeUnderTest.RandomGenerator, "The Car constructor should initialize the rando m generator correctly.");

7.

Ejecuta todos los test unitarios con el Test Explorer para asegurarnos de que todos pasan.

Paso 7 Aade un test para la propiedad Car.ShouldMove


Revisa la propiedad ShouldMove de la clase Car.cs. El getter de la propiedad tiene varias sentencias condicionales para determinar qu valor booleano devolver, dependiendo del estado de la propiedad llamada Location. Esta propiedad no se inicializa por el constructor y en el getter de ShouldMove, hay varias comprobaciones de null tanto para Location como para las propiedades hijas. La lgica del bloque actual depende de una llamada al mtodo Location.Road.IsFree si devuelve true. Esto hace una llamada al mtodo DiscoveredRoutes.ToRoutePart e interacta con la propiedad local System.Random. Continuando con el ejercicio de testear este cdigo, produciremos algunos test unitarios simples para comprobar el estado de esta propiedad. Le primer test que haremos ser testear la condicin cuando la propiedad Location sea null. 1. 2. 3. 4. Aade un nuevo test llamado Car_ShouldMoveProperty_ReturnsFalseIFLocationIsNull a la clase CarTests.cs. Debe ser decorado con el atributo TestMethod. En el cuerpo del mtodo, repite los puntos 4 y 5 del Paso 6 para obtener los datos para el test. Ahora setea la propiedad Location de la variable codeUnderTest a null. Ahora comprueba que la propiedad codeunderTest.ShouldMove es false. Aade un mensaje de error para indicar al desarrollador qu hacer si el valor de la propiedad no es vlido.
/// <summary> /// Test to ensure that the Car.ShouldMove property returns false where Location is null. /// </summary> [TestMethod] public void Car_ShouldMoveProperty_ReturnsFalseIfLocationIsNull() { var stubAlgorithm = new StubRoutingAlgorithm(); var testBrush = Brushes.AliceBlue; Car codeUnderTest = new Car(stubAlgorithm, testBrush); codeUnderTest.Location = null; Assert.IsFalse(codeUnderTest.ShouldMove, "The Car.ShouldMove property should return false wh ere Car.Location is null."); }

El test unitario completo ser:

El siguiente test unitario que haremos ser testear el getter de ShouldMove, cuando la propiedad Loation.Road sea null 5. 6. Aade un nuevo mtodo de test llamado Car_ShouldMoveProperty_ReturnsFalseIfLocationRoadIsNull y repite los puntos 4 y 5 del Paso 6. Usa una instancia del tipo StubElementLocation con la propiedad Road a null. Asgnala a la propiedad codeUnderTest.Location de la siguiente manera:
codeUnderTest.Location = new StubElementLocation { Road = null };

7.

Ahora aade un assert para comprobar que la propiedad codeUnderTest.ShouldMove es false. De nuevo, aade un mensaje de error adecuado.
Page 79 of 82

Testing Unitario con Microsoft Fakes - Captulo 6: Hands-on Lab


El cdigo completo del test ser algo as:
/// <summary> /// Test to ensure that the Car.ShouldMove property returns false where Location is null. /// </summary> [TestMethod] public void Car_ShouldMoveProperty_ReturnsFalseIfLocationRoadPropertyIsNull() { var stubAlgorithm = new StubRoutingAlgorithm(); var testBrush = Brushes.AliceBlue; Car codeUnderTest = new Car(stubAlgorithm, testBrush); codeUnderTest.Location = new StubElementLocation { Road = null }; Assert.IsFalse(codeUnderTest.ShouldMove, "The Car.ShouldMove property should return false where Car.Location.Road is null."); }

Ahora somos dependientes del resultado del mtodo Location.Result.IsFree. Como la implementacin de la propiedad ShouldMove depende de que esta llamada devuelva true, haremos un test unitario que comprueba el estado cuando esta propiedad devuelva false. Como la propiedad Location.Result es del tipo Block, tendremos que usar una instancia de ShimBlock 8. Aade un nuevo test llamado Car_ShouldMoveProperty_ReturnsFalseIfLocationRoadsIsFreeReturnsFalse a la clase CarTests.cs 9. En el cuerpo del mtodo, crea las variables locales stubAlgorithm y testBrush como en el test unitario anterior. 10. Aade una sentencia using ShimsContext.Create() para aislar las llamadas de la clase Block. 11. Usando la clase ShimBlock, ofrece una implementacin para asegurarnos de que cualquier llamada al mtodo IsFree devolver false. Aqu est el cdigo:
ShimBlock.AllInstances.IsFreeInt32 = (block, position) => { return false; };

12. Crea una instancia de Car usando las variables StubAlgorithm y testBrush 13. Crea una instancia de la clase StubElementLocation (mira el paso 6) pero en lugar de asignar un null a la propiedad Road, usa una instancia de un StubBlock. 14. Por ltimo, aade un assert para asegurarnos de que la propiedad codeUnderTest.ShouldMove devuelve false. El cdigo completo del test unitario es:
/// <summary> /// Test to ensure that the Car.ShouldMove property returns false where Location.Road.IsFree ret urns false. /// </summary> [TestMethod] public void Car_ShouldMoveProperty_ReturnsFalseIfLocationRoadIsFreeReturnsFalse() { var stubAlgorithm = new StubRoutingAlgorithm(); var testBrush = Brushes.AliceBlue; using (ShimsContext.Create()) { // Ensure any calls to Block.IsFree return false. ShimBlock.AllInstances.IsFreeInt32 = (block, position) => { return false; }; Car codeUnderTest = new Car(stubAlgorithm, testBrush); codeUnderTest.Location = new StubElementLocation { Road = new StubBlock() };

Page 80 of 82

Testing Unitario con Microsoft Fakes - Captulo 6: Hands-on Lab


Assert.IsFalse(codeUnderTest.ShouldMove, "The Car.ShouldMove property should return false where Car.Location.Road is null."); } }

Paso 8 Intentando hacer un shim de la clase DiscoveredRoutes


El mtodo ShouldMove hace una llamada a la clase DiscoveredRoutes como vemos aqu:
if (this.Location.Road.IsFree(this.Location.Position + 1)) { var routePart = DiscoveredRoutes.ToRoutePart(this.Location.Road); if (routePart == null) { return false; } var probability = routePart.Probability; return this.RandomGenerator.NextDouble() < probability; }

Para tener esta parte del mtodo testeado, deberamos hacer un Shim de la clase DiscoveredRoutes. Sin embargo, viendo esta clase, veremos que es internal. Para este ejercicio, hemos decidido que el cdigo bajo test es inmutable; es decir, que no podemos aadir un atributo InternalsVisibleTo para el assembly Traffic.Core. Esto significa que los intentos de tener un cdigo testeado no han valido para nada? No necesariamente. Hemos ampliado la cobertura de nuestro cdigo desde cero hasta algo ms, esto aade valor a nuestro sistema. En este punto podemos optar por tener un cierto nivel de test de integracin para mitigar aquellas reas en las que el sistema no puede ser cubierto por tests unitarios.
REVISIN
En este ejercicio, hemos visto cmo podemos empezar a tener un sistema complejo testeado incrementando la cobertura de cdigo

Page 81 of 82

Testing Unitario con Microsoft Fakes - Conclusin

Conclusin
Aqu concluye nuestra aventura de testing unitario con Microsoft Fakes. Hemos tocado la teora, introducido los Shims y Stubs, y esperamos haber enseado cundo usarlos. Tambin hemos visto varios ejercicios en los Hands-on Lab, siguiendo varios escenarios, y hemos visto varias tcnicas avanzadas. Estamos al principio del ciclo de vida de Microsoft Fakes con ms actualizaciones que vendrn en el futuro en prximas versiones de Visual Studio. Esperamos que encuentres til esta tecnologa y esta gua. Atentamente,

Page 82 of 82

Potrebbero piacerti anche