testing unitario con microsoft fakes

82
Testing Unitario con Microsoft Fakes - Prólogo Page 1 of 82

Upload: kharonte24

Post on 29-Dec-2015

118 views

Category:

Documents


5 download

TRANSCRIPT

Page 1: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Prólogo

Page 1 of 82

Page 2: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Prólogo

Page 2 of 82

La información contenida en este documento representa la visión Microsoft Corporation sobre los asuntos

analizados a la fecha de publicación. 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 información presentada después de la fecha de publicación.

Este documento es sólo para fines informativos. MICROSOFT NO OFRECE NINGUNA GARANTÍA, EXPRESA,

IMPLÍCITA O LEGAL, EN CUANTO A LA INFORMACIÓN CONTENIDA EN ESTE DOCUMENTO.

Microsoft publica este documento bajo los términos de la licencia Creative Commons Attribution 3.0 License.

Todos los demás derechos están reservados.

© 2013 Microsoft Corporation.

Microsoft, Active Directory, Excel, Internet Explorer, SQL Server, Visual Studio, and Windows son marcas

comerciales del grupo de compañías de Microsoft.

Todas las demás marcas son propiedad de sus respectivos dueños

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 3: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Prólogo

Page 3 of 82

Índice Prólogo ........................................................................................................................................................................................................................................6

Introducción ..............................................................................................................................................................................................................................7

Audiencia ............................................................................................................................................................................................. 7

Visual Studio ALM Rangers .......................................................................................................................................................... 7

Autores ................................................................................................................................................................................................. 8

Uso del código fuente, erratas y soporte ............................................................................................................................... 8

Capítulo 1: Breve teoría sobre Testing Unitario .........................................................................................................................................................9

Testing de software .............................................................................................................................................................................. 9

Estrategias de testing ..................................................................................................................................................................... 9

Tipos de tests ..................................................................................................................................................................................... 9

La delgada línea 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

Capítulo 2: Introducción a Microsoft Fakes .............................................................................................................................................................. 14

Stubs ......................................................................................................................................................................................................... 14

¿Qué testeamos? ............................................................................................................................................................................ 14

¿Por qué usar Stubs? ..................................................................................................................................................................... 15

Shims ........................................................................................................................................................................................................ 16

Elegir entre un stub o un shim ....................................................................................................................................................... 18

Capítulo 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

Introducción ..................................................................................................................................................................................... 20

Diferencias entre estos productos y Microsoft Fakes ...................................................................................................... 21

Migrando desde Moq ................................................................................................................................................................... 22

Migrando desde RhinoMocks ................................................................................................................................................... 32

Conclusión ......................................................................................................................................................................................... 35

Capítulo 4: FAQ ..................................................................................................................................................................................................................... 36

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

Page 4: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Prólogo

Page 4 of 82

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 generación de Fakes .......................................................................................................................................... 38

Mirando bajo las sábanas ................................................................................................................................................................ 39

Refactorizando código 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

Capítulo 5: Técnicas avanzadas ...................................................................................................................................................................................... 47

Tratando con servicios Windows Communication Foundation (WCF) ........................................................................... 47

Rompiendo la dependencia ....................................................................................................................................................... 47

Mejorando la situación ................................................................................................................................................................ 48

Tratando con cálculos no deterministas .................................................................................................................................... 49

Operaciones basadas en Timer ................................................................................................................................................. 49

Datos no repetibles ....................................................................................................................................................................... 49

Recopilación de casos de uso y más información analítica. ............................................................................................... 50

Validar detalles de implementación ....................................................................................................................................... 50

Analizando el estado interno .......................................................................................................................................................... 51

Evitando la duplicación de estructuras de testing ................................................................................................................. 52

Capítulo 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

Patrón de implementación ......................................................................................................................................................... 53

Pasos a realizar ................................................................................................................................................................................ 54

Paso 1 – Revisar la solución de inicio ..................................................................................................................................... 54

Paso 2 – Prepara el proyecto de test para Microsoft Fakes Stubs .............................................................................. 55

Paso 3 – Añade 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 método de test unitario .......................................................................................................................... 57

Page 5: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Prólogo

Page 5 of 82

Paso 7 – Ordena el método y crea un Stub para la interfaz Repository .................................................................. 58

Paso 8 – Llama al método de acción del controlador y comprueba los resultados ............................................ 60

Paso 9 – Completar la implementación de la acción 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 – Añadir shims como fake del sistema de archivos ........................................................................................... 65

Paso 5 – Añadir un Shim para aislarnos de la fecha del sistema ................................................................................ 68

Paso 6 – (Opcional) Ejectua el test con el debugger para entender el flujo de ejecución. .............................. 68

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

Escenario ............................................................................................................................................................................................ 69

Preparación ....................................................................................................................................................................................... 69

Paso 1 – Crear una característica de ejemplo de SharePoint ....................................................................................... 69

Paso 2 – Crear un test ................................................................................................................................................................... 70

Ejercicio 4: Haciendo testable código 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 – Añadir las referencias de Fakes al assembly Traffic.Core ............................................................................. 75

Paso 4 – Modificar el test unitario para la propiedad Run ............................................................................................ 75

Paso 5 – Añadir una referencia Fake de la clase System.Timer .................................................................................... 77

Paso 6 – Crear un test unitario para el constructor de Car ............................................................................................ 78

Paso 7 – Añade un test para la propiedad Car.ShouldMove ........................................................................................ 79

Paso 8 – Intentando hacer un shim de la clase DiscoveredRoutes ............................................................................ 81

Conclusión .............................................................................................................................................................................................................................. 82

Page 6: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Prólogo

Page 6 of 82

Prólogo En equipos de desarrollo modernos, el valor de un testeo unitario efectivo y eficiente es algo en lo que están todos

de acuerdo. Tests rápidos, seguros y automáticos que permiten a los desarrolladores comprobar que su código

hace lo que ellos piensan que debe hacer, incrementan significativamente la calidad general del código. Sin

embargo, crear test unitarios buenos y efectivos es más difícil de lo que parece. Un buen test unitario es como un

buen experimento científico: aísla tantas variables como sea posible (llamadas variables de control) y valida o

rechaza una hipótesis sobre lo que ocurre cuando una variable (la independiente) cambia.

Para poder crear código con este tipo de aislamiento hay que prestar especial atención al diseño y a los patrones

que usan los desarrolladores. En algunos casos, el código está diseñado de manera que aislar un componente de

otro es fácil. Sin embargo, en la mayoría de los casos, conseguir este nivel de aislamiento es muy difícil. De hecho,

es tan difícil que para algunos desarrolladores es algo imposible.

Incluido por primera vez en Visual Studio 2012, Microsoft Fakes nos ayuda a mitigar esta dificultad. Hace más fácil

y rápido crear test unitarios bien aislados cuando ya tenemos sistemas que son “testables”, permite centrarnos en

escribir los test adecuados sin escribir test acoplados. También nos permite aislar y testear código que

tradicionalmente no es fácil de testear, usando la tecnología 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 rápidos y de calidad.

Shims ofrece una forma de evitar muchas de las dificultades que nos encontramos cuando queremos hacer tests

unitarios de nuestro código. Como con todas las herramientas de este tipo, hay algunos patrones, técnicas y

conceptos que pueden llevar tiempo aprender. Este documento no es más que un empujón para adquirir ese

conocimiento compartiendo numerosos ejemplos y técnicas que permitan usar de manera adecuada Microsoft

Fakes en vuestros proyectos.

Nos alegra escribir el prólogo de esta guía 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 código.

Peter Provost- Program Manager Lead, Visual Studio ALM Tools

Joshua Weber –Program Manager, Visual Studio ALM Tools

Page 7: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Introducción

Page 7 of 82

Introducción Bienvenidos a Mejor Unit Testing con Microsoft Fakes 1 en el que, los ALM Rangers, os acompañaremos en un viaje

fascinante para descubrir una nueva, excitante y poderosa característica introducida en Visual Studio 2012.

NO

TE Esta guía se basa en Visual Studio 2012 Update 1

Audiencia

Developers! Developers! Developers! Esperamos que nuestra audiencia sean mayoritariamente desarrolladores.

Veremos algunos conceptos básicos sobre tests unitarios pero esperamos que la mayoría de nuestros lectores ya

tengan alguna experiencia en escribir tests unitarios. Sin duda, cierta experiencia previa con algún otro framework

de mocking sería muy positiva. Sin embargo, si estás valorando adoptar Microsoft Fakes como tu primera solución

de mocking, creemos que esta guía te ayudará a implementar una solución de mocking con Microsoft Fakes de

manera adecuada. Si es la primera vez que oyes términos como test unitarios y mocking, esta guía también 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 guía:

Visual Studio Ultimate 2012

Visual Studio Premium 2012 (Es necesario Visual Studio 2012 Update 2)

Para escribir test unitarios con Microsoft Fakes necesitarás una edición soportada de Visual Studio. La ejecución

de estos test en un servidor de builds también requiere una edición soportada. Es posible ejecutar los test con

Team Foundation Server 2010 y quizás con versiones anteriores. Sin embargo, para una buena experiencia,

recomendamos usar Team Foundation Server 2012. Los test de Microsoft Fakes se pueden compartir y enseñar a

otros miembros del equipo que no estén usando una edición soportada de Visual Studio, pero no podrán

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 misión es ayudar a la comunidad. La lista de Rangers está creciendo y la podéis ver online 2.

1 http://msdn.microsoft.com/en-us/library/hh549175.aspx

2 http://blogs.msdn.com/b/willy-peter_schaub/archive/2012/06/22/introducing-the-visual-studio-alm-rangers.aspx

Page 8: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Introducción

Page 8 of 82

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 código fuente, erratas y soporte

Todo el código fuente de la guía está disponible en la página de Visual Studio Test Tooling Guidance 3 con las

últimas correcciones y actualizaciones.

Los Hands-on Labs usan la propiedad Nuget Package Restore 4. Tendréis que habilitarlo en las opciones de Visual

Studio si aún no lo habéis hecho:

Expresiones Lambda

Una expresión lambda es una forma concisa de escribir funciones anónimas que podemos usar para crear

delegados o expresiones arbóreas. Con las expresiones lambda, podemos escribir funciones locales que se pueden

pasar como parámetros o que se pueden devolver como valor de retorno de una función. Haremos un uso extenso

de estas expresiones en el código de ejemplo. Si eres nuevo en el tema de las expresiones lambda, te

