Upload
kharonte24
View
110
Download
5
Embed Size (px)
Citation preview
Testing Unitario con Microsoft Fakes - Prólogo
Page 1 of 82
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.
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
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
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
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
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
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
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.
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
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:
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
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!
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:
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.
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:
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
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.
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()
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”.
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();
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);
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) =>
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;
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
};
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.
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.
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 };
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.
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>
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
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
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);
}
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);
}
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.
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
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
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" />
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
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:
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:
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
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
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 =>
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;
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);
}
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
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)
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.
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
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;
};
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
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.
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)
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.
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; }
}
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
{
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.
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”.
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>
{
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();
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.
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);
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á.
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
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
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;
}
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
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
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
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.
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
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
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;
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.
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");
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()
{
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);
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.
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()
};
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
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,