recomendamos que leas la sección de MSDN dedicada a ello Lambda Expresions (C# Programming Guide) 5

3 http://vsartesttoolingguide.codeplex.com

4 http://blog.nuget.org/20120518/package-restore-and-consent.html

5 http://msdn.microsoft.com/en-us/library/bb397687.aspx

Page 9: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Capítulo 1: Breve teoría sobre Testing Unitario

Page 9 of 82

Capítulo 1: Breve teoría sobre Testing

Unitario Antes de entrar en los detalles técnicos de Microsoft Fakes, veremos un poco de teoría para recordar cosas a los

lectores experimentados y para hacer que los nuevos aprendan las nociones básicas y necesarias sobre Testing

Unitario. Hay dos grandes corrientes de pensamiento entre los que se dedican al Testing Unitario:

¿Debería o no debería cambiar el diseño del código existente para que sea más testable?

La respuesta a esta pregunta tiene un impacto directo sobre cómo un framework de aislamiento como Microsoft

Fakes es usado por el equipo, todo gira en cómo se aísla el código que se va a testear de sus dependencias. Unos

dicen que cambiar el diseño para que el código sea más testable es bueno ya que ayuda a conseguir un diseño

más desacoplado y cohesivo. En este caso, se usan técnicas para sustituir clases concretas para testear código

heredado (legacy code) que no fue diseñado para que fuese testable (en Microsoft Fakes esto se hace a través de

Shims). Otros dicen que los tests no deben comprometer el diseño. En este caso, el código nuevo tiende a hacer

uso de clases más concretas; para testarlo se requieren técnicas especiales para sustituir las clases concretas en los

test unitarios.

La vertiente que elijas es cosa tuya y está fuera del alcance de esta guía. Por lo tanto, no vamos a valorar ni

recomendar ninguna de las dos. Independientemente de tu elección, está generalmente aceptado que los tests

unitarios deben ser pequeños y rápidos.

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 políticas operacionales se cumplan. El testing es un esfuerzo del equipo para conseguir un

mínimo de calidad y tener una “definición de hecho” entendida y aceptada por todos.

NO

TA

Definición de hecho – es una definición del equipo y un compromiso de calidad que se aplicará a la solución en cada

iteración (también se puede definir en el nivel de Tareas o de Historias de Usuario). Ten en cuenta el diseño, las

revisiones de código, refactorización y testing cuando discutáis y defináis vuestra “definición de hecho”.

Estrategias de testing

Las estrategias de testing se dividen tradicionalmente en testing de caja negra, blanca y gris.

Estrategia Descripción

Caja Negra El contenido de la caja (la implementación de la solución) es oscura. Los testers sólo se centran en la

entrada y en la salida, normalmente cuando se realizan test de sistema o de aceptación de usuario.

Caja Blanca El contenido de la caja es visible y se puede analizar como parte del testing.

Caja Gris Es una combinación de caja blanca y negra que se usa normalmente en casos especiales, en los que se

requiere una comprensión de cómo funciona por dentro y cómo debe comportarse.

Tipos de tests

No hay una bala de plata cuando hablamos de testing (ver Figura 2). Hay veces que se necesita de interacción

humana y feedback, otras es necesario automatizar la ejecución de estos test y otras hay que ejecutarlos

manualmente.

Page 10: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Capítulo 1: Breve teoría sobre Testing Unitario

Page 10 of 82

Estrategia Descripción Herramienta de Visual Studio

Test exploratorio 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

Testing exploratorio con

Microsoft Test Manager (MTM)

Test de integración Testear diferentes componentes de la solución trabajando

como si fueran uno

Visual Studio Unit Test

Test de carga Testear cómo se comporta el sistema con cargas de trabajo

en un entorno controlado

Visual Studio Load Test Agent

Test de Regresión Asegura que el sistema mantiene los mínimos de calidad

después de hacer cambios como corregir bugs. Usa una

mezcla de tests unitarios y de sistema

Testing automático con MTM

Smoke Test Se usa para testear una nueva característica o idea antes de

subir al repositorio de código los cambios

Test de sistema Testea el sistema completo con características fijas y

comprueba los requerimientos no funcionales

Visual Studio Lab Management

Test unitario Un test de la unidad más pequeña de código (método, clase,

etc.) que puede ser testeada de manera aislada del sistema.

Ver Verifying Code By Using Unit Test y La delgada línea entre

buen y mal test unitario en esta guía para más información

Visual Studio Test Explorer.

Frameworks de test unitarios

Test de aceptación de

usuario

Cuando se acerca el final de los ciclos del producto, se invita

a los usuarios a que realicen test de aceptación en escenarios

reales, normalmente basados en casos de tests

Test automático con MTM

Page 11: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Capítulo 1: Breve teoría sobre Testing Unitario

Page 11 of 82

La delgada línea 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 ayudarán a diseñar, desarrollar e implementar buenos test unitarios.

AV

ISO

Advertencia: El testeo unitario se basa en pequeñas porciones de código, de manera que sólo usando test unitarios

es probable que no se incremente la calidad del producto. Recomendamos su uso junto a otras técnicas de tests

descritas en esta guía. 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 avión comercial que usan habitualmente para cruzar océanos sea testeado meticulosamente

antes de cada vuelo. ¿Es importante que el avión sea testeado? Si, ok, ahora, ¿es importante que el altímetro sea

testeado aparte? Por supuesto. Eso es el test unitario… testear el altímetro. No garantiza que el avión vaya a volar,

pero no puede volar de forma segura sin él.

La importancia de los test unitarios, empieza con el proyecto y continúa durante todo el ciclo de vida de la

aplicación, también depende de si estamos buscando:

- Código de calidad desde el principio

- Menos bugs

- Código auto explicativo

- Reducir el coste de corregir errores encontrándolos lo antes posible

- Sentido de responsabilidad del código, estar orgullosos de nuestro código

NO

TA

El valor principal del desarrollo guiado por test y de los test unitarios es asegurarse de que el equipo piensa e incluso

sueña con el código, estudia los requerimientos, y evitan posibles problemas proactivamente.

Automatiza tus tests:

Plantéate automatizar tus test. Esto te permitirá testear rápida y automáticamente cambios de código incluso a

medida que se escribe. Sin embargo, la automatización de los tests tiene sus desafíos y te recomendamos que

investigues sobre el tema (http://blogs.msdn.com/b/steverowe/archive/2008/02/26/when-to-test-manually-and-

when-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 lógica y la intención de un test unitario, hay que seguir estos pasos:

Page 12: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Capítulo 1: Breve teoría sobre Testing Unitario

Page 12 of 82

1. Escribe el código del test (Stub) para que compile (pase de RED a GEEN)

a. Inicialmente la compilación fallará RED debido a que falta código

b. Implementa sólo el código necesario para que compile GREEN (aún no hay implementación real).

2. Escribe el código 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.

3. Refactoriza el test y el código una vez que este todo GREEN y la solución vaya evolucionando.

NO

TA

Echad un vistazo a Guidelines for Test-Driven Development(http://msdn.microsoft.com/en-

us/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 más buenas prácticas a la hora de escribir

tests unitarios

Page 13: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Capítulo 1: Breve teoría sobre Testing Unitario

Page 13 of 82

Checklist de un test unitario

Check Descripción Check

Convención de nombres

descriptivos

Establece una convención de nombres descriptivos como

ClassName_Purpose_ExpectedResult, consiguiendo un estilo de tests

consistente y con una intención clara.

Ejemplos:

- Credentials_UserNameLength_Succeed()

- Credentials_UserNameLength_Fail()

Documenta los tests Los test unitarios prueban y validan características de negocio. El propio

código es una documentación viva del sistema. Recomendamos el uso

combinado de código conciso y documentación mínima, para conseguir un

test unitario entendible, mantenible y autodescriptivo.

Errores y aserciones

descriptivas

Usa mensajes descriptivos para mejorar la lectura del código y el log de

compilación.

Ejemplos: Content submission failed due to too many spelling errors.

Adopta el desarrollo contra

interfaces

La interfaz define el contrato, permitiendo el uso de stubs y tests de caja

negra.

Mantenlo simple El test unitario debe ser tan simple, limpio y conciso como sea posible. Los

tests simples son un valor añadido, si no, se ignorarán y no se mantendrán

Mantenlo centrado Un test unitario está centrado en la mínima unidad de código (método,

clase, etc.) que puede ser probada de manera aislada, no a nivel de sistema

o de integración. Para mantener un test y su infraestructura asociada centrada,

evita probar cosas entre los diferentes niveles de aplicación o de sistema.

Tiene una sola aserción

lógica

Cada test debe tener sólo una aserción lógica. Debe validar el test, como se

indica en su nombre. Una aserción lógica puede contener una o varias

sentencias de assert.

Organiza y mantén los tests El código debe ser organizado y mantenido como si fuese una clase de primer

nivel, al igual que el resto de la solución. Su código debe basarse en los

mismos requisitos de buenas prácticas, calidad y estilo de código de la

compañía.

Testea los casos buenos,

malos y los límite

El test unitario debe cubrir todos los posibles escenarios y busca siempre una

amplia cobertura de código. Testear los casos límites y las excepciones es

responsabilidad del testador, ¡no del usuario final!

Page 14: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Capítulo 2: Introducción a Microsoft Fakes

Page 14 of 82

Capítulo 2: Introducción a Microsoft Fakes Microsoft Fakes es un nuevo framework de aislamiento que nos permite aislar el código a testear reemplazando

otras partes de la aplicación con stubs o shims. Nos permite testear partes de nuestra solución incluso si otras

partes de nuestra aplicación no han sido implementadas o aún no funcionan.

Microsoft Fakes viene con dos sabores:

- Stubs… reemplaza una clase con un sustituto (“stub”) que implementa la misma interfaz.

- Shims… modifica el código compilado en tiempo de ejecución, 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 están bajo nuestro control:

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

¿Qué testeamos?

En la siguiente figura, tenemos un ejemplo típico de una aplicación en N-capas. La complejidad de cualquier

solución depende del nivel de características que dicha solución 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 15: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Capítulo 2: Introducción a Microsoft Fakes

Page 15 of 82

Para el aislamiento, vamos a los componentes individuales que se ejecutan en la capa de lógica de negocio (puede

que tu terminología varíe). Nuestra estrategia de testeo no es testear la lógica en la base de datos, ni la lógica de

los servicios WCF/Rest, ni el navegador, etc.

Test dobles

Los tests dobles nos permiten aislar el código bajo test de sus dependencias. Juegan un papel muy importante en

la evolución de la calidad de nuestro código y aumenta su testabilidad. Los stubs, un tipo de test doble, requieren

que el código que se va a probar esté diseñado de manera que permita separar y desacoplar dependencias.

Es importante diferenciar qué es un Stub de qué no lo es. El artículo de Martin Fowler “Moks aren’t 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 artículo, un stub conserva un estado estático que permite la verificación

de estados (http://xunitpatterns.com/State%20Verification.html ) del sistema en prueba, mientras que un mock

ofrece una verificación de comportamiento (http://xunitpatterns.com/Behavior%20Verification.html) del sistema

en prueba.

En esta sección, nos centraremos en los stubs y veremos el valor que aportan a nuestro proceso de desarrollo.

Podéis leer el artículo de Martin Fowler para conocer más detalles de los principios y contrastar las dos

aproximaciones.

Con stubs, podemos aislar el código que se quiere probar junto a unos casos de pruebas que validarán nuestro

código de negocio – esto es, objetos con un estado específico que podemos reproducir bajo unas condiciones

que podemos controlar y ejecutar. Es importante saber que Microsoft Fakes no ofrece herramientas de verificación

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 análisis de cobertura de código nos

permite mitigar problemas derivados de la falta de cobertura en otros escenarios de testing más “integrados”. Una

integración completa y tests de caja blanca pueden dejar partes significantes del código sin probar, incrementando

así las posibilidades de introducir defectos funcionales.

Page 16: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Capítulo 2: Introducción a Microsoft Fakes

Page 16 of 82

Aumentando la cobertura de código

Con stubs identificamos condiciones específicas a nivel de unidad e incorporamos este “estado” en un test o

conjunto de test simples. Incrementando así la cobertura de nuestro código. Los problemas y los errores son

detectados con los test de integración, errores de producción, etc. Estas áreas son partes del código que no están

cubiertas por los test unitarios necesarios. En este punto, añadir stubs que simulen estas condiciones o áreas de

código, ayuda tanto a la cobertura como a la calidad de la cobertura del sistema en test. Además, 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 aceptación, o incluso de producción). Los stubs hacen más fácil crear test

unitarios que aseguren la cobertura de ciertas condiciones aislándolas de sus dependencias. Además, una buena

aproximación TDD es no escribir el cambio en el código 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 código funcional que hace que el test pase.

Aislamiento y granularidad

Como vimos en la sección ¿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 Lógica de Negocio (BLL). El aislamiento reduce la cantidad de código en el que el test se tiene que centrar.

Esto reduce, en muchos casos, el código necesario para configurar el test también. Son necesarios también

componentes o entornos aislados si queremos desacoplarnos de estados físicos 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 detrás del aislamiento y los stubs en el testing unitario está directamente relacionado

con el desarrollo de componentes. En muchas ocasiones, creamos aplicaciones y soluciones dividiéndolas en

componentes. A medida que descomponemos la solución en partes más 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 solución de calidad; y el corolario de que si creamos algo con componentes de

calidad aumentamos las posibilidades de conseguir una solución de calidad. Fijaos que hemos dicho

“posibilidades”. Una solución 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 técnicas.

Shims Los Shims son una característica de Microsoft Fakes que permiten crear tests unitarios para código que de otra

manera no puede ser probado de manera aislada. Al contrario que los Stubs, los Shims no requieren que el código

a testear sea diseñado de ninguna manera. Para poder usar Stubs, tienen que ser inyectados* en el código que se

prueba de alguna manera, así las llamadas a las dependencias no las manejaran componentes reales (código de

producción), sino que será el Stub. De esta manera, los valores y objetos de test se les pueden pasar al código que

se está probando:

Page 17: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Capítulo 2: Introducción a Microsoft Fakes

Page 17 of 82

Pero hay ocasiones en las que el código que se quiere probar no está diseñado de manera que permita cambiar

sus dependencias, por ejemplo, cuando se llaman a métodos estáticos 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 ejecución desviándolas a un código especial con los valores de prueba

deseados para el test.

NO

TA

*La técnica de Inyección de Dependencias (DI) se usa en la programación orientada a objetos para desacoplar clases

de sus dependencias o al menos de la implementación concreta a través de interfaces. Esta técnica se puede usar

para inyectar stubs para motivos de testing. Esta inyección se puede realizar a través 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 parámetro en el constructor de la clase que queremos testar

(Inyección por Constructor). Para que la inyección 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

podrán inyectar stubs tampoco

Page 18: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Capítulo 2: Introducción a Microsoft Fakes

Page 18 of 82

Elegir entre un stub o un shim Como ya hemos visto, los Stubs ofrecen implementaciones de interfaces y clases, y son más efectivos cuando el

código a probar está diseñado pensando en tests. Los Shims también soportan aquellas situaciones en las que el

código dependiente, por ejemplo código heredado o externo, no puede cambiarse ofreciendo una forma de

interceptar y desviar las llamadas consiguiendo que se ejecute el código que queramos.

NO

TA

Cuando sea posible, usad Stubs. Vuestros tests se ejecutarán más rápido.

Objetivo | Consideración Stub Shim

¿Buscas el mejor rendimiento? X X(más lento)

Métodos abstractos y virtuales X

Interfaces X

Tipos internos X X

Métodos estáticos X

Tipos sellados X

Métodos privados x

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

MSDN para más información.

Page 19: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Capítulo 3: Migrando a Microsoft Fakes

Page 19 of 82

Capítulo 3: Migrando a Microsoft Fakes Esperamos que algunos de los que estáis leyendo esta guía tengáis 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 capítulo, veremos algunos de los pasos y problemas que nos podemos encontrar cuando

migremos a Microsoft Fakes. Recordad que podéis usar Microsoft Fakes con otros frameworks y podéis 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 automático para convertir un proyecto que use Moles

a otro que use Microsoft Fakes. Con la publicación 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 razón principal de esta recomendación, 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

sólo existen en .NET 4.5. El propio Moles está escrito en .NET 4 y esto genera 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/en-

us/projects/moles/molesdev11.aspx). Como es un proceso potencialmente complejo y tendiente a errores, os

recomendamos que no intentéis ejecutar Moles y Fakes a la vez.

Cualquier migración de Moles a Fakes requiere cambios en el código; sin embargo, dado que las bases de Microsoft

Fakes es Moles, dichos cambios no son muchos.

NO

TA

Microsoft Fakes no reemplaza a PEX(http://research.microsoft.com/en-us/projects/pex/) y no ofrece la generación

automática de test unitarios que si ofrece PEX

Cambiando referencias

El primer paso en cualquier migración 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 añadirá 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 operación de fake. En Moles, se coloca

un atributo HostType en el método 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 20: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Capítulo 3: Migrando a Microsoft Fakes

Page 20 of 82

{

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 básica 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 declararía así:

MDateTime.NowGet = () =>

{

return new DateTime(1, 1, 1);

};

Con Microsoft Fakes sería así:

System.Fakes.ShimDateTime.NowGet = () =>

{

return new DateTime(1, 1, 1);

};

Como sólo es un cambio de namespaces, bastaría con una operación de reemplazar con cualquier editor de texto

Moles y Microsoft SharePoint

Microsoft Research publicó unas librerías de behaviors para ayudar al testing de SharePoint

(http://research.microsoft.com/en-us/projects/pex/pexsharepointbehaviors.pdf). Esta librería es un mock de la API

de SharePoint. Cuando migremos a Microsoft Fakes, podemos usar los emuladores de SharePoint, que son una

versión de los comportamientos de Moles SharePoint. Podéis leer más sobre estos emuladores en el blog

Introducing SharePoint Emulators (http://blogs.msdn.com/b/visualstudioalm/archive/2012/11/26/introducing-

sharepoint-emulators.aspx) y están disponibles en Nuget.

Migrando de frameworks comerciales y open source

Introducción

Hay un gran número de proyectos open source, como RhinoMocks, Moq, etc, que ofrecen tecnologías equivalentes

para hacer stubs como en Microsoft Fakes. Sin embargo, no ofrecen una tecnología similar a los shims de Microsoft

Fakes. Sólo los productos comerciales ofrecen la posibilidad de mockear objetos de clases privadas y selladas.

Además de Microsoft Fakes, este tipo de mocking también 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 versión gratuita y “recortada”. También tienen

productos Premium que ofrecen herramientas como los shims para mockear objetos que no son “mockeables”.

Page 21: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Capítulo 3: Migrando a Microsoft Fakes

Page 21 of 82

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-generación, se encarga el propio framework en

tiempo de ejecución.

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 intención que se expresa es siempre

la misma.

Telerik JustMock

En este ejemplo vemos cómo 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 cómo 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 22: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Capítulo 3: Migrando a Microsoft Fakes

Page 22 of 82

// Assert

Assert.AreEqual(100, result);

}

Microsoft Fakes

Ahora vamos a ver cómo 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 sección veremos cómo migrar algunas de las características más usadas de Moq, Stubs a Microsoft Fakes.

La principal diferencia entre Moq y Stubs es que Moq usa interfaces genéricas y clases abstractas que tienen que

ser “stubbeadas” y Stubs usa la generación de código 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

métodos que están siendo stubbeados para ofrecer valores de retorno o para ejecutar cualquier código que sea

necesario.

Una de las pequeñas diferencias que veremos en el código 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 sección veremos algunos ejemplos de código, veremos los escenarios que ofrece Moq y luego

veremos cómo cambiarlo para que funcione con Stubs.

Código de ejemplo

En el resto de la sección, haremos referencia a un código de ejemplo. Usaremos una clase muy simple de cuenta

de banco como la que vemos en el siguiente trozo de código

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 23: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Capítulo 3: Migrando a Microsoft Fakes

Page 23 of 82

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 método Setup se usa para crear un stub que responderá al conjunto de parámetros de entrada con una

salida que le indiquemos. Por ejemplo, si queremos que el objeto ITransactionManager que se pasa al código a

testear devuelva un valor concreto cuando se le pase una fecha al método GetAccountTransactionCount,

deberemos usar el siguiente código:

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 expresión Lambda que nos devolverá el valor que queremos. Fijaos que esta expresión lambda

comprueba el parámetro de entrada de la misma manera que haría Moq. Si se le pasa un valor diferente al indicado,

devolverá el valor por defecto del tipo.

Moq también nos permite llamar al método Setup varias veces para devolver valores diferentes para diferentes

entradas. Aquí tenéis 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 expresión lambda algo más 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 expresión lambda las

busca. En este caso estamos ignorando el parámetro de fecha.

Algunas veces, los valores que queremos no se conocen con antelación y usar una colección nos permite inicializar

el diccionario dinámicamente, incluso después de que el stub se halla pasado al código que queremos testear. Un

escenario en el que necesitamos hacer esto es cuando queremos centralizar la creación del código bajo test en un

código de inicialización, pero cada test se ejecuta con diferentes conjuntos de valores. Podríamos 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 24: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Capítulo 3: Migrando a Microsoft Fakes

Page 24 of 82

(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 también es capaz de tratar la configuración de métodos con varios parámetros. Pero en Stub

es un poco más complejo crear expresiones lambda que comprueban cada uno de los parámetros.

Cuando tenemos varios parámetros de entrada en varios Setups, lo más simple suele ser usar un diccionario que

usa como key un Tuple<T,R>. Aquí tenéis 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 25: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Capítulo 3: Migrando a Microsoft Fakes

Page 25 of 82

[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 26: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Capítulo 3: Migrando a Microsoft Fakes

Page 26 of 82

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

}

Moq también 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 podría simplemente ignorar el parámetro apropiado en la lambda. En el caso anterior podría

ser algo así:

stubTM.GetTransactionDateTimeInt32 = (date, index) => txn1;

Migrando los Callbacks

Los Callbacks nos permiten registrar un método que se ejecutará cuando ocurra otra acción. Tanto Moq como

Stubs nos permiten especificar métodos de callback en el propio test unitario.

Si por ejemplo, queremos llamar a un método como este en nuestra clase de test:

bool callBackCalled = false;

public void CallBackMethod(decimal param)

{

callBackCalled = true;

}

En Moq, usaremos el método .Setup como en el ejemplo anterior. Sin embargo, en lugar de la llamada al .Returns

llamaremos al .Callback para indicar cuál es el método que se tiene que ejecutar, pasando los parámetros que

hagan falta de manera similar a como lo haríamos en el método 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 código que se está testeando

ha hecho ciertas llamadas con ciertos parámetros o que se han hecho ciertas llamadas un cierto número de veces.

Page 27: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Capítulo 3: Migrando a Microsoft Fakes

Page 27 of 82

Sin embargo estas capacidades de comprobación de comportamiento son limitadas. Por ejemplo, no se puede

comprobar que los métodos se ejecutan en cierto orden. Los Stubs en Microsoft Fakes no están 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 transacción al balance de apertura cuando se abre una

cuenta. Esto puede testearse de otras maneras, pero este ejemplo muestra cómo testear un comportamiento. En

Moq, el test podría 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 más de código en la expresión 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 método de verificación adecuado, dependiendo del elemento que

quiera verificar:

- .Verify – para los métodos

- .VerifyGet – para los Get de las propiedades.

- .VerifySet – para los Set de las propiedades.

Como Stubs no ofrece métodos de verificación, tendremos que crearnos los nuestros, de manera que no hay

diferencia entre verificaciones de métodos o propiedades; es todo código personalizado.

Obviamente, algunas veces hacen falta verificaciones más complejas. Por ejemplo, pueden ser necesarias diferentes

combinaciones de parámetros. Esto se puede realizar con la técnica del Diccionario de Tuplas, similar al que vimos

en la sección de Setup, para contar el número de llamadas por cada combinación de parámetros.

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 28: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Capítulo 3: Migrando a Microsoft Fakes

Page 28 of 82

En ambos casos, tendremos que registrar un delegado que será llamado cuando se lance el evento. En este

ejemplo, sólo setearemos un flag booleano para indicar que el evento se ha lanzado. Sin embargo, las técnicas de

verificación, como vimos antes, pueden usarse para comprobar cualquier parámetro que se halla pasado.

En la sección de “act”, lanzamos el evento que queremos testear pasando los parámetros adecuados. Aquí es

donde la sintaxis se diferencia. Con Stubs el evento se lanza con una llamada a un método:

[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 expresión lambda para pasar los parámetros:

[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 método

SetupAllProperties para las propiedades. De esta manera, el test no lanzará ningún NullReferenceException. Se

devolverá el valor por defecto de cualquier objeto que haya que devolver. Por ejemplo, los enteros devolverán un

0, y los strings devolverán un String.Empty. Si hace falta algún otro valor, tendremos que indicarlo de manera

explícita:

Mock<ITransaction> mockTr = new Mock<ITransaction>() { DefaultValue = DefaultValue.Mock };

Page 29: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Capítulo 3: Migrando a Microsoft Fakes

Page 29 of 82

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 método:

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 gustaría 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 30: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Capítulo 3: Migrando a Microsoft Fakes

Page 30 of 82

/// </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 31: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Capítulo 3: Migrando a Microsoft Fakes

Page 31 of 82

public WebRequest Create(Uri uri)

{

return nextRequest;

}

}

Nuestro test en Moq sería 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 sería 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 más ejemplos de Moq visitad: http://code.google.com/p/moq/wiki/QuickStart

Page 32: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Capítulo 3: Migrando a Microsoft Fakes

Page 32 of 82

Migrando desde RhinoMocks

En esta sección veremos cómo migrar alguna de las características más usadas en la API de RhinoMocks a Microsoft

Fakes. RhinoMocks es uno de muchos proyectos open source que ofrecen formas de stubbear el código bajo test.

Rhinomocks usa una API para crear stubs de interfaces y clases abstractas a través de reflexión. Microsoft Fakes lo

que hace es generar código para interfaces y clases abstractas. La sintaxis usada para RhinoMocks en este

documento corresponde a la versión 3.5 o superior. Para los desarrolladores que quieran usar Microsoft Fakes, tan

sólo tienen que hacer click derecho en el assembly para el que se quieren crear stubs y seleccionar la opción “Add

Fake Assembly” del menú.

Sólo veremos ejemplos de migración para las APIs que más 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 migración.

Código de ejemplo

La mayoría 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 dinámico. El mockeo estricto (Code 31) consiste en que el

desarrollador tiene que definir las salidas de todos los métodos que se van a llamar en el test. Si no se ha definido

una salida para algún método, se lanzará una excepción para ese método cuando sea llamado. Por defecto,

Microsoft Fakes usa el valor por defecto para el mocking. El desarrollador puede sobrescribir este comportamiento

Page 33: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Capítulo 3: Migrando a Microsoft Fakes

Page 33 of 82

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 excepción 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 método WhatIsCurrentTemp se lanzará una excepción cuando

se ejecute el test. Para la mayoría de ejemplos, usaremos el mocking dinámico.

Los stubs de RhinoMocks (setup y returns) son llamadas simples a la API, como vemos en el siguiente código:

[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 más 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 34: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Capítulo 3: Migrando a Microsoft Fakes

Page 34 of 82

Migrando las sentencias Expect y AssertWasCalled

RhinoMocks usa las sentencias Expect, AssertWasCalled o AssertWasNotCalled para verificar algoritmos o llamadas

a métodos. La API de RhinoMocks ofrece varias opciones para administrar el nivel de detalle de cuantas veces se

llamó a un método. Durante el testing, si alguna de las sentencias de verificación no se corresponde con las

llamadas esperadas, se lanza una excepción:

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 código

muestra una verificación para ver que WhatIsCurrentTemp y CalcSpecialCurrentTemp se han llamado una vez. En

el caso de CalcSpecialCurrentTemp se lanzará una excepción si no se llama al método. Con un poco más de trabajo

en la expresión lambda, Microsoft Fakes ofrece el mismo nivel de opciones para verificar algoritmos y llamadas a

métodos 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 35: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Capítulo 3: Migrando a Microsoft Fakes

Page 35 of 82

Conclusión

Cualquier migración de test de Telerik, RhinoMocks o Typemock requerirá un gran esfuerzo. Las migraciones

basadas en Buscar y Reemplazar no funcionarán bien ya que las definiciones de los comportamientos son muy

diferentes. Los detalles para cada uno de esos frameworks están fuera del alcance de este documento. Para ver

cómo migrar de estos productos a Microsoft Fakes, será mejor leer la documentación de cada producto.

Page 36: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Capítulo 4: FAQ

Page 36 of 82

Capítulo 4: FAQ En este capítulo, veremos algunas preguntas frecuentes, algunas creemos que son avanzadas, - algunos casos

extremos – aunque creemos que son lo suficientemente importantes como para cubrirlos en esta guía.

Trabajando con .NET Framework 4 Sólo porque Microsoft Fakes sea una nueva característica no quiere decir que esté restringido a .NET 4.5. Podemos

usar Microsoft Fakes con cualquier versión 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 versión

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 añadimos el assembly Microsoft.QualityTools.Testing.Fakes.dll a nuestro propio proyecto y hacemos check in, los

demás podrán compilarlo. Sin embargo, se lanzará la excepción NotSupportedException si están ejecutando una

edición de Visual Studio que no soporte Microsoft Fakes.

Para evitar estas excepciones deberemos colocar a los test en una categoría de test que no se ejecute cuando los

desarrolladores o los servidores de builds no estén ejecutando una versión adecuada de Visual Studio. Por ejemplo:

[TestCategory("FakesRequired"), TestMethod()]

public Void DebitTest()

{

}

Si optamos por no añadir la dll Microsoft.QualityTools.Testing.Fakes.dll de manera local, podemos aislar el uso de

Fakes añadiéndolos a un proyecto a parte y compilar ese proyecto sólo en con una configuración de build

específica.

Es importante ver que si nuestros servidores de builds están ejecutando una versión adecuada de Visual Studio

debemos tener también 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 versión RTM de Visual Studio 2012,

tendremos que generar y publicar nuestro archivo TRX. Visual Studio 2012 Update 1 incluye una actualización de

vstest.console.exe que soporta la publicación como parte de la llamada.

¡No se pueden hacer fakes de todo! Por defecto, a la mayoría de las clases de System o no se les hacen fakes o no se puede debido a decisiones de

diseño. Las clases de System se tratan de una forma especial ya que son usadas por el propio motor, y conllevaría

a un comportamiento impredecible. Por ejemplo, los siguientes namespaces no están soportados en proyectos

para .NET 4:

- System.Globalization

- System.IO

- System.Security.Principal

- System.Threading

Page 37: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Capítulo 4: FAQ

Page 37 of 82

No hay una lista definitiva de los tipos que no se soportan ya que depende de las diferentes combinaciones

posibles de versión 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.

AV

ISO

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 generación de los tipos de stub y filtrarlo en un archivo xml con la extensión .fakes:

NO

TA

Para eliminar los tipos que Microsoft Fakes no soporta, como CancellationToken y CancellationTokenSource,

tendremos que refactorizar nuestro código 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 compilación como un Warning.

Pásate por Code generation, compilation, and naming conventions in Microsoft Fakes para más información

Logging detallado Debido a varias razones, Fakes puede decidir saltarse una clase cuando genera los shims. Con Visual Studio 2012

Update 1, podemos obtener más información sobre el porqué de esa decisión 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 diagnóstico 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 38: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Capítulo 4: FAQ

Page 38 of 82

el assembly “shimeado”. Podemos especificar una clave pública 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 código 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 pública tanto para el assembly fake como para el assembly de test. Por ejemplo:

[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("SimpleLibrary.Test, PublicKey=002…8b")]

[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("SimpleLibrary.Fakes, PublicKey=002…8b")]

Fijaos que necesitaremos la clave pública y no el key token público del assembly, que es lo que normalmente

vemos. Para obtener la clave pública 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 debería ser:

[assembly: InternalsVisibleTo("ClassLibrary1.Fakes, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e92decb949446f688ab9f6973436c535bf50acd1fd580495aae3f875aa4e4f663ca77908c63b7f0996977cb98fcfdb35e05aa2c842002703cad835473caac5ef14107e3a7fae01120a96558785f48319f66daabc862872b2c53f5ac11fa335c0165e202b4c011334c7bc8f4c4e570cf255190f4e3e2cbc9137ca57cb687947bc")]

Optimizando la generación de Fakes Por defecto, cuando añadimos 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 compilación.

Si tenemos un código base testable y no necesitamos Shims, desactívalos. Si sólo necesitas que se incluya un

subconjunto de las clases de tu solución, identifícalos con el filtrado de Tipos. (Pasate por Code generation,

compilation, and naming conventions in Microsoft Fakes).

NO

TA

Antes de que indiques los filtros de tipo, pon siempre un <Clear/>

Deshabilita la generación con un solo <Clear/> o con el atributo Disable=”true” en el elemento StubGeneration o

ShimGeneration

En el siguiente código se desactiva el ShimGeneration, y genera Stubs sólo 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" />

Page 39: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Capítulo 4: FAQ

Page 39 of 82

</StubGeneration>

<ShimGeneration Disable="true"/>

</Fakes>

Tenemos que saber que la generación restringida tiene un efecto en el tiempo de compilación y que la mejor

optimización que podemos hacer es evitar la re-generación de los fakes. Si estás haciendo un fake de un assembly

que no suele cambiar deberías compilar los assemblies fakes una sola vez en un proyecto a parte y añadir esos

assemblies como referencias al control de código. Y referenciar desde ahí tus proyectos de test unitarios.

Mirando bajo las sábanas Vamos a ver qué ocurre si modificamos la configuración 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 añadirlo 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 Configuración Tiempo de

compilación

Estructura del Assembly y comentarios

Sin Fakes NA 1s NA

Por

defecto

<Fakes xmlns="http://schemas.microsoft.com/fakes/2011/">

<Assembly Name="System" Version="4.0.0.0"/>

</Fakes>

19.5s

(La imagen pequeña es intencionada lo

tienes todo)

Fíjate en el tiempo de compilación, por

eso es la recomendación de generarlo

una vez y hacer check in de los

assemblies que no vayan a cambiarse

Sin Stubs <Fakes xmlns="http://schemas.microsoft.com/fakes/2011/">

<Assembly Name="System" Version="4.0.0.0"/>

<StubGeneration Disable="true"/>

</Fakes>

18.6s

Este cambio al archivo de configuración

tiene un gran impacto en la salida, pero

muy poco en el tiempo de compilación

Sin Stubs

ni Shims

<Fakes xmlns="http://schemas.microsoft.com/fakes/2011/">

<Assembly Name="System" Version="4.0.0.0"/>

<StubGeneration Disable="true"/>

<ShimGeneration Disable="true"/>

</Fakes>

18.8s

Esto es sólo un ejemplo. No tendría

sentido no generar stubs ni shims

Page 40: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Capítulo 4: FAQ

Page 40 of 82

Desc Configuración Tiempo de

compilación

Estructura del Assembly y comentarios

Limitado <Fakes xmlns="http://schemas.microsoft.com/fakes/2011/">

<Assembly Name="System" Version="4.0.0.0"/>

<StubGeneration>

<Clear />

<Add Namespace="System!" />

<Add Namespace="System.IO!"/>

</StubGeneration>

</Fakes>

18.6s

Fijaos en el uso de <Clear/>. Sin el clear,

tendríamos la misma salida que en el

comportamiento por defecto

Refactorizando código bajo test Las convenciones de nombrado usados en Microsoft Fakes puede hacer que el refactoring del código 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 sería System.Fakes.ShimDateTime. El nombre de una

clase stub se deriva del nombre de la interfaz, con el prefijo “Fakes.Stub”, y añadiéndole el nombre del tipo. El

nombre del tipo stub se deriva de los nombres del método y los parámetros.

Si refactorizamos el código bajo test, el test unitario que hemos escrito usando Shims y Stubs de la versión

generada con Fakes assemblies no compilará. Ahora mismo, no hay una solución fácil a este problema a parte de

la de crear una expresión regular a medida para actualizar nuestros test unitarios. Ten esto en cuenta cuando

estimes cualquier refactorización del código que ha sido testeado unitariamente. Puede suponer un coste alto.

Eliminar Fakes de un proyecto Añadir y, especialmente, eliminar los Fakes de tu solución puede que no sea trivial. Te pedimos que evalúes 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 41: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Capítulo 4: FAQ

Page 41 of 82

1. Borra el directorio Fakes y los archivos asociados de tu proyecto

2. Borra las referencias a los assemblies .Fakes de tu proyecto

3. 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 hacían

referencia a los Fakes:

Page 42: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Capítulo 4: FAQ

Page 42 of 82

Uso de Fakes con el control de versiones de Team Foundation Cuando añadimos Fakes, nos daremos cuenta de que se crean los directorios 1 Fakes y 2 FakesAssemblies.

Contienen una serie de archivos de configuración y assemblies:

Team Explorer nos indica estos cambios cuando trabajamos en local:

Los assemblies Fakes son auto-generados y están en el directorio ‘FakesAssemblies’ bajo el proyecto que los

referencia. Estos archivos se crean en cada compilación. Por lo que no deberían ser considerados como elementos

Page 43: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Capítulo 4: FAQ

Page 43 of 82

configurables ni deberían añadirse al control de código. Sin embargo, los archivos de configuración de Fakes del

tipo ‘assemblyName.fakes’ que se crean en el directorio ‘Fakes’ en el proyecto son elementos configurables y

deberían incluirse en el control de código.

1. Selecciona los cambios del directorio Fakes

2. Añádelos al control de código.

Excluir Fakes – usando Team Explorer

Para excluir Fakes:

1. Selecciona los cambios del directorio Fakes y haz clic derecho.

2. Selecciona “Ignore”:

Otra forma sería seleccionar cada cambio de manera separada, lo que permite más opciones para ignorarlo (como

por extensión, 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 incluirán en el control de código:

Estas reglas se aplican a un archivo .tfignore:

- # para indicar que una línea es un comentario.

- Se soportan los caracteres especiales * y ?

- Una definición es recursiva a menos que se prefije con el carácter \

- El símbolo de exclamación, !, niega una definición (los archivos que cumplan el patrón NO son ignorados).

El archivo .tfignore puede editarse con cualquier editor e texto y debe añadirse al control de código.

Podemos configurar qué tipos de archivos se ignorarán 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 44: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Capítulo 4: FAQ

Page 44 of 82

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 difíciles 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 aplicación y testear sólo la funcionalidad que es

parte del controlador. Para ello, tenemos que inyectarle la dependencia al controlador, normalmente por el

constructor a través 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 código vemos cómo crear

un stub para inyectárselo 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 45: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Capítulo 4: FAQ

Page 45 of 82

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 más fáciles. Para

ese escenario, podemos usar Shims. Con Shims, podemos cambiar el comportamiento de un objeto, configurando

el resultado esperado en un método o propiedad. En este código vemos cómo 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 acción, 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 46: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Capítulo 4: FAQ

Page 46 of 82

};

// 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 47: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Capítulo 5: Técnicas avanzadas

Page 47 of 82

Capítulo 5: Técnicas avanzadas El tema central de este capítulo no es estrictamente el testing unitario, sino algunos escenarios en los que podemos

aprovechar más de lo que ofrece Fakes. Siguiendo los principios establecidos del Test-Driven Development suele

ser la mejor opción cuando empezamos un nuevo proyecto. Sin embargo, cuando tratamos con código heredado

(legacy code) que no fue diseñado 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

implementación, como la documentación, no está accesible.

En estas circunstancias, suele ser necesario entender el comportamiento de la implementación. El framework de

testing unitario que ofrece Visual Studio, junto a Microsoft Fakes, son un conjunto de herramientas perfectas para

esto. Además, la habilidad inherente de ejecutar los tests de manera selectiva para obtener información adicional

sobre lo que está ocurriendo en diferentes condiciones, aumenta el proceso de aprendizaje. Como también

veremos, el código resultante generará muchos artefactos, normalmente conocidos como “Emuladores”, que

pueden ser muy útiles para un testing unitario “convencional” y se podrán usar en desarrollos posteriores, lo que

incluirá un refactoring para mejorar la testabilidad.

El código de referencia es el simulador de tráfico que usamos en el Ejercicio 4. Tiene un número de elementos que

están altamente acoplados. Vamos a trabajar sobre algunos de los retos que nos ofrece esta aplicación.

Tratando con servicios Windows Communication Foundation

(WCF) Muchas aplicaciones se implementan como varios procesos que se comunican a través de servicios. Podemos

configurar un entorno para el sistema completo usando Microsoft Test Manager y la característica 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 factoría, es un

problema sencillo el hacer que la factoría 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 código, o cualquier otro código que llame al método UpdateRoadword(), tendremos que

tratar con esta situación. La mejor opción sería refactorizar para tratarlo bien, pero hay ocasiones en las que eso

no es posible o deseable.

Rompiendo la dependencia

La solución más simple es hacer un Shim del cliente WCF y ofrecer nuestra propia implementación. Esto no requiere

ejecutar una instancia del servicio:

using (ShimsContext.Create())

{

RoadworkServiceReference.Fakes.ShimRoadworkServiceClient.Constructor =

(real) => { };

var intercept = new

Page 48: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Capítulo 5: Técnicas avanzadas

Page 48 of 82

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 además de crear un shim en la operación especificada, también se ha creado uno para el

constructor. Esto es debido a que un constructor real de WCF lanzará una excepción debido a la falta de

configuración del servidor.

Esta aproximación puede tener algunas limitaciones. Primero, la lógica requerida para ofrecer una implementación

simple puede que no lo sea tanto, y segundo, esta aproximación enmascarará cualquier problema de serialización

que pueda ocurrir, como pasar una clase derivada que no sea reconocida como un tipo válido (known type).

Mejorando la situación

Si el assembly con la implementación actual del servicio es parte de la solución, hay una solución que solventará

ambas limitaciones. Crearemos una instancia actual y usaremos la lógica real, pero evitaremos los aspectos del

servicio actual.

Como el cliente y el servidor no comparten la implementación de los tipos, tendremos que transformar los

parámetros y la salida. Usando un DataContractSerializer, también veremos cualquier problema de serialización:

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 49: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Capítulo 5: Técnicas avanzadas

Page 49 of 82

{

var clientImpediment = new RoadworkServiceReference.Impediment();

clientImpediment.location = Transform(result.Location);

impediments.Add(clientImpediment);

}

return impediments.ToArray();

});

RoadworkServiceReference.Fakes.ShimRoadworkServiceClient.AllInstances.RetrieveCurrentBlockAr

ray = intercept;

La implementación completa está disponible en el código del Hands-on Lab en:

- Exercise 4\Traffic.AdvancedTechniques\Examples\BreakingServiceBoudnaryTechniques.cs

Tratando con cálculos no deterministas En este ejemplo, el simulador hace cálculos 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 código, y haremos que nuestros test los usen para las comparaciones.

Operaciones basadas en Timer

Tratar con elementos de código que se invocan dependiendo del tiempo suele presentar algunos retos:

- Si el tiempo es muy rápido, puede que sea imposible saber exactamente cuántas 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 invocación manual del

código:

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 parámetros.

const int IterationCount = 10;

for (int i = 1; i <= IterationCount; ++i)

{

applicationCallback(state);

Thread.Sleep(interval);

}

Invocación el código deseado de manera determinista

Datos no repetibles

Otra situación que se suele dar es cuando la lógica se basa en una distribución creada por un generador de

números aleatorios. Esto hace imposible saber exactamente qué datos se generarán. La forma más simple es hacer

un Shim de la clase Random, y ofrecer unos valores deterministas.

Page 50: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Capítulo 5: Técnicas avanzadas

Page 50 of 82

En este ejemplo vamos a asegurarnos que los coches son todos con la orientación oeste, en lugar de que sea algo

aleatorio en el código:

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 implementación completa está disponible en el Hands-on Lab:

- Exercise 4\Traffic.AdvancedTechniques\Examples\NonDeterministicBehaviorTechniques.cs

Como estamos tratando con generadores de número aleatorios, hay un detalle que se suele pasar por alto: ¡Varias

instancias con la misma semilla generará la misma secuencia de números! Si intentáis realizar operaciones con

conjuntos independientes de números aleatorios y estáis usando varias instancias de Random para conseguirlo,

debéis aseguraros de que tienen la misma semilla. Esto lo veremos en la próxima sección.

Recopilación de casos de uso y más información analítica. En este ejemplo, el código que se testea realiza un número de operaciones matemáticas, 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 implementación

Aquí nos enfrentamos a un problema potencial cuando trabajamos con generación de número aleatorios. Cada

vez que se crea una instancia de la clase Random, se usa una semilla; diferentes instancias con la misma semilla

generarán la misma secuencia de números.

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. Además, como queremos que este test recopile datos sin tener

que alterar el comportamiento del código que se testea, debemos asegurarnos de que el generador de números

aleatorios continúa trabajando.

NO

TA

Nota: El constructor sin parámetros usa Environment.TickCount, con lo que varias instancias creadas en

un tiempo pequeño podrían 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 51: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Capítulo 5: Técnicas avanzadas

Page 51 of 82

{

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 implementación 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 válidas cuando seleccionamos la mejor ruta para un coche. La lógica para

esto está contenida en las clases ShortestTime y ShortestDistance. Desafortunadamente, la lista de rutas válidas

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 52: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Capítulo 5: Técnicas avanzadas

Page 52 of 82

ShimShortestTime.AllInstances.SelectBestRouteCar = shim;

La implementación completa del código está en el Hands-on Lab:

- Excersie 4\Traffic.AdvancedTechniques\Examples\DataGatheringTechniques.cs

Evitando la duplicación 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 idéntica.

Una solución común 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 más complejos en los que o las relaciones entre las clases están muy acopladas o si queremos llamar a

implementaciones reales para alguna o todas las funcionalidades.

Una solución más sensata es crear un Doppelgänger. 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 conversión entre los dos. Como Doppelgänger es largo y difícil de decir, simplemente nos

referiremos a él como “clase Testable” y será fácilmente identificable ya que tendrá el mismo nombre que la clase

real pero con el prefijo “Testable”, por ejemplo TestableCar para Car.

Tenemos un conjunto de clases “Testable” que se corresponden con las clases del assembly que queremos testear.

Os pedimos que miréis estas clases. Nos permiten, por ejemplo, hacer un shim de una llamada a un servicio y hacer

un shim de la inicialización de una ruta, evitando los largos tiempos de inicialización en una TestableCity, o hacer

un shim del constructor Random para conseguir el control de un TestableCar

La implementación completa está disponible en el Hands-on Lab:

- Exercise 4\Traffic.AdvancedTechniques\Examples\AvoidingDuplicationTechniques.cs

Page 53: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Capítulo 6: Hands-on Lab

Page 53 of 82

Capítulo 6: Hands-on Lab

NO

TA

Pásate por la sección Uso del código 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 aplicación ASP.NET MVC 4 muy simple. La solución IntroToStubs.sln en el

directorio Hands-on Lab\Excercies 1\start tenemos sólo 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 cálculo 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 aproximación que tendríamos se nos ocurriría sería:

1. Crear una base de datos de ejemplo

2. Rellenarla con datos de ejemplo.

3. Crear los test – añadiendo los datos de ejemplo que requieran para ejecutarse.

OB

JETIV

O

En este ejercicio, veremos cómo con Microsoft Fakes podemos aislar la dependencia existente entre la base de datos

y nuestra clase controladora para testear la implementación funcional.

Dependencias del entorno

¿Qué es lo que está mal en esta primera aproximación? Bueno, ¿qué pasa si la base de datos es una base de

datos relacional? Recordad, los test unitarios tienen que ser pequeños y rápidos. Además, para que todo el

equipo de desarrollo pueda ejecutar estos test en sus máquinas habría 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

(máquinas o instancias configuradas con componentes conocidos, librerías, compiladores, scripts.) Estas

máquinas 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.

Patrón de implementación

En la siguiente figura podemos ver la interacción normal entre varias clases. Además, podemos ver que el

acoplamiento entre el Repositorio y la base de datos es lo que queremos aislar. Nuestra intención es centrar

nuestros test en la lógica de negocio que, para este ejemplo, estará en los métodos Action de la clase Controller.

Page 54: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Capítulo 6: Hands-on Lab

Page 54 of 82

Cuando aislamos, desacoplamos nuestra implementación 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 código del propio test indicará cuáles

son los valores necesarios para testear:

NO

TA

El ejemplo es una solución común que aprovecha el constructor con parámetros de la clase Controller, junto al

patrón Repository

Pasos a realizar

1. Añadir un assembly fake por cada assembly del que queramos un fake

2. Revisar y ajustar la configuración de los archivos Fakes [avanzado]

3. Ajustar los usings (C#) o los Import (VB) a los namespaces de los Stubs necesarios.

4. Añadir la implementación necesaria para los stubs en aquellas clases y métodos necesarios para los tests

que se vayan a hacer.

5. Dar el código para el Act del objeto o método que se va a testear.

6. Poner los Assert necesarios con los datos esperados.

Paso 1 – Revisar la solución de inicio

Primero, échale un vistazo a la solución con la que empezamos, IntroToStubs.sln, que se compone de dos

proyectos:

1. MainWeb – proyecto principal de MVC4

2. MainWeb.Tests – Proyecto del tipo Microsoft Unit Testing

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

1. Controller -> OrderController

2. Model -> IOrderRepository

3. Model -> Order

4. Model -> OrderRepositoriy (implementación de IOrderRepository)

Page 55: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Capítulo 6: Hands-on Lab

Page 55 of 82

NO

TA

Para este ejemplo, OrderRepository representa la implementación concreta con la responsabilidad de obtener datos

de una base de datos física; sin embargo, en este ejemplo, hemos dejado cada método 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. Seleciona el proyecto MainWeb.Tests y hacemos Add a Project Reference a Main Web

2. En este momento, nos aseguramos de que la solución compila. Pulsa F6 para compilar la solución. Con

esto el proyecto de test actualiza la referencia y todos los tipos del assembly MainWeb para cuando

generemos el Fake.

Paso 3 – Añade el assembly de Fake al proyecto de test

1. 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.

2. En el Solution Explorer, navega hasta el proyecto MainWeb.Tests y abre el nodo References.

3. Haz clic con el botón derecho en MainWeb y selecciona la opción Add Fakes Assembly

4. En este punto, revisa el proyecto MainWeb.Test y la estructura de directorio con el Solution Explorer;

deberías ver el siguiente nodo adicional Fakes añadido a la estructura del proyecto MainWeb.Tests con

el nombre completo del assembly MainWeb y un “.fakes” como extensión de archivo:

NO

TA

El framework de Fakes ha generado Stubs y Shims para nuestro Assembly y aquellos tipos están en el

Microsoft.ALMRangers.MainWeb.Fakes.

Page 56: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Capítulo 6: Hands-on Lab

Page 56 of 82

Paso 4 – Revisa y actualiza el archivo xml de Fakes

Vamos a ver un poco el archivo XML que ha sido generado cuando hemos añadido 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. El Solution Explorer, selecciona el archivo Microsoft.ALMRangers.MainWeb.fakes y mira las propiedades

(F4) del archivo. Verás que el Build Action es Fakes.

3. Opcional: Modifica el archivo generado de la siguiente manera para sólo 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>

NO

TA

Estas opciones muestran cómo podemos adelgazar el assembly generado filtrando por tipos específicos. Cuando

compilamos, el framework de Microsoft Fkes generará un assembly para nuestro proyecto basándose en estas

opciones. Lo hacemos aquí para mostrar los valores reducidos que aparecen en el IntelliSense cuando estemos en el

editor de código.

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

implementación 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 específico a nuestras necesidades de testeo. Además de eso, hay algunas clases básicas

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; }

}

Page 57: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Capítulo 6: Hands-on Lab

Page 57 of 82

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 método de test unitario

Ya estamos listos para crear nuestros tests unitarios. En este paso, vamos a implementar un listado de artículos

que simplemente resumirá la cantidad total de la orden.

1. Crea una clase de Test. Selecciona el proyecto de test, en el menú Project, elige la opción Add, Unit

Test

2. 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 también la clase.

Elige Si.

3. En el editor, renombra el TestMethod1 a OrderController_orderSumaryTotalCheck_equalSum()

4. La clase de test debería ser algo parecido a esto:

using System;

using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Microsoft.ALMRangers.MainWeb.Tests

{

[TestClass]

public class OrderControllerTests

{

Page 58: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Capítulo 6: Hands-on Lab

Page 58 of 82

[TestMethod]

public void OrderController_orderSummaryTotalCheck_equalsSum()

{

}

}

}

Paso 7 – Ordena el método y crea un Stub para la interfaz Repository

Ya estamos listos para escribir nuestro test unitario. Recuerda que estamos testeando el método de acción

OrderController en el controlador y que estamos aislando la lógica del OrderController de la implementación

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 están los tipos

(stubs y shims) generados por el framework de Fakes durante la compilación presentes en el assembly. En la

compilación, el objetivo de la generación son los assemblies y los namespaces. Hemos añadido un alias para

ModelFakes para que sea más fácil leer el código. No es necesario hacerlo, podemos usar el namespace

completo si queremos.

NO

TA

El alias del using anterior simplemente es para una lectura más sencilla del código, no es necesario hacerlo.

2. Crear una instancia de IOrderRepository. Se seteará a una implementación 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

}

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 implementación para

cualquier método que haga falta. La implementación del Stub, generada por el framework de Microsoft

Fakes es un tipo estándar de CLR – sin ningún comportamiento. Ahí es nosotros tenemos que inyectar el

código necesario para nuestro test.

3. En este punto, hemos inicializado la instancia para nuestro Stub del repositorio – pero aún no hemos

terminado. Tenemos que implementar dos métodos del Stub ya que nos va a hacer falta para nuestro

test.

Page 59: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Capítulo 6: Hands-on Lab

Page 59 of 82

4. Añade el siguiente código, en el que hemos tenido que añadir una expresión lambda para definir el

método IOrderRepository.Find(int):

FindInt32 = id =>

{

Order testOrder = new Order

{

Id = 1,

CustomerName = "smith",

TaxRate = 5

};

return testOrder;

},

NO

TA

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 método añadiendo el tipo del parámetro como parte del

nombre. Asi que como el método Find de IOrderRepository tiene un parámetro del tipo Int32, el nombre del stub

es FindInt32.

5. Haz un método de generación de datos estático 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. Añade el siguiente código para tener un stub del método IOrderRepository.OrderLines(int). Usa el

método estático GetOrderLines

OrderLinesInt32 = id =>

{

var OrderLines = GetOrderLines();

return OrderLines.AsQueryable();

}

7. Justo después del “}” añade el siguiente código para crear la instancia del OrderController usando el

constructor con parámetros:

var controller = new OrderController(repository);

NO

TA

La testabilidad de la solución 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 generación de Stubs con el framwork de Microsoft Fakes.

Lee el ejercicio de Shims para testear lo “intesteable”.

Page 60: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Capítulo 6: Hands-on Lab

Page 60 of 82

Paso 8 – Llama al método de acción del controlador y comprueba los resultados

1. Añade el siguiente código para completar el método

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í tenéis el código 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 61: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Capítulo 6: Hands-on Lab

Page 61 of 82

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();

}

NO

TA

En este punto, ya podemos ejecutar el test desde el Test Explorer y veremos que falla. La próxima tarea es modificar

la lógica del método para hacer que funcione.

Paso 9 – Completar la implementación de la acción del controlador

1. Añade el siguiente using a la clase OrderController:

using System.Linq;

2. Podemos copiar este código en el método de acción 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 62: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Capítulo 6: Hands-on Lab

Page 62 of 82

viewModel.Total = total;

return this.View(viewModel);

}

Paso 10 – Ejectuar el test unitario

1. Abre el Test Explorer y compila la solución (F6)

2. Una vez que se haya compilado, el Test Explorer debería mostrar un solo test en la solución:

OrderController_orderSummaryTotalCheck_equalsSum bajo la categoría Not Run Tests

3. Haz click en Run All para ejecutar todos los test (en esta solución solo hay 1).

4. Después 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 método de acción del OrderController (OrderLines) devuelve un modelo

con una propiedad Total que se corresponde con nuestros datos de test, basados en el cálculo de tasas.

REV

ISIÓ

N

En este ejercicio, hemos eliminado la dependencia de la base de datos y hemos visto cómo Microsoft Fakes Stubs se

puede usar para testear componentes a través del aislamiento de dependencias. Podéis ver el código final en Hands-

on Lab\Excercise 1\end.

Page 63: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Capítulo 6: Hands-on Lab

Page 63 of 82

Ejercicio 2: Usando Shims para aislarnos del sistema de archivos y

de la fecha (20 – 30 min)

OB

JETIV

O

En este ejercicio, veremos cómo usar Shims para aislarnos el código 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 añadir una nueva característica a la clase central del sistema: el LogAggregator. Esta

clase puede añadir archivos de log a un directorio y filtrar los archivos a sólo unos días.

No hay test unitarios para ese componente por ahora. Antes de cambiar nada en esa parte del código, quieres

asegurarte de que no rompes nada. Desafortunadamente, la clase LogAggregator no ha sido diseñada para que

se fácil asilarla ni del sistema de archivos ni de la hora del sistema a base de pasarle los valores necesarios. El

código no ofrece ninguna forma de inyectarle un stub, está ocultando su implementación.

Por lo tanto, vamos a crear nuestro primer Shim para poder testear la clase LogAggregator.

Paso 1 – Revisar la clase LogAggregator

1. Abre la solución EnterpriseLoger.sln en Hands-on Lab\Excercies 2\start y abre el archivo

LogAggergator.cs. Deberíamos ver el siguiente código 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 64: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Capítulo 6: Hands-on Lab

Page 64 of 82

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;

}

}

}

NO

TA

El código que vemos aquí es una clase para para centrarnos. Puedes hacer todos los pasos de este laboratorio

basándote en esta clase. Si no tienes acceso a la solución que tenemos preparada, puedes generarlo creando una

class library y copiando y pegando el código en él.

Paso 2 – Crea un proyecto de test

1. Añade un proyecto del tipo Visual C# Unit Test Proyect a la solución llamado

“EnterpriseLogger.Tests.Unit”

2. En el proyecto “EnterpriseLogger.Tests.Unit” añade una referencia al proyecto “EnterpriseLogger”.

Paso 3 – Crea el primer test

1. Renombra “UnitTetst1.cs” a “LogAggregatorTests.cs”

2. Abre “LogAggergatorTests.cs” y añade el siguiente using:

using Microsoft.ALMRangers.FakesGuide.EnterpriseLogger;

3. Reemplaza el método “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 método y selecciona la opción Run Tests. El test empezará a ejecutarse y fallará.

Page 65: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Capítulo 6: Hands-on Lab

Page 65 of 82

Este test que hemos creado testea el método “AggregateLogs” y dice lo que prueba en su propio nombre, que

es comprobar que la función “AggregateLogs” – cuando se llama con el path de un directorio que contiene

archivos de logs y un 3 en la variable daysInPast – debería devolver todas las líneas de aquellos logs desde hace

tres días hasta ahora.

Sin embargo, este test sólo funcionaría si el directorio “C:\SomeLogDir” existe y mágicamente contiene archivos

con los datos necesarios para este test. Esto se puede hacer escribiendo algún código de configuración. Sin

embargo, el test resultante sería más un test de integración más que un test unitario real, ya que estaría usando

el sistema de archivos.

Para hacer que sea un test unitario de verdad, vamos a aislar el test de los métodos estáticos que llama para

acceder a la fecha del sistema y al sistema de archivos. Vamos a revisar el código 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 método estático que vemos representa un patrón 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

más adelante en este ejercicio. Ahora vamos a hacer un shim para las funciones Directory.GetFiles() y

File.ReadAllLines().

Paso 4 – Añadir 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 opción Add Fakes assembly:

NO

TA

Visual Studio ha creado un nuevo directorio llamado “Fakes” que contiene dos archivos XML y ha añadido

referencias a dos assemblies recién generados.

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 razón por la que hay dos archivos

es porque el namespace System genera más de un assembly. Como mscorlib.dll no se puede referenciar

Page 66: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Capítulo 6: Hands-on Lab

Page 66 of 82

directamente, para seguir usando Fakes, siempre se añade un assembly fake de mscorlib.dll cuando se hace un

Fake de System.dll.

2. Por convención, los tipos Fakes para el namespace System.IO están en el namespaces System.IO.Fakes.

Para usarlos, trabajaremos con una sentencia using. En el Solution Explorer haz doble clic en

“LogAggregatorTests.cs” y añade el siguiente using al principio el archivo: using System.IO.Fakes;

3. Cambia el método de test en “LogAggregatorTests.cs” de la siguiente forma. Los cambios están 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 código. Hemos añadido dos sentencias:

ShimDirectory.GetFilesStringString = [some delegate];

ShimFile.ReadAllLinesString = [some delegate];

Estas sentencias le dicen al framework de Microsoft Fakes qué métodos de deben ser interceptados y qué código

se debe ejecutar. Los nombres están puestos por convención. 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 interceptación de la llamada es el nombre del método con el sufijo de los nombres de

los tipos de sus parámetros. Esta convención nos permite setear diferentes delegados para diferentes

sobrecargas de un método.

El código que hemos asignado a estas propiedades en el código anterior hace que el método

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 patrón que

Page 67: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Capítulo 6: Hands-on Lab

Page 67 of 82

se use en el nombrado de los directorios). Y ahora, el método ReadAllLines(string) devuelve arrays de strings que

representan las líneas de los archivos de logs imaginario, basándose en el parámetro del path.

4. Click derecho en el cuerpo del método de test y seleccionamos Run Tests. El test falla.

5. En el Test Explorer bajo Failed Tests, seleccionamos el test “AggregateLogs …” y vemos la descripción

del error.

El mensaje de error nos dice que usemos un ShimsContext. Esto es necesario para indicar el contexto en el que

se usarán los shims. Los shims sólo se usaran en ese contexto. Sin ese contexto, los shims podrían provocar

efectos secundarios en el resto de test. Así que vamos a hacerlo

NO

TA

La configuración de un ShimsContext debería hacerse siempre en una sentencia using, y nunca en un método

setup/initialize o en un teardown/cleanup. Esto podría dejar que los shims estén definidos en otras partes de los test

que no deberían afectando y alterando los test.

6. En el bloque de usings al principio del “LogAggregatorTests.cs” añade el siguiente using:

using Microsoft.QualityTools.Testing.Fakes;

7. Cambia el método 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 método shim GetFiles(string,string) son de más de tres días de antiguiedad y ninguna de ellas

entran en el filtro. Vamos a revisar el método 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 68: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Capítulo 6: Hands-on Lab

Page 68 of 82

Este método se basa en una llamada a la propiedad estática “Today” de la clase DateTime. Esto es lo que hace

que el test falle cuando se ejecuta en un día 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 – Añadir un Shim para aislarnos de la fecha del sistema

1. En la sección de usings de “LogAggregatorTests.cs” añade la línea:

using System.Fakes;

2. Añade la siguiente línea 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

ejecución.

1. Pon el cursor en la primera línea del código, debajo del comentario //Act

2. En el menú principal, ve a Debug, Toggle Breakpoint para añadir un punto de ruptura:

3. Haz clic derecho en cualquier parte del test y selecciona la opción Debug Tests.

4. Después de que lleguemos al breakpoint, pulsa F11.

5. Continúa paso a paso la ejecución del código (usando F11) hasta que llegamos a la primera ejecución de

la expresión lambda:

NO

TA

Cuando usamos un Shim, todo el dominio de la aplicación es enrutado a través 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.

6. Continúa la ejecución hasta que pases por todas las expresiones lambda, y en el menú principal elige

Debug, Continue.

REV

ISIÓ

N

Hemos conseguido aislar el código de producción del LogAggregator de sus dependencias con el sistema de

archivos y de la fecha del sistema – sin tener que cambiarlo. Puedes ver el código final en Hands-on

Lab\Exercies2\end

Page 69: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Capítulo 6: Hands-on Lab

Page 69 of 82

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 públicas y clases virtuales hace que no sea posible usar Stub.

Esto es algo común en cualquier tecnología que ofrezca un “framework” para resolver cierto tipo de problemas.

También suele ser habitual en sistemas heredados que no siguieron las conocidas “buenas prácticas” – diseña tu

sistema para que sea fácilmente testable. Estos sistemas heredados no usan el patrón de inyección de

dependencias.

Eso deja al desarrollador con dos opciones si quiere escribir tests.

1. No hacer testing unitario. Confiar en los test de integración basados en UI dejando el subsistema de

SharePoint como una caja negra.

2. Hacer un wrapper de todo el subsistema que llama a los objetos de SharePoint a través de un interfaz y

una implementación. Esto puede ser una buena solución en muchos proyectos. Ya que los detalles de

implementación del código de SharePoint se ocultan tras la interfaz, se puede testear unitariamente

todo el código que no sea de SharePoint. Los detalles de implementación de las características 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 técnicas de Shim que vimos ántes.

Sin embargo, en el caso de SharePoint, la técnica 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 práctico usar este tipo de encapsulación.

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.

OB

JETIV

O

En este ejercicio, veremos cómo usar Shims para testear que un recibidor de eventos de SharePoint funciona

correctamente.

Preparación

Este ejercicio requiere que el PC tenga una versión soportada de Visual Studio y SharePoint 2010 Foundation

(aunque cualquier SKU posterior de SharePoint también se puede usar). La instalación de SharePoint no se

ejecutará directamente durante este ejercicio, pero los assemblies que se instalarán en el GAC y en Program Files

los usaremos para el proceso e Faking. Otra opción, si no tenemos estas herramientas disponibles en nuestro PC,

es usar la máquina 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-management-

virtual-machine-and-hands-on-labs-demo-scripts.aspx) ya que tiene todos los componentes necesarios.

AV

ISO

SharePoint debe estar instalado en la máquina de desarrollo, no sólo una copia de las dll’s, para que todos los

assemblies necesarios para crear los fakes estén disponibles

Paso 1 – Crear una característica de ejemplo de SharePoint

1. En Visual Studio, añade una class library para .NET 3.5 llamada Samples.SharePoint

Page 70: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Capítulo 6: Hands-on Lab

Page 70 of 82

NO

TA

En código de producción, podemos usar cualquier plantilla de SharePoint para la característica que vamos a

desarrollar, de este modo se seleccionará la versión correcta de .NET. Sin embargo, para que este HOL sea lo más

simple posible, vamos a usar una Class Library báscia. 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

2. Añade 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.

3. Renombra el archivo Class1.cs a ContentTypeItemEventReceiver.cs

4. En el archivo ContentTypeItemEventReceiver.cs reemplaza el cuerpo de la clase con el siguiente

código:

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;

}

}

Esta será nuestra aplicación de ejemplo. Hemos añadido un recibidor de eventos que se llamará cada vez

que se añade un elemento a la lista de SharePoint. Este recibidor edita el título del nuevo elemento con

lo que haya en la propiedad “ContentType”.

5. Compila el proyecto. Debería compilar sin errores. Si hay algún problema, comprueba los errores.

Paso 2 – Crear un test

1. Añade un Unit Test Proyect para .NET 4 a la solución con el nombre Samples.SharePoint.Tests

NO

TA

En este ejercicio, usamos MSTest como framework de testing unitario. Sin embargo, podríamos 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 código 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. Añade una referencia al proyecto Samples.SharePoint

3. Añade una referencia a la dll Microsoft.SharePoint.dll, como hiciste en el proyecto de ejemplo.

4. En la sección de referencias del proyecto de test selecciona la referencia Microsoft.SharePoint y haz clic

derecho. Selecciona la opción Add Fake Assembly. Esto puede tardar unos segundos (incluso algunos

Page 71: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Capítulo 6: Hands-on Lab

Page 71 of 82

minutos). Cuando termine, deberías poder ver los archivos que se han creado en el directorio de fakes y

añade una referencia a la dll Microsoft.SharePoint.Fakes.dll:

AV

ISO

Si por alguna razón la creación de los fakes falla y tienes que repetir el proceso, asegúrate de borrar el archivo del

directorio de Fakes. Si no, aparecerá un error cuando repitamos el proceso.

La razón más común 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

5. Renombra el archivo UnitTest1.cs a SharePointEventTests.cs

6. Renombra el método TestMethod1 con un nombre significativo como

Contributor_AddsNewItem_EventFires.

7. Añade las referencias necesarias para el assembly fake de SharePoint y a la librería de faking

El código listado al final de esta sección muestra el test completo. Los siguientes pasos muestran la jerarquía

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 funcionarán dentro de ese bloque.

9. Añade dos variables locales, systemUpdateHasBeenCalled y itemTitleValue. Serán usadas como flags

para indicar que se ha llamado al código correcto.

10. Crea un shim para un objeto SPItemEventProperties y configura su comportamiento para las tres

propiedades que serán llamadas.

11. Crea un shim para interceptar la llamada al constructor SPSite

NO

TA

En el código de ejemplo, los parámetros del constructor “shimeado” comienzan con el símbolo ‘@’. Esto permite el

uso de palabras reservadas para el nombre de las variables que, en este caso, hace más fácil su comprensión.

12. Dentro del bloque SimSPSite configura el método 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 parámetro

GUID no se usa.

15. Dentro del bloque, configura el método GetItemById para que devuelva un SPListItem, el parámetro Int

no se usa.

16. Dentro del boque, configura la propiedad Item (get y set) y el método SystemUpdate. Fíjate 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, después de la creación de shim anidados, crea una instancia de la clase a testear.

18. Añade una llamada al recibidor de eventos que queremos testear.

Page 72: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Capítulo 6: Hands-on Lab

Page 72 of 82

19. Añade dos asserts para comprobar que se han hecho las llamadas que esperamos que se hagan.

20. Compila el proyecto. Debería compilar sin errores. Si hay alguno, comprueba los mensajes (comprueba el

código al final de esta sección).

21. Abre el Test Explorer (en el menú Test\Windows\Test Explorer). Deberías ver el nuevo test en la lista, si

no lo ves, recompila la solución.

22. Elige la opción Run All.

23. El test debería ejecutarse y pasar.

El código completo del test sería este:

namespace Microsoft.ALMRangers.FakesGuide.Sharepoint.Tests

{

using System;

using Microsoft.QualityTools.Testing.Fakes;

using Microsoft.SharePoint.Fakes;

using 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 73: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Capítulo 6: Hands-on Lab

Page 73 of 82

Assert.AreEqual(true, systemUpdateHasBeenCalled);

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

}

}

}

}

NO

TA

Microsoft ha publicado SharePoint Emulator, que son una versión 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

están disponibles como un paquete Nuget

REV

ISIÓ

N

En este ejercicio, hemos visto cómo los Shims de Microsoft Fakes se pueden usar para testear las características e

SharePoint. Puedes ver el código final en Hands-on Lab\Exercise 3\end

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

Page 74: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Capítulo 6: Hands-on Lab

Page 74 of 82

Ejercicio 4: Haciendo testable código heredado (20 – 30 min) El código heredado puede plantear problemas para refactorizarlo, especialmente cuando es código altamente

acoplado o que hace poco uso de interfaces. Para refactorizar el código, es preferible tener tests unitarios que

aseguren el comportamiento del código antes de cambiarlo.

OB

JETIV

O

En este ejercicio, usaremos Shims y Stubs para conseguir que el código heredado esté testado

Escenario

En este escenario usaremos la aplicación 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 disposición

de la ciudad para el simulador Traffic. Esta clase consume el servicio WFC Traffic.RoadworkService a través de

una clase proxy que se invoca desde un método privado. Ese método 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 vehículo en el simulador. Esta clase consume un conjunto de objetos del

componente Traffic Core a través de la propiedad ShouldMove. Usaremos una combinación de Stubs y Shims

para poder testear esta propiedad

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

1. En la solución ComplexDependencies, añade un nuevo proyecto con la plantilla Visual C# Unit Test y

llámalo Traffic.Core.Tests

2. Añade una clase llamada CityTests.cs

3. En el nuevo proyecto de test, añade una referencia al proyecto Traffic.Core

Después de haber visto el código a testear, podemos añadir un test unitario para testear la propiedad

Run de la clase City.

4. Abre la clase City.cs el directorio Model del proyecto Traffic.Core

5. Busca la propiedad Run y mira el código. Fíjate que el setter de la propiedad tiene una dependencia con

una instancia de System.Threading.Timer, que invoca al método OnTimer

6. Ve a ese método haciendo clic derecho en la llamada OnTimer y selecciona la opción Go To Definition.

Verás que este método llama al método 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. En el archivo CityTests.cs renombra el método TestMethod1() a City_CanSetRunProperty_True().

2. Actualiza tus referencias para incluir System.Threading y Traffic.Core.Models y añade los usings

necesarios:

using System;

using System.Threading;

Page 75: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Capítulo 6: Hands-on Lab

Page 75 of 82

using Microsoft.VisualStudio.TestTools.UnitTesting;

using Microsoft.ALMRangers.FakesGuide.ComplexDependencies.Traffic.Core.Models;

3. Añade el siguiente código al método:

[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 código, el test fallará ya que las llamadas que hay por debajo al servicio Roadwork no

serán invocadas. Además, podemos decir que este test es frágil 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 – Añadir 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 opción 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 implementación de Shim se invocará en lugar del

servicio actual y que devolveremos un Stub como resultado. Para asegurarnos de que nuestra

implementación ha sido invocada, usaremos una variable privada booleana que pondremos a true en el

método. Además, también queremos ofrecer una implementación alternativa al constructor del

RoadworkServiceClient. Esto asegura que una clase proxy muy básica se ha habilitado.

Paso 4 – Modificar el test unitario para la propiedad Run

1. Añade las siguientes referencias al proyecto Traffic.Core.Tests:

System.Runtime.Serialization

System.ServiceModel

2. En la clase CitiTests añade estos usings:

using System.Collections.Generic;

using System.Linq;

using Microsoft.QualityTools.Testing.Fakes;

using

Microsoft.ALMRangers.FakesGuide.ComplexDependencies.Traffic.Core.RoadworkServiceReference.Fakes

3. Para usar los métodos del Shim, tenemos que envolver las llamadas donde invocamos a la propiedad

Run en un ShimsContext. Esto asegurará que las llamadas se sustituirán sólo en el código que se está

testeando. Envuelve el contenido del método con este código:

using (ShimsContext.Create())

{ }

4. Bajo la línea en la que se declara el booleano expected, añade otra variable local booleana llamada

hasServiceBeenInvoked e inicialízala a false.

Page 76: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Capítulo 6: Hands-on Lab

Page 76 of 82

5. No queremos que se invoque al constructor actual del RoadworkServiceClient así que usaremos Shims

para crear una implementación alternativa. Justo después de la variable booleana que acabamos de

declarar añade el siguiente código: ShimRoadworkServiceClient.Constructor = (x) => { };

6. Ahora, añadamos la implementación para la operación RetrieveCurrentBlock a través de la clase

ShimRoadworkServiceClient. Este método devuelve un array de Block; usaremos nuestra propia

implementación para setear nuestra variable local hasServiceBeenInvoked a true y devolveremos un Stub.

Justo después del código que hemos añadido en el paso 4, añade el siguiente código:

ShimRoadworkServiceClient.AllInstances.RetrieveCurrentBlockArray =

(instance, blocks) =>

{

hasServiceBeenInvoked = true;

return new List<StubImpediment>

{

new StubImpediment

{

description = string.Empty, location = blocks.FirstOrDefault(),

relativeSpeed = double.MinValue

}

}.ToArray();

};

7. Añade 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 código 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 77: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Capítulo 6: Hands-on Lab

Page 77 of 82

}

}

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 frágil ya que sigue necesitando la llamada al

método Thread.Sleep para que le dé tiempo a la propiedad Run a inicializar la clase Timer.

Paso 5 – Añadir 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 inicialización de la propiedad Run. Para ello, eliminaremos la

dependencia del constructor Timer ofreciendo una alternativa a través de la clase ShimTimer.

1. Expande el nodo References del proyecto Traffic.Core.Tests, clic derecho en System y selecciona la

opción Add Fakes Assembly

Fíjate 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 también 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 añadido –

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 corrígelo para que sea así:

3. En la clase CityTests.cs, añade un using a System.Threading.Timer.Fakes

Ahora vamos a modificar el test para reemplazar la llamada al constructor de Timer con nuestra propia

implementación. El constructor de Timer usado es uno que recibe algunos parámetros 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 implementación.

4. Renombra la variable local hasServiceBeenInvoked a hastimerBeenInvoked y asegúrate de que la referencia

de esta variable también ha sido renombrada. Borra el código que configura el shim del constructor de

RoadworkServiceClient y las llamadas a RetrieveCurrentBlockArray. Ahora, añade la implementación para

el constructor del Timer que tiene cuatro parámetros – un callback, un object, y dos TimeSpan. En esta

implementación pon la variable hasTimerBeenInvoked a true. El código debería 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 debería 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 78: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Capítulo 6: Hands-on Lab

Page 78 of 82

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 debería 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. Fíjate que el constructor recibe dos

parámetros – una instancia de Traffic.Core.Algorithms.RoutingAlgorithm y una implementación de

System.Windows.Media.Bursh. Ambos parámetros se usan para inicializar el estado de la instancia de Car

setenado el estado de las propiedades Car.VehicleColor y Car.Routing. Además, también se inicializa una

propiedad pública 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. Añade una nueva clase de test llamada CarTests.cs al proyecto Traffic.Core.Tests

2. Añade 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 método de test, añade las siguientes líneas de código:

var expectedAlgorithm = new StubRoutingAlgorithm(); var expectedColor = Brushes.Aqua;

5. Ahora, crea la instancia del Car usando las dos variables locales como parámetros de entrada del

constructor:

Car codeUnderTest = new Car(expectedAlgorithm, expectedColor);

Page 79: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Capítulo 6: Hands-on Lab

Page 79 of 82

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 – Añade 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 lógica del bloque actual

depende de una llamada al método Location.Road.IsFree si devuelve true. Esto hace una llamada al método

DiscoveredRoutes.ToRoutePart e interactúa con la propiedad local System.Random. Continuando con el

ejercicio de testear este código, produciremos algunos test unitarios simples para comprobar el estado de esta

propiedad. Le primer test que haremos será testear la condición cuando la propiedad Location sea null.

1. Añade un nuevo test llamado Car_ShouldMoveProperty_ReturnsFalseIFLocationIsNull a la clase

CarTests.cs. Debe ser decorado con el atributo TestMethod.

2. En el cuerpo del método, repite los puntos 4 y 5 del Paso 6 para obtener los datos para el test.

3. Ahora setea la propiedad Location de la variable codeUnderTest a null.

4. Ahora comprueba que la propiedad codeunderTest.ShouldMove es false. Añade un mensaje de error para

indicar al desarrollador qué hacer si el valor de la propiedad no es válido.

El test unitario completo será:

/// <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 siguiente test unitario que haremos será testear el getter de ShouldMove, cuando la propiedad

Loation.Road sea null

5. Añade un nuevo método de test llamado

Car_ShouldMoveProperty_ReturnsFalseIfLocationRoadIsNull y repite los puntos 4 y 5 del Paso 6.

6. Usa una instancia del tipo StubElementLocation con la propiedad Road a null. Asígnala a la propiedad

codeUnderTest.Location de la siguiente manera:

codeUnderTest.Location = new StubElementLocation { Road = null };

7. Ahora añade un assert para comprobar que la propiedad codeUnderTest.ShouldMove es false. De nuevo,

añade un mensaje de error adecuado.

Page 80: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Capítulo 6: Hands-on Lab

Page 80 of 82

El código 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 método Location.Result.IsFree. Como la implementación 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. Añade un nuevo test llamado

Car_ShouldMoveProperty_ReturnsFalseIfLocationRoadsIsFreeReturnsFalse a la clase CarTests.cs

9. En el cuerpo del método, crea las variables locales stubAlgorithm y testBrush como en el test unitario

anterior.

10. Añade una sentencia using ShimsContext.Create() para aislar las llamadas de la clase Block.

11. Usando la clase ShimBlock, ofrece una implementación para asegurarnos de que cualquier llamada al

método IsFree devolverá false. Aquí está el código:

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, añade un assert para asegurarnos de que la propiedad codeUnderTest.ShouldMove devuelve

false.

El código 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 81: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Capítulo 6: Hands-on Lab

Page 81 of 82

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 método 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 método testeado, deberíamos hacer un Shim de la clase DiscoveredRoutes. Sin embargo,

viendo esta clase, veremos que es internal. Para este ejercicio, hemos decidido que el código bajo test es inmutable;

es decir, que no podemos añadir un atributo InternalsVisibleTo para el assembly Traffic.Core. ¿Esto significa que

los intentos de tener un código testeado no han valido para nada? No necesariamente. Hemos ampliado la cobertura

de nuestro código desde cero hasta algo más, esto añade valor a nuestro sistema.

En este punto podemos optar por tener un cierto nivel de test de integración para mitigar aquellas áreas en las

que el sistema no puede ser cubierto por tests unitarios.

REV

ISIÓ

N

En este ejercicio, hemos visto cómo podemos empezar a tener un sistema complejo testeado incrementando la

cobertura de código

Page 82: Testing Unitario Con Microsoft Fakes

Testing Unitario con Microsoft Fakes - Conclusión

Page 82 of 82

Conclusión

Aquí concluye nuestra aventura de testing unitario con Microsoft Fakes. Hemos tocado la teoría, introducido los

Shims y Stubs, y esperamos haber enseñado cuándo usarlos. También hemos visto varios ejercicios en los

Hands-on Lab, siguiendo varios escenarios, y hemos visto varias técnicas avanzadas.

Estamos al principio del ciclo de vida de Microsoft Fakes con más actualizaciones que vendrán en el futuro en

próximas versiones de Visual Studio. Esperamos que encuentres útil esta tecnología y esta guía.

Atentamente,