Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
Page 1 of 82
The information contained in this document represents the current view of Microsoft Corporation on the issues discussed as of the date of publication. Because Microsoft must respond to changing market conditions, it should not be interpreted to be a commitment on the part of Microsoft, and Microsoft cannot guarantee the accuracy of any information presented after the date of publication. This document is for informational purposes only. MICROSOFT MAKES NO WARRANTIES, EXPRESS, IMPLIED OR STATUTORY, AS TO THE INFORMATION IN THIS DOCUMENT. Microsoft grants you a license to this document under the terms of the Creative Commons Attribution 3.0 License. All other rights are reserved. 2013 Microsoft Corporation. Microsoft, Active Directory, Excel, Internet Explorer, SQL Server, Visual Studio, and Windows are trademarks of the Microsoft group of companies. All other trademarks are property of their respective owners.
Page 2 of 82
ndice
Prlogo........................................................................................................................................................................................................................................6 Introduccin ..............................................................................................................................................................................................................................7
Audiencia ............................................................................................................................................................................................. 7 Visual Studio ALM Rangers .......................................................................................................................................................... 7 Autores ................................................................................................................................................................................................. 8 Uso del cdigo fuente, erratas y soporte ............................................................................................................................... 8
Captulo 1: Breve teora sobre Testing Unitario .........................................................................................................................................................9
Testing de software .............................................................................................................................................................................. 9 Estrategias de testing ..................................................................................................................................................................... 9 Tipos de tests ..................................................................................................................................................................................... 9 La delgada lnea entre buen y mal test unitario ..................................................................................................................... 11 Porqu son importantes los test unitarios ........................................................................................................................... 11 Define un paradigma de desarrollo guiado por tests unitarios................................................................................... 11 Checklist de un test unitario ...................................................................................................................................................... 13
Captulo 2: Introduccin a Microsoft Fakes .............................................................................................................................................................. 14
Stubs ......................................................................................................................................................................................................... 14 Qu testeamos? ............................................................................................................................................................................ 14 Por qu usar Stubs? ..................................................................................................................................................................... 15 Shims ........................................................................................................................................................................................................ 16 Elegir entre un stub o un shim ....................................................................................................................................................... 18
Captulo 3: Migrando a Microsoft Fakes .................................................................................................................................................................... 19
Migrando de Moles a Microsoft Fakes ....................................................................................................................................... 19 Cambiando referencias ................................................................................................................................................................ 19 La forma de hacer fakes ............................................................................................................................................................... 19 Definiendo comportamientos ................................................................................................................................................... 20 Moles y Microsoft SharePoint ................................................................................................................................................... 20 Migrando de frameworks comerciales y open source ......................................................................................................... 20 Introduccin ..................................................................................................................................................................................... 20 Diferencias entre estos productos y Microsoft Fakes ...................................................................................................... 21 Migrando desde Moq ................................................................................................................................................................... 22 Migrando desde RhinoMocks ................................................................................................................................................... 32 Conclusin ......................................................................................................................................................................................... 35
Captulo 4: FAQ ..................................................................................................................................................................................................................... 36
Tratando con servicios Windows Communication Foundation (WCF) ........................................................................... 47 Rompiendo la dependencia ....................................................................................................................................................... 47 Mejorando la situacin ................................................................................................................................................................ 48 Tratando con clculos no deterministas .................................................................................................................................... 49 Operaciones basadas en Timer ................................................................................................................................................. 49 Datos no repetibles ....................................................................................................................................................................... 49 Recopilacin de casos de uso y ms informacin analtica. ............................................................................................... 50 Validar detalles de implementacin ....................................................................................................................................... 50 Analizando el estado interno .......................................................................................................................................................... 51 Evitando la duplicacin de estructuras de testing ................................................................................................................. 52
Captulo 6: Hands-on Lab................................................................................................................................................................................................. 53
Ejercicio 1: Usando Stubs para aislarnos del acceso a la base de datos (20 30 min) ........................................... 53 Dependencias del entorno ......................................................................................................................................................... 53 Patrn de implementacin ......................................................................................................................................................... 53 Pasos a realizar ................................................................................................................................................................................ 54 Paso 1 Revisar la solucin de inicio ..................................................................................................................................... 54 Paso 2 Prepara el proyecto de test para Microsoft Fakes Stubs .............................................................................. 55 Paso 3 Aade el assembly de Fake al proyecto de test............................................................................................... 55 Paso 4 Revisa y actualiza el archivo xml de Fakes ......................................................................................................... 56 Paso 5 Revisa las clases del modelo y del controlador del MainWeb ................................................................... 56 Paso 6 Crear un mtodo de test unitario .......................................................................................................................... 57
Page 4 of 82
Page 5 of 82
Prlogo
En equipos de desarrollo modernos, el valor de un testeo unitario efectivo y eficiente es algo en lo que estn todos de acuerdo. Tests rpidos, seguros y automticos que permiten a los desarrolladores comprobar que su cdigo hace lo que ellos piensan que debe hacer, incrementan significativamente la calidad general del cdigo. Sin embargo, crear test unitarios buenos y efectivos es ms difcil de lo que parece. Un buen test unitario es como un buen experimento cientfico: asla tantas variables como sea posible (llamadas variables de control) y valida o rechaza una hiptesis sobre lo que ocurre cuando una variable (la independiente) cambia. Para poder crear cdigo con este tipo de aislamiento hay que prestar especial atencin al diseo y a los patrones que usan los desarrolladores. En algunos casos, el cdigo est diseado de manera que aislar un componente de otro es fcil. Sin embargo, en la mayora de los casos, conseguir este nivel de aislamiento es muy difcil. De hecho, es tan difcil que para algunos desarrolladores es algo imposible. Incluido por primera vez en Visual Studio 2012, Microsoft Fakes nos ayuda a mitigar esta dificultad. Hace ms fcil y rpido crear test unitarios bien aislados cuando ya tenemos sistemas que son testables, permite centrarnos en escribir los test adecuados sin escribir test acoplados. Tambin nos permite aislar y testear cdigo que tradicionalmente no es fcil de testear, usando la tecnologa conocida como Shims. Shims permite eliminar las dependencias complejas y reemplazarlas por algo que podemos controlar. Como ya hemos comentado, ser capaz de crear estas variables de control es muy importante cuando creamos test unitarios rpidos y de calidad. Shims ofrece una forma de evitar muchas de las dificultades que nos encontramos cuando queremos hacer tests unitarios de nuestro cdigo. Como con todas las herramientas de este tipo, hay algunos patrones, tcnicas y conceptos que pueden llevar tiempo aprender. Este documento no es ms que un empujn para adquirir ese conocimiento compartiendo numerosos ejemplos y tcnicas que permitan usar de manera adecuada Microsoft Fakes en vuestros proyectos. Nos alegra escribir el prlogo de esta gua producida por los Visual Studio ALM Rangers. Estamos seguros de que os ayudar a entender el poder y las capacidades que Microsoft Fakes ofrece a la hora de crear mejores test unitarios y mejor cdigo. Peter Provost- Program Manager Lead, Visual Studio ALM Tools Joshua Weber Program Manager, Visual Studio ALM Tools
Page 6 of 82
Introduccin
Bienvenidos a Mejor Unit Testing con Microsoft Fakes 1 en el que, los ALM Rangers, os acompaaremos en un viaje fascinante para descubrir una nueva, excitante y poderosa caracterstica introducida en Visual Studio 2012.
NOTE
Esta gua se basa en Visual Studio 2012 Update 1
Audiencia
Developers! Developers! Developers! Esperamos que nuestra audiencia sean mayoritariamente desarrolladores. Veremos algunos conceptos bsicos sobre tests unitarios pero esperamos que la mayora de nuestros lectores ya tengan alguna experiencia en escribir tests unitarios. Sin duda, cierta experiencia previa con algn otro framework de mocking sera muy positiva. Sin embargo, si ests valorando adoptar Microsoft Fakes como tu primera solucin de mocking, creemos que esta gua te ayudar a implementar una solucin de mocking con Microsoft Fakes de manera adecuada. Si es la primera vez que oyes trminos como test unitarios y mocking, esta gua tambin es muy buena para introducirte en estos conceptos que todos los desarrolladores necesitan en su caja de herramientas.
Qu necesitas?
Las siguientes ediciones de Visual Studio soportan Microsoft Fakes y son las que se han usado en esta gua: Visual Studio Ultimate 2012 Visual Studio Premium 2012 (Es necesario Visual Studio 2012 Update 2)
Para escribir test unitarios con Microsoft Fakes necesitars una edicin soportada de Visual Studio. La ejecucin de estos test en un servidor de builds tambin requiere una edicin soportada. Es posible ejecutar los test con Team Foundation Server 2010 y quizs con versiones anteriores. Sin embargo, para una buena experiencia, recomendamos usar Team Foundation Server 2012. Los test de Microsoft Fakes se pueden compartir y ensear a otros miembros del equipo que no estn usando una edicin soportada de Visual Studio, pero no podrn ejecutarlos.
1 2
http://msdn.microsoft.com/en-us/library/hh549175.aspx http://blogs.msdn.com/b/willy-peter_schaub/archive/2012/06/22/introducing-the-visual-studio-alm-rangers.aspx
Page 7 of 82
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
Expresiones Lambda
Una expresin lambda es una forma concisa de escribir funciones annimas que podemos usar para crear delegados o expresiones arbreas. Con las expresiones lambda, podemos escribir funciones locales que se pueden pasar como parmetros o que se pueden devolver como valor de retorno de una funcin. Haremos un uso extenso de estas expresiones en el cdigo de ejemplo. Si eres nuevo en el tema de las expresiones lambda, te recomendamos que leas la seccin de MSDN dedicada a ello Lambda Expresions (C# Programming Guide) 5
3 4 5
Page 8 of 82
Testing Unitario con Microsoft Fakes - Captulo 1: Breve teora sobre Testing Unitario
Testing de software
El testing de software es el arte de medir y mantener la calidad del software para asegurar que las expectativas y requerimientos del usuario, el valor de negocio, los requisitos no funcionales como la seguridad, confiabilidad y tolerancia a fallos, y las polticas operacionales se cumplan. El testing es un esfuerzo del equipo para conseguir un mnimo de calidad y tener una definicin de hecho entendida y aceptada por todos.
Definicin de hecho es una definicin del equipo y un compromiso de calidad que se aplicar a la solucin en cada iteracin (tambin se puede definir en el nivel de Tareas o de Historias de Usuario). Ten en cuenta el diseo, las revisiones de cdigo, refactorizacin y testing cuando discutis y definis vuestra definicin de hecho.
Estrategias de testing
Las estrategias de testing se dividen tradicionalmente en testing de caja negra, blanca y gris.
Estrategia Caja Negra Caja Blanca Caja Gris Descripcin El contenido de la caja (la implementacin de la solucin) es oscura. Los testers slo se centran en la entrada y en la salida, normalmente cuando se realizan test de sistema o de aceptacin de usuario. El contenido de la caja es visible y se puede analizar como parte del testing. Es una combinacin de caja blanca y negra que se usa normalmente en casos especiales, en los que se requiere una comprensin de cmo funciona por dentro y cmo debe comportarse.
Tipos de tests
No hay una bala de plata cuando hablamos de testing (ver Figura 2). Hay veces que se necesita de interaccin humana y feedback, otras es necesario automatizar la ejecucin de estos test y otras hay que ejecutarlos manualmente.
Page 9 of 82
NOTA
Testing Unitario con Microsoft Fakes - Captulo 1: Breve teora sobre Testing Unitario
Descripcin El tester imagina posibles escenarios que no se hayan cubierto por otros test. Es muy til cuando se observa al usuario usando el sistema. NO hay test predefinidos Testear diferentes componentes de la solucin trabajando como si fueran uno Testear cmo se comporta el sistema con cargas de trabajo en un entorno controlado Asegura que el sistema mantiene los mnimos de calidad despus de hacer cambios como corregir bugs. Usa una mezcla de tests unitarios y de sistema Se usa para testear una nueva caracterstica o idea antes de subir al repositorio de cdigo los cambios Testea el sistema completo con caractersticas fijas y comprueba los requerimientos no funcionales Un test de la unidad ms pequea de cdigo (mtodo, clase, etc.) que puede ser testeada de manera aislada del sistema. Ver Verifying Code By Using Unit Test y La delgada lnea entre buen y mal test unitario en esta gua para ms informacin Cuando se acerca el final de los ciclos del producto, se invita a los usuarios a que realicen test de aceptacin en escenarios reales, normalmente basados en casos de tests
Herramienta de Visual Studio Testing exploratorio con Microsoft Test Manager (MTM) Visual Studio Unit Test Visual Studio Load Test Agent Testing automtico con MTM
Visual Studio Lab Management Visual Studio Test Explorer. Frameworks de test unitarios
Page 10 of 82
Testing Unitario con Microsoft Fakes - Captulo 1: Breve teora sobre Testing Unitario
AVISO
Cdigo de calidad desde el principio Menos bugs Cdigo auto explicativo Reducir el coste de corregir errores encontrndolos lo antes posible Sentido de responsabilidad del cdigo, estar orgullosos de nuestro cdigo
El valor principal del desarrollo guiado por test y de los test unitarios es asegurarse de que el equipo piensa e incluso suea con el cdigo, estudia los requerimientos, y evitan posibles problemas proactivamente. Automatiza tus tests: Plantate automatizar tus test. Esto te permitir testear rpida y automticamente cambios de cdigo incluso a medida que se escribe. Sin embargo, la automatizacin de los tests tiene sus desafos y te recomendamos que investigues sobre el tema (http://blogs.msdn.com/b/steverowe/archive/2008/02/26/when-to-test-manually-andwhen-to-automate.aspx)
Page 11 of 82
Testing Unitario con Microsoft Fakes - Captulo 1: Breve teora sobre Testing Unitario
1.
2.
3.
Escribe el cdigo del test (Stub) para que compile (pase de RED a GEEN) a. Inicialmente la compilacin fallar RED debido a que falta cdigo b. Implementa slo el cdigo necesario para que compile GREEN (an no hay implementacin real). Escribe el cdigo del test para que se ejecute (pase de RED a GREEN) a. Inicialmente el test fallar RED ya que no existe funcionalidad. b. Implementa la funcionalidad que va a probar el test hasta que se ejecute adecuadamente GREEN. Refactoriza el test y el cdigo una vez que este todo GREEN y la solucin vaya evolucionando.
Echad un vistazo a Guidelines for Test-Driven Development(http://msdn.microsoft.com/enus/library/aa730844(v=vs.80).aspx) y Testing for Countinous Delivery with Visual Studio (http://msdn.microsoft.com/en-us/library/jj159345.aspx) para conocer ms buenas prcticas a la hora de escribir tests unitarios
NOTA
Page 12 of 82
Testing Unitario con Microsoft Fakes - Captulo 1: Breve teora sobre Testing Unitario
Los test unitarios prueban y validan caractersticas de negocio. El propio cdigo es una documentacin viva del sistema. Recomendamos el uso combinado de cdigo conciso y documentacin mnima, para conseguir un test unitario entendible, mantenible y autodescriptivo. Usa mensajes descriptivos para mejorar la lectura del cdigo y el log de compilacin. Ejemplos: Content submission failed due to too many spelling errors. La interfaz define el contrato, permitiendo el uso de stubs y tests de caja negra. El test unitario debe ser tan simple, limpio y conciso como sea posible. Los tests simples son un valor aadido, si no, se ignorarn y no se mantendrn Un test unitario est centrado en la mnima unidad de cdigo (mtodo, clase, etc.) que puede ser probada de manera aislada, no a nivel de sistema o de integracin. Para mantener un test y su infraestructura asociada centrada, evita probar cosas entre los diferentes niveles de aplicacin o de sistema. Cada test debe tener slo una asercin lgica. Debe validar el test, como se indica en su nombre. Una asercin lgica puede contener una o varias sentencias de assert. El cdigo debe ser organizado y mantenido como si fuese una clase de primer nivel, al igual que el resto de la solucin. Su cdigo debe basarse en los mismos requisitos de buenas prcticas, calidad y estilo de cdigo de la compaa. El test unitario debe cubrir todos los posibles escenarios y busca siempre una amplia cobertura de cdigo. Testear los casos lmites y las excepciones es responsabilidad del testador, no del usuario final!
Errores y aserciones descriptivas Adopta el desarrollo contra interfaces Mantenlo simple Mantenlo centrado
Page 13 of 82
Como vemos en la figura, los stubs son usados normalmente para llamadas en nuestro sistema que podemos desacoplar usando interfaces; los shims se usan para llamadas a assemblies que no estn bajo nuestro control:
Stubs
Vamos a ver ms detenidamente a los Stubs para poder empezar a integrarlo en nuestro ciclo de vida.
Qu testeamos?
En la siguiente figura, tenemos un ejemplo tpico de una aplicacin en N-capas. La complejidad de cualquier solucin depende del nivel de caractersticas que dicha solucin ofrece, y para nuestro ejemplo, lo vamos a simplificar. Vamos a centrarnos en el aislamiento dentro de lo razonable de los componentes que forman la base de nuestro sistema:
Page 14 of 82
Para el aislamiento, vamos a los componentes individuales que se ejecutan en la capa de lgica de negocio (puede que tu terminologa vare). Nuestra estrategia de testeo no es testear la lgica en la base de datos, ni la lgica de los servicios WCF/Rest, ni el navegador, etc.
Test dobles
Los tests dobles nos permiten aislar el cdigo bajo test de sus dependencias. Juegan un papel muy importante en la evolucin de la calidad de nuestro cdigo y aumenta su testabilidad. Los stubs, un tipo de test doble, requieren que el cdigo que se va a probar est diseado de manera que permita separar y desacoplar dependencias. Es importante diferenciar qu es un Stub de qu no lo es. El artculo de Martin Fowler Moks arent Stubs (http://martinfowler.com/articles/mocksArentStubs.html) compara y contrasta los principios que hay debajo de los stubs y los mocks. Como se entiende del artculo, un stub conserva un estado esttico que permite la verificacin de estados (http://xunitpatterns.com/State%20Verification.html ) del sistema en prueba, mientras que un mock ofrece una verificacin de comportamiento (http://xunitpatterns.com/Behavior%20Verification.html) del sistema en prueba. En esta seccin, nos centraremos en los stubs y veremos el valor que aportan a nuestro proceso de desarrollo. Podis leer el artculo de Martin Fowler para conocer ms detalles de los principios y contrastar las dos aproximaciones. Con stubs, podemos aislar el cdigo que se quiere probar junto a unos casos de pruebas que validarn nuestro cdigo de negocio esto es, objetos con un estado especfico que podemos reproducir bajo unas condiciones que podemos controlar y ejecutar. Es importante saber que Microsoft Fakes no ofrece herramientas de verificacin de estado como otros frameworks de mocking como NMoq o Rhino Mocks.
Page 15 of 82
Testing Unitario con Microsoft Fakes - Captulo 2: Introduccin a Microsoft Fakes Aumentando la cobertura de cdigo
Con stubs identificamos condiciones especficas a nivel de unidad e incorporamos este estado en un test o conjunto de test simples. Incrementando as la cobertura de nuestro cdigo. Los problemas y los errores son detectados con los test de integracin, errores de produccin, etc. Estas reas son partes del cdigo que no estn cubiertas por los test unitarios necesarios. En este punto, aadir stubs que simulen estas condiciones o reas de cdigo, ayuda tanto a la cobertura como a la calidad de la cobertura del sistema en test. Adems, estos stubs se convierten en parte de los test unitarios, con la idea de as no volver a pasar. Durante el desarrollo guiado por pruebas (TDD), los problemas se suelen detectar muy pronto, cuando se escriben los primeros tests (test de sistema o aceptacin, o incluso de produccin). Los stubs hacen ms fcil crear test unitarios que aseguren la cobertura de ciertas condiciones aislndolas de sus dependencias. Adems, una buena aproximacin TDD es no escribir el cambio en el cdigo funcional inmediatamente. En lugar de ello, primero se escribe un test unitario que sea capaz de reproducir el estado de los componentes aislados. Primero se incluye dicho test que representa las condiciones de error. El test falla, y luego es el desarrollador, no el tester, el que corrige el defecto en el cdigo funcional que hace que el test pase.
Aislamiento y granularidad
Como vimos en la seccin Qu testeamos?, el aislamiento nos permite centrarnos en una parte del sistema que se est probando sin ninguna dependencia externa. En el diagrama anterior, esto eran los componentes de la capa de la Lgica de Negocio (BLL). El aislamiento reduce la cantidad de cdigo en el que el test se tiene que centrar. Esto reduce, en muchos casos, el cdigo necesario para configurar el test tambin. Son necesarios tambin componentes o entornos aislados si queremos desacoplarnos de estados fsicos y condiciones especiales para reproducir el error, por ejemplo, una base de datos con datos o un archivo con datos de ejemplo.
Shims
Los Shims son una caracterstica de Microsoft Fakes que permiten crear tests unitarios para cdigo que de otra manera no puede ser probado de manera aislada. Al contrario que los Stubs, los Shims no requieren que el cdigo a testear sea diseado de ninguna manera. Para poder usar Stubs, tienen que ser inyectados* en el cdigo que se prueba de alguna manera, as las llamadas a las dependencias no las manejaran componentes reales (cdigo de produccin), sino que ser el Stub. De esta manera, los valores y objetos de test se les pueden pasar al cdigo que se est probando:
Page 16 of 82
Pero hay ocasiones en las que el cdigo que se quiere probar no est diseado de manera que permita cambiar sus dependencias, por ejemplo, cuando se llaman a mtodos estticos o cuando se basa en frameworks de terceros. En estos casos, los Shims ofrecen la posibilidad de reemplazar las dependencias interceptando las llamadas a las dependencias en tiempo de ejecucin desvindolas a un cdigo especial con los valores de prueba deseados para el test.
*La tcnica de Inyeccin de Dependencias (DI) se usa en la programacin orientada a objetos para desacoplar clases de sus dependencias o al menos de la implementacin concreta a travs de interfaces. Esta tcnica se puede usar para inyectar stubs para motivos de testing. Esta inyeccin se puede realizar a travs de frameworks (como Spring.NET o Unity) o manualmente inyectando implementaciones concretas con clases. Por ejemplo, creando una instancia de la clase dependiente y pasarla como parmetro en el constructor de la clase que queremos testar (Inyeccin por Constructor). Para que la inyeccin por constructor funcione, el componente dependiente debe tener un constructor apropiado. Para una clase que oculte completamente sus dependencias, DI no funcionar y no se podrn inyectar stubs tampoco
NOTA
Page 17 of 82
Objetivo | Consideracin Buscas el mejor rendimiento? Mtodos abstractos y virtuales Interfaces Tipos internos Mtodos estticos Tipos sellados Mtodos privados
Stub X X X X
X X X x
Leed Isolating Code under Test with Microsoft Fakes(http://msdn.microsoft.com/en-us/library/hh549175.aspx) en MSDN para ms informacin.
Page 18 of 82
Cambiando referencias
El primer paso en cualquier migracin de Moles a Fakes es eliminar las referencias a los assemblies y borrar los archivos .moles. El siguiente paso es generar un nuevo assembly de Fake. Esto aadir las referencias necesarias y los archivos .fakes al proyecto de test.
Definiendo comportamientos
En los test, la forma bsica en la que se definen los comportamientos de los Stubs o Shims no ha cambiado entre Moles y Fakes. Sin embargo, s ha cambiado el nombre de las clases que los definen. Por ejemplo, en Moles, el comportamiento de una llamada a la propiedad Now de DateTime se declarara as:
MDateTime.NowGet = () => { return new DateTime(1, 1, 1); };
Como slo es un cambio de namespaces, bastara con una operacin de reemplazar con cualquier editor de texto
Page 20 of 82
Typemock isolator Ahora vamos a ver cmo se mockea la misma llamada a DateTime.Now usando Typemock Isolator
[TestMethod, Isolated] public void DateTimeTest() { // Arrange Isolate.WhenCalled(() => DateTime.Now).WillReturn(new DateTime(2016, 2, 29)); // Act int result = MyCode.DoSomethingSpecialOnALeapYear();
Page 21 of 82
Microsoft Fakes Ahora vamos a ver cmo se mockea esa misma llamada con Microsoft Fakes
[TestMethod] public void DateTimeTes() { using (ShimsContext.Create()) { // Arrange: System.Fakes.ShimDateTime.NowGet = () => { return new DateTime(2016, 2, 29); }; // Act int result = MyCode.DoSomethingSpecialOnALeapYear(); // Assert Assert.AreEqual(100, result); } }
Cdigo de ejemplo
En el resto de la seccin, haremos referencia a un cdigo de ejemplo. Usaremos una clase muy simple de cuenta de banco como la que vemos en el siguiente trozo de cdigo
public enum TransactionType { Credit, Debit }; public interface ITransaction { decimal Amount { get; } TransactionType TransactionType { get; } } public interface ITransactionManager { int GetAccountTransactionCount(DateTime date); ITransaction GetTransaction(DateTime date, int transactionNumber);
Page 22 of 82
La clase Stub que crea Fakes ofrece un miembro llamado GetAccountTransactionCountDateTime, que podemos asignar a una expresin Lambda que nos devolver el valor que queremos. Fijaos que esta expresin lambda comprueba el parmetro de entrada de la misma manera que hara Moq. Si se le pasa un valor diferente al indicado, devolver el valor por defecto del tipo. Moq tambin nos permite llamar al mtodo Setup varias veces para devolver valores diferentes para diferentes entradas. Aqu tenis un ejemplo:
// txn1 and txn2 previously set up as mock transactions mockTM.Setup(tm => tm.GetTransaction(testDate, 0)).Returns(txn1.Object); mockTM.Setup(tm => tm.GetTransaction(testDate, 1)).Returns(txn2.Object);
Esto lo podemos conseguir con una expresin lambda algo ms compleja como la siguiente:
// txn1 and txn2 previously set up as stub transactions ITransaction[] stubTransactions = new ITransaction[] { txn1, txn2 }; stubTM.GetTransactionDateTimeInt32 = (date, index) => (index >= 0 || index < stubTransactions.Le ngth) ? stubTransactions[index] : null;
Estamos usando un array para poder corresponder los valores de entrada, de manera que la expresin lambda las busca. En este caso estamos ignorando el parmetro de fecha. Algunas veces, los valores que queremos no se conocen con antelacin y usar una coleccin nos permite inicializar el diccionario dinmicamente, incluso despus de que el stub se halla pasado al cdigo que queremos testear. Un escenario en el que necesitamos hacer esto es cuando queremos centralizar la creacin del cdigo bajo test en un cdigo de inicializacin, pero cada test se ejecuta con diferentes conjuntos de valores. Podramos hacer algo como esto en Stubs:
private StubITransactionManager stubTM = new StubITransactionManager(); private List<ITransaction> transactions = new List<ITransaction>(); private DateTime testDate = new DateTime(2012, 1, 1); private Account cut; [TestInitialize] public void InitializeTest() { this.stubTM.GetTransactionDateTimeInt32 = (date, index) =>
Page 23 of 82
Por supuesto, Moq tambin es capaz de tratar la configuracin de mtodos con varios parmetros. Pero en Stub es un poco ms complejo crear expresiones lambda que comprueban cada uno de los parmetros. Cuando tenemos varios parmetros de entrada en varios Setups, lo ms simple suele ser usar un diccionario que usa como key un Tuple<T,R>. Aqu tenis un ejemplo:
private StubITransactionManager stubTM = new StubITransactionManager(); private Dictionary<Tuple<DateTime, int>, ITransaction> transactions = new Dictionary<Tuple<DateT ime, int>, ITransaction>(); private DateTime testDate = new DateTime(2012, 1, 1); private Account cut;
Page 24 of 82
Page 25 of 82
Moq tambin nos ofrece varias formas de enlazar varios valores de entrada en una sola llamada al Setup. Por ejemplo:
mockTM.Setup(tm => tm.GetTransaction(It.IsAny<DateTime>(), It.IsAny<int>())) .Returns(txn.Object);
En este caso, un Stub podra simplemente ignorar el parmetro apropiado en la lambda. En el caso anterior podra ser algo as:
stubTM.GetTransactionDateTimeInt32 = (date, index) => txn1;
En Moq, usaremos el mtodo .Setup como en el ejemplo anterior. Sin embargo, en lugar de la llamada al .Returns llamaremos al .Callback para indicar cul es el mtodo que se tiene que ejecutar, pasando los parmetros que hagan falta de manera similar a como lo haramos en el mtodo Returns:
[TestMethod] public void MoqCallback() { // arrange Mock<ITransactionManager> mockTM = new Mock<ITransactionManager>(); mockTM.Setup(tm => tm.PostTransaction(It.IsAny<decimal>(), It.IsAny<TransactionType>())) .Callback<decimal, TransactionType> ((amount, transType) => CallBackMethod(amount)); Account cut = new Account(mockTM.Object); // act cut.AddCredit(9.99m); // assert Assert.AreEqual(true, callBackCalled); }
Usando Stubs en Microsoft Fakes esto necesita un poco ms de cdigo en la expresin lambda para grabar las llamadas:
[TestMethod] public void StubAccountOpenPostsInitialBalanceCreditTransaction() { // Arrange int callCount = 0; StubITransactionManager stubTM = new StubITransactionManager { PostTransactionDecimalTransactionType = (amount, type) => { if (amount == 10m && type == TransactionType.Credit) { callCount++; } } }; Account cut = new Account(stubTM); // Act cut.Open(10m); // Assert Assert.AreEqual<int>(1, callCount); }
Con Moq, el desarrollador tiene que llamar al mtodo de verificacin adecuado, dependiendo del elemento que quiera verificar: .Verify para los mtodos .VerifyGet para los Get de las propiedades. .VerifySet para los Set de las propiedades.
Como Stubs no ofrece mtodos de verificacin, tendremos que crearnos los nuestros, de manera que no hay diferencia entre verificaciones de mtodos o propiedades; es todo cdigo personalizado. Obviamente, algunas veces hacen falta verificaciones ms complejas. Por ejemplo, pueden ser necesarias diferentes combinaciones de parmetros. Esto se puede realizar con la tcnica del Diccionario de Tuplas, similar al que vimos en la seccin de Setup, para contar el nmero de llamadas por cada combinacin de parmetros.
Mientras que en Moq tenemos que usar una expresin lambda para pasar los parmetros:
[TestMethod] public void MoqRaiseEvent() { // arrange bool delegateCalled = false; DateTime testDate = new DateTime(2012, 1, 1); Mock<ITransactionManager> mockTM = new Mock<ITransactionManager>(); mockTM.Object.TransactionEvent += delegate { delegateCalled = true; }; // act // Raise passing the custom arguments expected by the event delegate mockTM.Raise(tm => tm.TransactionEvent += null, testDate, 9.99m, TransactionType.Credit); // assert Assert.AreEqual(true, delegateCalled); }
Fakes recursivos
Cuando tenemos un rbol de objetos complejo que tiene que ser fakeado, se tarda mucho tiempo en setear todas las propiedades y suele ser innecesario. En muchos casos lo nico necesario es que el objeto que se ha fakeado no lance excepciones del tipo NullReferenceException. Moq ofrece una manera de conseguir que esto no ocurra, seteando todas las referencias/propiedades en el rbol de objetos. Esto se hace usando las opciones de DefaultValue.Mock para los objetos mockeados y el mtodo SetupAllProperties para las propiedades. De esta manera, el test no lanzar ningn NullReferenceException. Se devolver el valor por defecto de cualquier objeto que haya que devolver. Por ejemplo, los enteros devolvern un 0, y los strings devolvern un String.Empty. Si hace falta algn otro valor, tendremos que indicarlo de manera explcita:
Mock<ITransaction> mockTr = new Mock<ITransaction>() { DefaultValue = DefaultValue.Mock };
Page 28 of 82
Con Stubs, se usa una sintaxis muy similar. Simplemente seteando la propiedad InstanceBehavior al comportamiento deseado cuando se acceda a cualquier propiedad o mtodo:
StubITransaction stubTr = new StubITransaction(); stubTr.InstanceBehavior = StubBehaviors.DefaultValue;
Ejemplo adicional
Por motivos ilustrativos, vamos a ver otro ejemplo para terminar de migrar de Moq a Microsoft Fakes. Este ejemplo est basado en el post Mocking HttpWebRequest using Microsoft Fakes, tenemos un objeto WebServiceClient que usa un HttpWebReques, que nos gustara testear:
public class WebServiceClient { /// <summary> /// Calls a web service with the given URL /// </summary> /// <param name="url">The web service's URL</param> /// <returns>True if the services responds with an OK status code (200). False Otherwise</re turns> public bool CallWebService(string url) { var request = CreateWebRequest(url); var isValid = true; try { var response = request.GetResponse() as HttpWebResponse; isValid = HttpStatusCode.OK == response.StatusCode; } catch (Exception ex) { isValid = false; } return isValid; } /// <summary> /// Creates an HttpWebRequest object /// </summary> /// <param name="url">The URL to be called.</param> /// <returns>An HttpWebRequest.</returns> private static HttpWebRequest CreateWebRequest(string url) { var request = WebRequest.Create(url) as HttpWebRequest; request.ContentType = "text/xml;charset=\"utf-8\""; request.Method = "GET"; request.Timeout = 1000; request.Credentials = CredentialCache.DefaultNetworkCredentials; return request; } }
Para usar Moq necesitamos crear un objeto CustomWebRequestCreate que implemente la interfaz IWebRequestCreate. Esto nos permite mockear el HttpWebResponse usando RegisterPrefix:
/// <summary> /// A custom implementation of IWebRequestCreate for Web Requests.
Page 29 of 82
Page 30 of 82
Con Microsoft Fakes, podemos falisificar el objeto HttpWebRequest sin tener que implementar la interfaz IWebRequestCreate. El test sera algo as:
[TestMethod] public void TestThatServiceReturnsAForbiddenStatuscode() { using (ShimsContext.Create()) { // Arrange var requestShim = new ShimHttpWebRequest(); ShimWebRequest.CreateString = (uri) => requestShim.Instance; requestShim.GetResponse = () => { return new ShimHttpWebResponse() { StatusCodeGet = () => { return HttpStatusCode.Forbidden; } }; }; var client = new WebServiceClient(); var url = "testService"; var expectedResult = false; // Act bool actualresult = client.CallWebService(url); // Assert Assert.AreEqual(expectedResult, actualresult); } }
Referencias
Para ver ms ejemplos de Moq visitad: http://code.google.com/p/moq/wiki/QuickStart
Page 31 of 82
Cdigo de ejemplo
La mayora de ejemplos que vamos a ver se basan en la siguiente interfaz y clase para testear:
public interface IDetermineTempWithWindChill { double WhatisCurrentTemp(double airTemp, double airSpeed); double CalcSpecialCurrentTemp(double airTemp, double airSpeed, double aboveSeaLevel); } public interface IClassUnderTest { double WhatIsTheTempToday(double currentAirTemp, double currentWindSpeed, double currentFeetAboveSeaLevel); } public class ClassUnderTest : IClassUnderTest { private readonly IDetermineTempWithWindChill _determineTempWithWindChill; public ClassUnderTest(IDetermineTempWithWindChill determineTempWithWindChill) { _determineTempWithWindChill = determineTempWithWindChill; } public double WhatIsTheTempToday(double currentAirTemp, double currentWindSpeed, double currentFeetAboveSeaLevel) { return currentFeetAboveSeaLevel >= 5000.0 ? _determineTempWithWindChill.WhatisCurrentTemp (currentAirTemp, currentWindSpeed) * 0.1 : _determineTempWithWindChill.WhatisCurrentTemp (currentAirTemp, currentWindSpeed); } }
Usando el stubIDetermineTempWithWindChill.InstanceBehavior = StubBehaviors.NotImplemented; si el desarrollador no define un valor de salida para el mtodo WhatIsCurrentTemp se lanzar una excepcin cuando se ejecute el test. Para la mayora de ejemplos, usaremos el mocking dinmico. Los stubs de RhinoMocks (setup y returns) son llamadas simples a la API, como vemos en el siguiente cdigo:
[TestMethod] public void TestSetupAndReturn() { //Arrange double airTemp = 35; double airSpeed = 5.0; IDetermineTempWithWindChill determineTempWithWindChill = MockRepository.GenerateStub<IDetermineTempWithWindChill>(); determineTempWithWindChill.Stub(x => x.WhatisCurrentTemp(airTemp, airSpeed)) .Return(airTemp * airSpeed); IClassUnderTest classUnderTest = new ClassUnderTest(determineTempWithWindChill); //Act var results = classUnderTest.WhatIsTheTempToday(airTemp, airSpeed, 2000.0); //Assert Assert.AreEqual(airTemp * airSpeed, results); }
En Microsoft Fakes el stub del setup y del return es un poco ms evolucionado con expresiones lambda, como vemos aqu:
[TestMethod] public void TestSetupAndReturn() { //Arrange double airTemp = 35; double airSpeed = 5.0; StubIDetermineTempWithWindChill stubIDetermineTempWithWindChill = new StubIDetermineTempWithWindChill(); stubIDetermineTempWithWindChill.WhatisCurrentTempDoubleDouble = (x, y) => x * y; IClassUnderTest classUnderTest = new ClassUnderTest(stubIDetermineTempWithWindChill); //Act var results = classUnderTest.WhatIsTheTempToday(airTemp, airSpeed, 2000.0); //Assert Assert.AreEqual(airTemp * airSpeed, results); }
Page 33 of 82
Testing Unitario con Microsoft Fakes - Captulo 3: Migrando a Microsoft Fakes Migrando las sentencias Expect y AssertWasCalled
RhinoMocks usa las sentencias Expect, AssertWasCalled o AssertWasNotCalled para verificar algoritmos o llamadas a mtodos. La API de RhinoMocks ofrece varias opciones para administrar el nivel de detalle de cuantas veces se llam a un mtodo. Durante el testing, si alguna de las sentencias de verificacin no se corresponde con las llamadas esperadas, se lanza una excepcin:
determineTempWithWindChill.Expect(x => x.WhatisCurrentTemp(airTemp, airSpeed)).Repeat.Times(2); determineTempWithWindChill.AssertWasCalled(x => x.WhatisCurrentTemp(airTemp, airSpeed), options => options.Repeat.Times(1)); determineTempWithWindChill.AssertWasNotCalled(x => x.CalcSpecialCurrentTemp(airTemp, airSpeed, aboveSeaLevel));
Con Microsoft Fakes, se usan expresiones lambda para realizar las mismas verificaciones. El siguiente cdigo muestra una verificacin para ver que WhatIsCurrentTemp y CalcSpecialCurrentTemp se han llamado una vez. En el caso de CalcSpecialCurrentTemp se lanzar una excepcin si no se llama al mtodo. Con un poco ms de trabajo en la expresin lambda, Microsoft Fakes ofrece el mismo nivel de opciones para verificar algoritmos y llamadas a mtodos que RhinoMocks:
[TestMethod] public void TestSetupAndReturn() { //Arrange int WhatIsCurrentTempCalled = 0; int CalcSpecialCurrentTempCalled = 0; double airTemp = 35; double airSpeed = 5.0; StubIDetermineTempWithWindChill stubIDetermineTempWithWindChill = new StubIDetermineTempWithWindChill(); stubIDetermineTempWithWindChill.WhatisCurrentTempDoubleDouble = (x, y) => { WhatIsCurrentTempCalled++; return x * y; }; stubIDetermineTempWithWindChill.CalcSpecialCurrentTempDoubleDoubleDouble = (x, y, z) => { CalcSpecialCurrentTempCalled++; throw new Exception("CalcSpecialCurrentTemp should not have been " + "called"); }; IClassUnderTest classUnderTest = new ClassUnderTest(stubIDetermineTempWithWindChill); //Act var results = classUnderTest.WhatIsTheTempToday(airTemp, airSpeed, 2000.0); //Assert Assert.AreEqual(airTemp * airSpeed, results); Assert.AreEqual(1, WhatIsCurrentTempCalled); Assert.AreEqual(0, CalcSpecialCurrentTempCalled); }
Page 34 of 82
Conclusin
Cualquier migracin de test de Telerik, RhinoMocks o Typemock requerir un gran esfuerzo. Las migraciones basadas en Buscar y Reemplazar no funcionarn bien ya que las definiciones de los comportamientos son muy diferentes. Los detalles para cada uno de esos frameworks estn fuera del alcance de este documento. Para ver cmo migrar de estos productos a Microsoft Fakes, ser mejor leer la documentacin de cada producto.
Page 35 of 82
Captulo 4: FAQ
En este captulo, veremos algunas preguntas frecuentes, algunas creemos que son avanzadas, - algunos casos extremos aunque creemos que son lo suficientemente importantes como para cubrirlos en esta gua.
Si optamos por no aadir la dll Microsoft.QualityTools.Testing.Fakes.dll de manera local, podemos aislar el uso de Fakes aadindolos a un proyecto a parte y compilar ese proyecto slo en con una configuracin de build especfica. Es importante ver que si nuestros servidores de builds estn ejecutando una versin adecuada de Visual Studio debemos tener tambin Team Foundation Server 2012 para que esos tests se ejecuten de la misma manera en una build. Si estamos usando Team Foundation Server 2010, tendremos que editar nuestra plantilla de build para que ejecute los test que usen Fakes con vstest.console.exe. Si estamos usando la versin RTM de Visual Studio 2012, tendremos que generar y publicar nuestro archivo TRX. Visual Studio 2012 Update 1 incluye una actualizacin de vstest.console.exe que soporta la publicacin como parte de la llamada.
Podemos sobrescribir el comportamiento de algunas clases de System como cualquier otro assembly configurando la generacin de los tipos de stub y filtrarlo en un archivo xml con la extensin .fakes:
Para eliminar los tipos que Microsoft Fakes no soporta, como CancellationToken y CancellationTokenSource, tendremos que refactorizar nuestro cdigo para cambiar las interfaces y las dependencias de los componentes que queramos testear. Cuando se haya hecho un fake de un tipo no soportado al compilar el .fakes se ver en el resultado de compilacin como un Warning.
Psate por Code generation, compilation, and naming conventions in Microsoft Fakes para ms informacin
Logging detallado
Debido a varias razones, Fakes puede decidir saltarse una clase cuando genera los shims. Con Visual Studio 2012 Update 1, podemos obtener ms informacin sobre el porqu de esa decisin cambiando el atributo Diagnostic de Fakes a true; esto har que Fakes nos muestre las clases que se ha saltado como warnings. Cuando Fakes decide saltarse un elemento de un tipo, escribe mensajes de diagnstico en el log de MSBuild. Podemos habilitarlo seteando la propiedad Verbosity del elemento .fakes e incrementar el nivel de detalle de MSBuild:
Si tenemos acceso al cdigo del assembly al que estamos haciendo el fake, podemos exponer los tipos internos usando la propiedad InternalsVisibleTo. Cuando hacemos esto en un assembly con nombre fuerte, tendremos que indicar el nombre y la clave pblica tanto para el assembly fake como para el assembly de test. Por ejemplo:
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("SimpleLibrary.Test, PublicKey=0028b")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("SimpleLibrary.Fakes, PublicKey=0028b")]
Fijaos que necesitaremos la clave pblica y no el key token pblico del assembly, que es lo que normalmente vemos. Para obtener la clave pblica de un assembly firmado, necesitaremos la herramienta sn.exe que est incluida en Visual Studio. Por ejemplo:
C:\sn -Tp ClassLibrary1.Fakes.dll Microsoft (R) .NET Framework Strong Name Utility Version 4.0.30319.17929 Copyright (c) Microsoft Corporation. All rights reserved. Public key (hash algorithm: sha1): 0024000004800000940000000602000000240000525341310004000001000100172b76875201e1 5855757bb1d6cbbf8e943367d5d94eb7f2b5e229e90677c649758c6a24186f6a0c79ba23f2221a 6ae7139b8ae3a6e09cb1fde7ce90d1a303a325719c2033e4097fd1aa49bb6e25768fa37bee3954 29883062ab47270f78828d2d2dbe00ae137604808713a28fce85dd7426fded78e1d1675ee3a1e8 0cdcd3be Public key token is 28030c10971c279e
En el siguiente cdigo se desactiva el ShimGeneration, y genera Stubs slo para los tipos que contengan Contoso.MainWeb.Repository en el nombre:
<Fakes xmlns="http://schemas.microsoft.com/fakes/2011/"> <Assembly Name=" Contoso.MainWeb"/> <StubGeneration> <Clear/> <Add Namespace="Contoso.MainWeb.Repository" />
NOTA
Page 38 of 82
Tenemos que saber que la generacin restringida tiene un efecto en el tiempo de compilacin y que la mejor optimizacin que podemos hacer es evitar la re-generacin de los fakes. Si ests haciendo un fake de un assembly que no suele cambiar deberas compilar los assemblies fakes una sola vez en un proyecto a parte y aadir esos assemblies como referencias al control de cdigo. Y referenciar desde ah tus proyectos de test unitarios.
Page 39 of 82
Page 40 of 82
1. 2. 3.
Borra el directorio Fakes y los archivos asociados de tu proyecto Borra las referencias a los assemblies .Fakes de tu proyecto Borra el directorio FakesAssemblies del directorio de tu proyecto.
4.
Edita manualmente el archivo de tu proyecto de test. Busca los warnings para eliminar los using que hacan referencia a los Fakes:
Page 41 of 82
Los assemblies Fakes son auto-generados y estn en el directorio FakesAssemblies bajo el proyecto que los referencia. Estos archivos se crean en cada compilacin. Por lo que no deberan ser considerados como elementos
Page 42 of 82
Otra forma sera seleccionar cada cambio de manera separada, lo que permite ms opciones para ignorarlo (como por extensin, nombre de archivo o directorio).
Estas reglas se aplican a un archivo .tfignore: # para indicar que una lnea es un comentario. Se soportan los caracteres especiales * y ? Una definicin es recursiva a menos que se prefije con el carcter \ El smbolo de exclamacin, !, niega una definicin (los archivos que cumplan el patrn NO son ignorados).
El archivo .tfignore puede editarse con cualquier editor e texto y debe aadirse al control de cdigo. Podemos configurar qu tipos de archivos se ignorarn poniendo un archivo llamado .tfignore en el directorio que queremos que se apliquen las reglas. Los efectos del .tfignore son recursivos. Sin embargo, podemos crear .tfignore en los subdirectorios para sobrescribir los efectos de un .tfignore que haya en un directorio padre. http://msdn.microsoft.com/en-us/library/ms245454.aspx#tfignore
Page 43 of 82
Es posible crear stubs con Microsoft Fakes para aislar esa dependencia. En el siguiente cdigo vemos cmo crear un stub para inyectrselo al constructor del controlador:
[TestClass] public class CustomersControllerTest { private StubICustomerRepository stubCustomerRepository; private CustomersController controller; [TestInitialize] public void SetupController() { stubCustomerRepository = new StubICustomerRepository(); controller = new CustomersController(stubCustomerRepository); } [TestMethod] public void CreateInsertsCustomerAndSaves() { // arrange bool isInsertOrUpdateCalled = false; bool isSaveCalled = false; stubCustomerRepository.InsertOrUpdateCustomer = customer =>
Page 44 of 82
Para testear esta accin, tenemos que usar tipos Shim para aislar las clases Membership y FormsAuthentication:
[TestMethod] public void Login_with_valid_model_and_valid_user_authenticate_and_redirect() { // arrange var model=new LogOnModel{Password = "123", UserName = "usrtest", RememberMe = true}; var returnUrl = "/home/index"; bool isAuthenticationCalled = false; bool isValidateUserCalled = false; RedirectResult redirectResult; using (ShimsContext.Create()) { ShimMembership.ValidateUserStringString = (usr, pwd) => isValidateUserCalled = true; ShimFormsAuthentication.SetAuthCookieStringBoolean = (username, rememberme) => { Assert.AreEqual(model.UserName, username); Assert.AreEqual(model.RememberMe, rememberme); isAuthenticationCalled = true;
Page 45 of 82
Page 46 of 82
Si queremos testear este cdigo, o cualquier otro cdigo que llame al mtodo UpdateRoadword(), tendremos que tratar con esta situacin. La mejor opcin sera refactorizar para tratarlo bien, pero hay ocasiones en las que eso no es posible o deseable.
Rompiendo la dependencia
La solucin ms simple es hacer un Shim del cliente WCF y ofrecer nuestra propia implementacin. Esto no requiere ejecutar una instancia del servicio:
using (ShimsContext.Create()) { RoadworkServiceReference.Fakes.ShimRoadworkServiceClient.Constructor = (real) => { }; var intercept = new
Page 47 of 82
Es importante ver que adems de crear un shim en la operacin especificada, tambin se ha creado uno para el constructor. Esto es debido a que un constructor real de WCF lanzar una excepcin debido a la falta de configuracin del servidor. Esta aproximacin puede tener algunas limitaciones. Primero, la lgica requerida para ofrecer una implementacin simple puede que no lo sea tanto, y segundo, esta aproximacin enmascarar cualquier problema de serializacin que pueda ocurrir, como pasar una clase derivada que no sea reconocida como un tipo vlido (known type).
Mejorando la situacin
Si el assembly con la implementacin actual del servicio es parte de la solucin, hay una solucin que solventar ambas limitaciones. Crearemos una instancia actual y usaremos la lgica real, pero evitaremos los aspectos del servicio actual. Como el cliente y el servidor no comparten la implementacin de los tipos, tendremos que transformar los parmetros y la salida. Usando un DataContractSerializer, tambin veremos cualquier problema de serializacin:
var services = new Dictionary<RoadworkServiceReference.RoadworkServiceClient, RoadworkService.RoadworkService>(); using (ShimsContext.Create()) { RoadworkServiceReference.Fakes.ShimRoadworkServiceClient.Constructor = real => { services.Add(real, new RoadworkService.RoadworkService()); }; var intercept = new FakesDelegates.Func<RoadworkServiceReference.RoadworkServiceClient, RoadworkServiceReference.Block[], RoadworkServiceReference.Impediment[]>( (instance, blocks) => { // ============================================================================= // The following (commented out) code uses explicit transforms, see docs for // reasons this may rapidly become difficult, and other potential issues.. // ============================================================================= // var realBlocks = new List<Models.Block>(); // foreach (RoadworkServiceReference.Block item in blocks) // { // // // } var realBlock = Transform(item); realBlocks.Add(realBlock);
Models.Block[] dataContractTransform = DataContractTransform<RoadworkServiceReference.Block[], Models.Block[]>(blocks); var realBlocks = new List<Models.Block>(dataContractTransform); var service = services[instance]; var results = service.RetrieveCurrent(realBlocks); var impediments = new List<RoadworkServiceReference.Impediment>(); foreach (var result in results)
Page 48 of 82
La implementacin completa est disponible en el cdigo del Hands-on Lab en: Exercise 4\Traffic.AdvancedTechniques\Examples\BreakingServiceBoudnaryTechniques.cs
Para afrontar ambos escenarios, podemos generar un shim sobre el timer y permitir la invocacin manual del cdigo:
TimerCallback applicationCallback = null; object state = null; TimeSpan interval = TimeSpan.Zero; System.Threading.Fakes.ShimTimer.ConstructorTimerCallbackObjectTimeSpanTimeSpan = (timer, callba ck, arg3, arg4, arg5) => { applicationCallback = callback; state = arg3; interval = arg5; };
Datos no repetibles
Otra situacin que se suele dar es cuando la lgica se basa en una distribucin creada por un generador de nmeros aleatorios. Esto hace imposible saber exactamente qu datos se generarn. La forma ms simple es hacer un Shim de la clase Random, y ofrecer unos valores deterministas.
Page 49 of 82
La implementacin completa est disponible en el Hands-on Lab: Exercise 4\Traffic.AdvancedTechniques\Examples\NonDeterministicBehaviorTechniques.cs Como estamos tratando con generadores de nmero aleatorios, hay un detalle que se suele pasar por alto: Varias instancias con la misma semilla generar la misma secuencia de nmeros! Si intentis realizar operaciones con conjuntos independientes de nmeros aleatorios y estis usando varias instancias de Random para conseguirlo, debis aseguraros de que tienen la misma semilla. Esto lo veremos en la prxima seccin.
Nota: El constructor sin parmetros usa Environment.TickCount, con lo que varias instancias creadas en un tiempo pequeo podran tener la misma semilla.
System.Fakes.ShimRandom.Constructor = delegate(Random random) { ShimsContext.ExecuteWithoutShims(delegate { var constructor = typeof(Random).GetConstructor(new Type[] { }); constructor.Invoke(random, new object[] { }); }); }; System.Fakes.ShimRandom.ConstructorInt32 = delegate(Random random, int i) { ShimsContext.ExecuteWithoutShims(delegate
Page 50 of 82
Page 51 of 82
Page 52 of 82
Ejercicio 1: Usando Stubs para aislarnos del acceso a la base de datos (20 30 min)
Para este ejercicio vamos a usar una aplicacin ASP.NET MVC 4 muy simple. La solucin IntroToStubs.sln en el directorio Hands-on Lab\Excercies 1\start tenemos slo la clase Controller. No contiene vistas (est configurado para que use Razor), y, para este ejercicio, no requeriremos ninguna vista. Nuestro trabajo consistir en implementar un aspecto funcional: Obtener un resumen de compra y un clculo del precio total de esa orden de compra. Es importante ver que no hay definida ninguna base de datos, ni siquiera necesitamos crearnos una para empezar a hacer tests unitarios y validar nuestros componentes. Sin Stubs, la primera aproximacin que tendramos se nos ocurrira sera: 1. 2. 3. Crear una base de datos de ejemplo Rellenarla con datos de ejemplo. Crear los test aadiendo los datos de ejemplo que requieran para ejecutarse.
En este ejercicio, veremos cmo con Microsoft Fakes podemos aislar la dependencia existente entre la base de datos y nuestra clase controladora para testear la implementacin funcional.
Patrn de implementacin
En la siguiente figura podemos ver la interaccin normal entre varias clases. Adems, podemos ver que el acoplamiento entre el Repositorio y la base de datos es lo que queremos aislar. Nuestra intencin es centrar nuestros test en la lgica de negocio que, para este ejemplo, estar en los mtodos Action de la clase Controller.
Page 53 of 82
OBJETIVO
Cuando aislamos, desacoplamos nuestra implementacin del repositorio con la base de datos, al mismo tiempo, ofrecemos un estado conocido al test y el comportamiento que se usar luego por los componentes que se testan. En la siguiente figura, se usa el Fake Stub en lugar del repositorio real, y el cdigo del propio test indicar cules son los valores necesarios para testear:
Pasos a realizar
1. 2. 3. 4. 5. 6. Aadir un assembly fake por cada assembly del que queramos un fake Revisar y ajustar la configuracin de los archivos Fakes [avanzado] Ajustar los usings (C#) o los Import (VB) a los namespaces de los Stubs necesarios. Aadir la implementacin necesaria para los stubs en aquellas clases y mtodos necesarios para los tests que se vayan a hacer. Dar el cdigo para el Act del objeto o mtodo que se va a testear. Poner los Assert necesarios con los datos esperados.
An no tenemos ninguna clase de test definida. En MainWeb, trabajaremos con las siguientes clases:
NOTA
El ejemplo es una solucin comn que aprovecha el constructor con parmetros de la clase Controller, junto al patrn Repository
Page 54 of 82
NOTA
4.
En este punto, revisa el proyecto MainWeb.Test y la estructura de directorio con el Solution Explorer; deberas ver el siguiente nodo adicional Fakes aadido a la estructura del proyecto MainWeb.Tests con el nombre completo del assembly MainWeb y un .fakes como extensin de archivo:
NOTA
El framework de Fakes ha generado Stubs y Shims para nuestro Assembly y aquellos tipos estn en el Microsoft.ALMRangers.MainWeb.Fakes.
Page 55 of 82
2. 3.
El Solution Explorer, selecciona el archivo Microsoft.ALMRangers.MainWeb.fakes y mira las propiedades (F4) del archivo. Vers que el Build Action es Fakes. Opcional: Modifica el archivo generado de la siguiente manera para slo crear Stubs (no Shims) y para filtrar los tipos que vamos a necesitar:
<Fakes xmlns="http://schemas.microsoft.com/fakes/2011/"> <Assembly Name="Microsoft.ALMRangers.FakesGuide.MainWeb"/> <StubGeneration> <Clear/> <Add Namespace="Microsoft.ALMRangers.FakesGuide.MainWeb.Models" /> </StubGeneration> <ShimGeneration Disable="true"/> </Fakes>
Estas opciones muestran cmo podemos adelgazar el assembly generado filtrando por tipos especficos. Cuando compilamos, el framework de Microsoft Fkes generar un assembly para nuestro proyecto basndose en estas opciones. Lo hacemos aqu para mostrar los valores reducidos que aparecen en el IntelliSense cuando estemos en el editor de cdigo.
Paso 5 Revisa las clases del modelo y del controlador del MainWeb
1. Revisa las clases del modelo del directorio Model del proyecto MainWeb. Fijate que hemos usado una implementacin Testable, en el que la clase OrderController usa una interfaz (IOrderRepository); esta interfaz nos permite ofrecer tanto un stub del IOrderRepository al OrderController como un comportamiento especfico a nuestras necesidades de testeo. Adems de eso, hay algunas clases bsicas que representan objetos de negocio que van a ser usados por los componentes de negocio durante los tests:
public interface IOrderRepository { IQueryable<Order> All { get; } IQueryable<OrderLines> OrderLines(int id); Order Find(int id); } public class Order { public Order() { this.OrderLines = new HashSet<OrderLines>(); } public int Id { get; set; } public string CustomerName { get; set; } public double TaxRate { get; set; } public ICollection<OrderLines> OrderLines { get; set; } }
NOTA
Page 56 of 82
3. 4.
Page 57 of 82
Estos usings incluyen el namespace Microsoft.ALMRangers.MainWeb.Models.Fakes . En l estn los tipos (stubs y shims) generados por el framework de Fakes durante la compilacin presentes en el assembly. En la compilacin, el objetivo de la generacin son los assemblies y los namespaces. Hemos aadido un alias para ModelFakes para que sea ms fcil leer el cdigo. No es necesario hacerlo, podemos usar el namespace completo si queremos.
NOTA
El alias del using anterior simplemente es para una lectura ms sencilla del cdigo, no es necesario hacerlo.
2.
Crear una instancia de IOrderRepository. Se setear a una implementacin Stub que podremos definir en el contexto de este test:
[TestMethod] public void OrderController_orderSummaryTotalCheck_equalsSum() { // arrange const int TestOrderId = 10; IOrderRepository repository = new ModelFakes.StubIOrderRepository { // lambda code }
3.
De esta manera configuramos una instancia de IOrderRepository que ser un Stub (Fake). No el objeto real. Aqu es donde, a medida que nuestro test lo necesite, deberemos ofrecer una implementacin para cualquier mtodo que haga falta. La implementacin del Stub, generada por el framework de Microsoft Fakes es un tipo estndar de CLR sin ningn comportamiento. Ah es nosotros tenemos que inyectar el cdigo necesario para nuestro test. En este punto, hemos inicializado la instancia para nuestro Stub del repositorio pero an no hemos terminado. Tenemos que implementar dos mtodos del Stub ya que nos va a hacer falta para nuestro test.
Page 58 of 82
El nombre de la propiedad del tipo StubIOrderRepository tiene la signatura de FakesDelegates.Func<int, Order> FindInt32. El framework de Microsoft Fakes nombra a cada mtodo aadiendo el tipo del parmetro como parte del nombre. Asi que como el mtodo Find de IOrderRepository tiene un parmetro del tipo Int32, el nombre del stub es FindInt32.
NOTA
5.
Haz un mtodo de generacin de datos esttico para que nuestro test lo use:
private static IQueryable<OrderLines> GetOrderLines() { var OrderLines = new List<OrderLines> { new OrderLines { Id = 10, IsTaxable = true, ProductName = "widget1", Quantity = 10, UnitCost = 10 }, new OrderLines { Id = 10, IsTaxable = false, ProductName = "widget2", Quantity = 20, UnitCost = 20 }, new OrderLines { Id = 10, IsTaxable = true, ProductName = "widget3", Quantity = 30, UnitCost = 30 }, new OrderLines { Id = 10, IsTaxable = false, ProductName = "widget4", Quantity = 40, UnitCost = 40 }, new OrderLines { Id = 10, IsTaxable = true, ProductName = "widget5", Quantity = 50, UnitCost = 50 }, }; return OrderLines.AsQueryable(); }
6.
Aade el siguiente cdigo para tener un stub del mtodo IOrderRepository.OrderLines(int). Usa el mtodo esttico GetOrderLines
OrderLinesInt32 = id => { var OrderLines = GetOrderLines(); return OrderLines.AsQueryable(); }
7.
Justo despus del } aade el siguiente cdigo para crear la instancia d el OrderController usando el constructor con parmetros:
var controller = new OrderController(repository);
La testabilidad de la solucin y sus componentes influyen a la hora de elegir Stubs o Shims. Nuestro ejemplo funciona bien con Stubs ya que usa interfaces. Las interfaces nos permiten inyectar objetos concretos diferentes para nuestros test, son nuestros Fakes. Las implementaciones testables usan interfaces, clases abstractas, y miembros virtuales que permiten la generacin de Stubs con el framwork de Microsoft Fakes. Lee el ejercicio de Shims para testear lo intesteable. Page 59 of 82
NOTA
Page 60 of 82
NOTA
En este punto, ya podemos ejecutar el test desde el Test Explorer y veremos que falla. La prxima tarea es modificar la lgica del mtodo para hacer que funcione.
2.
Page 61 of 82
En este punto, hemos validado que el mtodo de accin del OrderController (OrderLines) devuelve un modelo con una propiedad Total que se corresponde con nuestros datos de test, basados en el clculo de tasas.
REVISIN
En este ejercicio, hemos eliminado la dependencia de la base de datos y hemos visto cmo Microsoft Fakes Stubs se puede usar para testear componentes a travs del aislamiento de dependencias. Podis ver el cdigo final en Handson Lab\Excercise 1\end.
Page 62 of 82
Ejercicio 2: Usando Shims para aislarnos del sistema de archivos y de la fecha (20 30 min)
OBJETIVO
1.
En este ejercicio, veremos cmo usar Shims para aislarnos el cdigo que queremos testear de las dependencias del sistema de archivos y de la fecha del sistema.
Escenario
Eres uno de los desarrolladores de una empresa de software. Tu equipo est a cargo del mantenimiento de un assembly de log que se usa en todas las aplicaciones del departamento. Te han asignado la tarea de aadir una nueva caracterstica a la clase central del sistema: el LogAggregator. Esta clase puede aadir archivos de log a un directorio y filtrar los archivos a slo unos das. No hay test unitarios para ese componente por ahora. Antes de cambiar nada en esa parte del cdigo, quieres asegurarte de que no rompes nada. Desafortunadamente, la clase LogAggregator no ha sido diseada para que se fcil asilarla ni del sistema de archivos ni de la hora del sistema a base de pasarle los valores necesarios. El cdigo no ofrece ninguna forma de inyectarle un stub, est ocultando su implementacin. Por lo tanto, vamos a crear nuestro primer Shim para poder testear la clase LogAggregator.
Page 63 of 82
}
El cdigo que vemos aqu es una clase para para centrarnos. Puedes hacer todos los pasos de este laboratorio basndote en esta clase. Si no tienes acceso a la solucin que tenemos preparada, puedes generarlo creando una class library y copiando y pegando el cdigo en l.
NOTA
3.
4.
Haz clic derecho en el mtodo y selecciona la opcin Run Tests. El test empezar a ejecutarse y fallar.
Page 64 of 82
El mtodo esttico que vemos representa un patrn bastante adecuado para las clases File y Directory del namespace System.IO. Puede haber razones para usar el sistema de archivos de manera diferente, ya lo veremos ms adelante en este ejercicio. Ahora vamos a hacer un shim para las funciones Directory.GetFiles() y File.ReadAllLines().
Los archivos del directorio Fakes le dicen a Visual Studio para qu tipos hay que generar los shims. Puedes usar estos archivos para personalizar para qu tipos generar un shims o un stub. La razn por la que hay dos archivos es porque el namespace System genera ms de un assembly. Como mscorlib.dll no se puede referenciar
Page 65 of 82
NOTA
Visual Studio ha creado un nuevo directorio llamado Fakes que contiene dos archivos XML y ha aadido referencias a dos assemblies recin generados.
3.
Cambia el mtodo de test en LogAggregatorTests.cs de la siguiente forma. Los cambios estn en negrita:
// Arrange var sut = new LogAggregator(); ShimDirectory.GetFilesStringString = (dir, pattern) => new string[] { @"C:\someLogDir\Log_20121001.log", @"C:\someLogDir\Log_20121002.log", @"C:\someLogDir\Log_20121005.log", }; ShimFile.ReadAllLinesString = (path) => { switch (path) { case @"C:\someLogDir\Log_20121001.log": return new string[] {"OctFirstLine1", "OctFirstLine2"}; case @"C:\someLogDir\Log_20121002.log": return new string[] {"ThreeDaysAgoFirstLine","OctSecondLine2"}; case @"C:\someLogDir\Log_20121005.log": return new string[] {"OctFifthLine1", "TodayLastLine"}; } return new string[] {}; }; // Act var result = sut.AggregateLogs(@"C:\SomeLogDir", daysInPast: 3); // Assert Assert.AreEqual(4, result.Length, "Number of aggregated lines incorrect."); CollectionAssert.Contains(result, "ThreeDaysAgoFirstLine", "Expected line missing from aggregate d log."); CollectionAssert.Contains(result, "TodayLastLine", "Expected line missing from aggregated log.") ;
Estas sentencias le dicen al framework de Microsoft Fakes qu mtodos de deben ser interceptados y qu cdigo se debe ejecutar. Los nombres estn puestos por convencin. El nombre de la clase que se usa para acceder al Shim de un tipo concreto es el nombre del tipo, con el prefijo de Shim. El nombre de la propiedad usada para setear el delegado de la interceptacin de la llamada es el nombre del mtodo con el sufijo de los nombres de los tipos de sus parmetros. Esta convencin nos permite setear diferentes delegados para diferentes sobrecargas de un mtodo. El cdigo que hemos asignado a estas propiedades en el cdigo anterior hace que el mtodo GetFiles(string,string) devuelva tres paths (C:\someLogDir\Log_20121001.log, C:\someLogDir\Log_20121002.log, y C:\someLogDir\Log_20121005.log) que tienen fechas codificadas en su nombre. (No nos importa el patrn que
Page 66 of 82
El mensaje de error nos dice que usemos un ShimsContext. Esto es necesario para indicar el contexto en el que se usarn los shims. Los shims slo se usaran en ese contexto. Sin ese contexto, los shims podran provocar efectos secundarios en el resto de test. As que vamos a hacerlo
La configuracin de un ShimsContext debera hacerse siempre en una sentencia using, y nunca en un mtodo setup/initialize o en un teardown/cleanup. Esto podra dejar que los shims estn definidos en otras partes de los test que no deberan afectando y alterando los test.
NOTA
6.
7.
8.
Los test fallan esta vez con el mensaje Assert.AreEqual failed. Expected: <4>, Actual <0>. Number of aggregated lines incorrect. Esto es debido a que las fechas codificadas en los nombres de los archivos que pusimos el mtodo shim GetFiles(string,string) son de ms de tres das de antiguiedad y ninguna de ellas entran en el filtro. Vamos a revisar el mtodo LogAggregator.IsInDateRange(string,int) que es el responsable del filtrado de fechas:
private bool IsInDateRange(string filePath, int daysInPast) { string logName = Path.GetFileNameWithoutExtension(filePath); if (logName.Length < 8) { return false; } string logDayString = logName.Substring(logName.Length - 8, 8); DateTime logDay; DateTime today = DateTime.Today; if (DateTime.TryParseExact(logDayString, "yyyyMMdd", CultureInfo.InvariantCulture, DateTimeStyles.None, out logDay)) { return logDay.AddDays(daysInPast) >= today; } return false; }
Page 67 of 82
2.
3.
Paso 6 (Opcional) Ejectua el test con el debugger para entender el flujo de ejecucin.
1. 2. Pon el cursor en la primera lnea del cdigo, debajo del comentario //Act En el men principal, ve a Debug, Toggle Breakpoint para aadir un punto de ruptura:
3. 4. 5.
Haz clic derecho en cualquier parte del test y selecciona la opcin Debug Tests. Despus de que lleguemos al breakpoint, pulsa F11. Contina paso a paso la ejecucin del cdigo (usando F11) hasta que llegamos a la primera ejecucin de la expresin lambda:
Cuando usamos un Shim, todo el dominio de la aplicacin es enrutado a travs del contexto del shim; as que si hacemos una llamada al objeto shim cuando debugeamos, veremos los resultados del shim en lugar de los valores reales.
NOTA
6.
Contina la ejecucin hasta que pases por todas las expresiones lambda, y en el men principal elige Debug, Continue.
Hemos conseguido aislar el cdigo de produccin del LogAggregator de sus dependencias con el sistema de archivos y de la fecha del sistema sin tener que cambiarlo. Puedes ver el cdigo final en Hands-on Lab\Exercies2\end
REVISIN
Page 68 of 82
Sin embargo, en el caso de SharePoint, la tcnica del warpper no siempre es posible ya que el desarrollador de SharePoint suele crear elementos como recibidores de eventos y web parts que necesitan una infraestructura dentro de SharePoint, no encima. Esto hace que no sea prctico usar este tipo de encapsulacin. Ninguna de esas dos opciones soluciona el problema principal. Tenemos que ser capaces de sustituir las instancias de los objetos de SharePoint. As que necesitamos usar la funcionalidad que los Shims nos ofrecen.
Preparacin
Este ejercicio requiere que el PC tenga una versin soportada de Visual Studio y SharePoint 2010 Foundation (aunque cualquier SKU posterior de SharePoint tambin se puede usar). La instalacin de SharePoint no se ejecutar directamente durante este ejercicio, pero los assemblies que se instalarn en el GAC y en Program Files los usaremos para el proceso e Faking. Otra opcin, si no tenemos estas herramientas disponibles en nuestro PC, es usar la mquina virtual de la demo de Brian Keller de VS2012 ALM con Hyper-V (http://blogs.msdn.com/b/briankel/archive/2011/09/16/visual-studio-11-application-lifecycle-managementvirtual-machine-and-hands-on-labs-demo-scripts.aspx) ya que tiene todos los componentes necesarios.
AVISO
SharePoint debe estar instalado en la mquina de desarrollo, no slo una copia de las dlls, para que todos los assemblies necesarios para crear los fakes estn disponibles
OBJETIVO
En este ejercicio, veremos cmo usar Shims para testear que un recibidor de eventos de SharePoint funciona correctamente.
NOTA
2.
3. 4.
Aade una referencia a la dll Microsoft.SharePoint.dll. Normalmente, este archivo se instala como parte del servidor de SharePoint en c:\Program Files\Common Files\Microsoft Shared\ Web Server Extension\T4\ISAPI. Renombra el archivo Class1.cs a ContentTypeItemEventReceiver.cs En el archivo ContentTypeItemEventReceiver.cs reemplaza el cuerpo de la clase con el siguiente cdigo:
using Microsoft.SharePoint; public class ContentTypeItemEventReceiver : SPItemEventReceiver { public void UpdateTitle(SPItemEventProperties properties) { using (SPWeb web = new SPSite(properties.WebUrl).OpenWeb()) { SPList list = web.Lists[properties.ListId]; SPListItem item = list.GetItemById(properties.ListItemId); item["Title"] = item["ContentType"]; item.SystemUpdate(false); } } public override void ItemAdded(SPItemEventProperties properties) { this.EventFiringEnabled = false; this.UpdateTitle(properties); this.EventFiringEnabled = true; } }
5.
Esta ser nuestra aplicacin de ejemplo. Hemos aadido un recibidor de eventos que se llamar cada vez que se aade un elemento a la lista de SharePoint. Este recibidor edita el ttulo del nuevo elemento con lo que haya en la propiedad ContentType. Compila el proyecto. Debera compilar sin errores. Si hay algn problema, comprueba los errores.
Aade un Unit Test Proyect para .NET 4 a la solucin con el nombre Samples.SharePoint.Tests
En este ejercicio, usamos MSTest como framework de testing unitario. Sin embargo, podramos usar otro como nUnit o xUnit gracias a Visual Studio Extension Test Adaptors. Microsoft Fakes no tiene ninguna dependencia con MSTest. Al contrario del proyecto que contiene el cdigo de SharePoint, que necesita el framework 3.5, el proyecto que contiene los test puede usar versiones nuevas del framework. Para el testing de SharePoint se recomienda usar .NET 4.0 para evitar problemas cuando se generen los Fakes.
2. 3. 4.
Aade una referencia al proyecto Samples.SharePoint Aade una referencia a la dll Microsoft.SharePoint.dll, como hiciste en el proyecto de ejemplo. En la seccin de referencias del proyecto de test selecciona la referencia Microsoft.SharePoint y haz clic derecho. Selecciona la opcin Add Fake Assembly. Esto puede tardar unos segundos (incluso algunos
Page 70 of 82
Si por alguna razn la creacin de los fakes falla y tienes que repetir el proceso, asegrate de borrar el archivo del directorio de Fakes. Si no, aparecer un error cuando repitamos el proceso. La razn ms comn para que ocurra un error es que el proyecto de test est configurado para usar .NET 4.5. En este caso, Visual Studio no podr generar los fakes para SharePoint. Tenemos que seleccionar .NET 3.5 o 4 como target del proyecto de test
AVISO
5. 6. 7.
Renombra el archivo UnitTest1.cs a SharePointEventTests.cs Renombra el mtodo TestMethod1 con un nombre significativo Contributor_AddsNewItem_EventFires. Aade las referencias necesarias para el assembly fake de SharePoint y a la librera de faking
como
El cdigo listado al final de esta seccin muestra el test completo. Los siguientes pasos muestran la jerarqua de shims que se han creado: 8. Alrededor de todo el contenido del test, crea un bloque using para el ShimContext. Esto administra el contexto de las operaciones de shimming. Los shims funcionarn dentro de ese bloque. 9. Aade dos variables locales, systemUpdateHasBeenCalled y itemTitleValue. Sern usadas como flags para indicar que se ha llamado al cdigo correcto. 10. Crea un shim para un objeto SPItemEventProperties y configura su comportamiento para las tres propiedades que sern llamadas. 11. Crea un shim para interceptar la llamada al constructor SPSite
NOTA
En el cdigo de ejemplo, los parmetros del constructor shimeado comienzan con el smbolo @. Esto permite el uso de palabras reservadas para el nombre de las variables que, en este caso, hace ms fcil su comprensin.
12. Dentro del bloque SimSPSite configura el mtodo OpenWeb. Esto crea un nuevo objeto shim de SPWeb 13. Dentro del bloque, configura la propiedad List para que devuelva un SPListCollection. 14. Dentro del bloque, configura la propiedad Item para que devuelva un shim de un SPList, el parmetro GUID no se usa. 15. Dentro del bloque, configura el mtodo GetItemById para que devuelva un SPListItem, el parmetro Int no se usa. 16. Dentro del boque, configura la propiedad Item (get y set) y el mtodo SystemUpdate. Fjate que estamos definiendo el valor de la variable local que creamos en lo alto del test para comprobar que se han realizado ciertas llamadas. 17. Finalmente, despus de la creacin de shim anidados, crea una instancia de la clase a testear. 18. Aade una llamada al recibidor de eventos que queremos testear.
Page 71 of 82
[TestClass] public class SharePointEventTests { [TestMethod] public void The_item_title_is_set_to_the_content_type_when_event_fires() { using (ShimsContext.Create()) { // arrange // create the local variables we will write into to check that the correct methods are called var systemUpdateHasBeenCalled = false; var itemTitleValue = string.Empty; // create the fake properties var fakeProperties = new ShimSPItemEventProperties() { WebUrlGet = () => "http://fake.url", ListIdGet = () => Guid.NewGuid(), ListItemIdGet = () => 1234 }; // create the fake site ShimSPSite.ConstructorString = (@this, @string) => { new ShimSPSite(@this) { OpenWeb = () => new ShimSPWeb() { ListsGet = () => new ShimSPListCollection() { ItemGetGuid = (guid) => new ShimSPList() { GetItemByIdInt32 = (id) => new ShimSPListItem() { ItemGetString = (name) => string.Format("Field is {0}", name), SystemUpdateBoolean = (update) => systemUpdateHasBeenCalled = true, ItemSetStringObject = (name, value) => itemTitleValue = value.ToString() } } } } }; }; // create the instance of the class under test var cut = new ContentTypeItemEventReceiver(); // act cut.ItemAdded(fakeProperties); // assert
Page 72 of 82
Microsoft ha publicado SharePoint Emulator, que son una versin de los comportamientos de Moles SharePoint. Muchas de las implementaciones de shim para el core de SharePoint se han empaquetado y se ofrecen como parte de los emuladores de SharePoint. Estos SharePoint Emulator se ven en el blog Introducing SharePoint 6Emulators y estn disponibles como un paquete Nuget
NOTA
6
http://blogs.msdn.com/b/visualstudioalm/archive/2012/11/26/introducing-sharepoint-emulators.aspx
REVISIN
En este ejercicio, hemos visto cmo los Shims de Microsoft Fakes se pueden usar para testear las caractersticas e SharePoint. Puedes ver el cdigo final en Hands-on Lab\Exercise 3\end
Page 73 of 82
Escenario
En este escenario usaremos la aplicacin Traffic Simulator del directorio Excercise 4. En el componente Traffic Core, hay un conjunto de clases que hacen de Modelo del componente Traffic UI. Comenzaremos el proceso de crear tests para las clases City y Car para asegurar su comportamiento actual. La clase City expone la disposicin de la ciudad para el simulador Traffic. Esta clase consume el servicio WFC Traffic.RoadworkService a travs de una clase proxy que se invoca desde un mtodo privado. Ese mtodo privado se invoca desde un callback a una instancia de System.Thread.Timer que se crea en el setter de la propiedad llamada Run. Usaremos Shims para producir los test de la clase City que nos permitir shimear las referencias al servicio WCF y a la instancia del Timer. La clase Car representa un vehculo en el simulador. Esta clase consume un conjunto de objetos del componente Traffic Core a travs de la propiedad ShouldMove. Usaremos una combinacin de Stubs y Shims para poder testear esta propiedad
Vers que este mtodo llama al mtodo UpdateRoadwork(), que contiene una referencia al proxy cliente del servicio. Por lo que, cualquier test que ejecute la propiedad Run tendr una dependencia tando del Timer como del RoadwordServiceClient
OBJETIVO
En este ejercicio, usaremos Shims y Stubs para conseguir que el cdigo heredado est testado
Page 74 of 82
3.
Si intentas ejecutar este cdigo, el test fallar ya que las llamadas que hay por debajo al servicio Roadwork no sern invocadas. Adems, podemos decir que este test es frgil ya que es necesaria una llamada a Thread.Sleep para darle tiempo a la propiedad Run para que cree el Timer, registre el evento, e invoque al servicio Roadwork. Ahora vamos a intentar testar esta propiedad usando Shims para aislarla de sus dependencias externas.
2.
3.
Para usar los mtodos del Shim, tenemos que envolver las llamadas donde invocamos a la propiedad Run en un ShimsContext. Esto asegurar que las llamadas se sustituirn slo en el cdigo que se est testeando. Envuelve el contenido del mtodo con este cdigo:
using (ShimsContext.Create()) { }
4.
Bajo la lnea en la que se declara el booleano expected, aade otra variable local booleana llamada hasServiceBeenInvoked e inicialzala a false.
Page 75 of 82
6.
Ahora, aadamos la implementacin para la operacin RetrieveCurrentBlock a travs de la clase ShimRoadworkServiceClient. Este mtodo devuelve un array de Block; usaremos nuestra propia implementacin para setear nuestra variable local hasServiceBeenInvoked a true y devolveremos un Stub. Justo despus del cdigo que hemos aadido en el paso 4, aade el siguiente cdigo:
ShimRoadworkServiceClient.AllInstances.RetrieveCurrentBlockArray = (instance, blocks) => { hasServiceBeenInvoked = true; return new List<StubImpediment> { new StubImpediment { description = string.Empty, location = blocks.FirstOrDefault(), relativeSpeed = double.MinValue } }.ToArray(); };
7.
Aade otro assert para asegurarnos de que la variable hasServiceBeenInvoked vale lo que debe valer:
Assert.IsTrue(hasServiceBeenInvoked, "City.Run should invoke the Roadwork service");
Page 76 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 frgil ya que sigue necesitando la llamada al mtodo Thread.Sleep para que le d tiempo a la propiedad Run a inicializar la clase Timer.
Fjate que se han creado las referencias a System.4.0.0.0.Fakes y mscorlib.4.0.0.0.Fakes. Esto es debido a que el namespace System existe tambin en el assembly mscorlib. Expande el directorio Fakes en el proyecto de Test y veremos que se han generado dos archivos correspondientes a las nuevas referencias que se han aadido mscrolib.fakes y System.fakes. 2. Este paso es necesario ya que Shims por defecto no crear un namespace para System.Threading. Abre el archivo mscorlib.fakes y corrgelo para que sea as:
3.
Ahora vamos a modificar el test para reemplazar la llamada al constructor de Timer con nuestra propia implementacin. El constructor de Timer usado es uno que recibe algunos parmetros para inicializar; tenemos que encontrar el que se corresponde con la signatura de nuestra clase ShimTimer. De nuevo, usaremos una variable local que nos permita comprobar que se ha llamado a nuestra implementacin. 4. Renombra la variable local hasServiceBeenInvoked a hastimerBeenInvoked y asegrate de que la referencia de esta variable tambin ha sido renombrada. Borra el cdigo que configura el shim del constructor de RoadworkServiceClient y las llamadas a RetrieveCurrentBlockArray. Ahora, aade la implementacin para el constructor del Timer que tiene cuatro parmetros un callback, un object, y dos TimeSpan. En esta implementacin pon la variable hasTimerBeenInvoked a true. El cdigo debera ser algo as:
ShimTimer.ConstructorTimerCallbackObjectTimeSpanTimeSpan = (timer, callback, state, dueTime, per iod) => { // Do nothing else but confirm that our implementation was called hasTimerBeenInvoked = true; };
Page 77 of 82
Ejecuta el test desde el Test Explorer y debera pasar. Ya podemos decir que la propiedad City.Run est testada.
3.
En lugar de una instancia de Traffic.Core.Algorithms.RoutingAlgorithm usaremos un StubRoutingAlgorithm para crear una variable local llamada expectedAlgorithm y seleccionamos un valor System.Windows.Media.Brushes para asignarla a una variable local llamada expectedColor. 4. En el mtodo de test, aade las siguientes lneas de cdigo:
var expectedAlgorithm = new StubRoutingAlgorithm(); var expectedColor = Brushes.Aqua;
5.
Ahora, crea la instancia del Car usando las dos variables locales como parmetros de entrada del constructor:
Car codeUnderTest = new Car(expectedAlgorithm, expectedColor);
Page 78 of 82
7.
Ejecuta todos los test unitarios con el Test Explorer para asegurarnos de que todos pasan.
El siguiente test unitario que haremos ser testear el getter de ShouldMove, cuando la propiedad Loation.Road sea null 5. 6. Aade un nuevo mtodo de test llamado Car_ShouldMoveProperty_ReturnsFalseIfLocationRoadIsNull y repite los puntos 4 y 5 del Paso 6. Usa una instancia del tipo StubElementLocation con la propiedad Road a null. Asgnala a la propiedad codeUnderTest.Location de la siguiente manera:
codeUnderTest.Location = new StubElementLocation { Road = null };
7.
Ahora aade un assert para comprobar que la propiedad codeUnderTest.ShouldMove es false. De nuevo, aade un mensaje de error adecuado.
Page 79 of 82
Ahora somos dependientes del resultado del mtodo Location.Result.IsFree. Como la implementacin de la propiedad ShouldMove depende de que esta llamada devuelva true, haremos un test unitario que comprueba el estado cuando esta propiedad devuelva false. Como la propiedad Location.Result es del tipo Block, tendremos que usar una instancia de ShimBlock 8. Aade un nuevo test llamado Car_ShouldMoveProperty_ReturnsFalseIfLocationRoadsIsFreeReturnsFalse a la clase CarTests.cs 9. En el cuerpo del mtodo, crea las variables locales stubAlgorithm y testBrush como en el test unitario anterior. 10. Aade una sentencia using ShimsContext.Create() para aislar las llamadas de la clase Block. 11. Usando la clase ShimBlock, ofrece una implementacin para asegurarnos de que cualquier llamada al mtodo IsFree devolver false. Aqu est el cdigo:
ShimBlock.AllInstances.IsFreeInt32 = (block, position) => { return false; };
12. Crea una instancia de Car usando las variables StubAlgorithm y testBrush 13. Crea una instancia de la clase StubElementLocation (mira el paso 6) pero en lugar de asignar un null a la propiedad Road, usa una instancia de un StubBlock. 14. Por ltimo, aade un assert para asegurarnos de que la propiedad codeUnderTest.ShouldMove devuelve false. El cdigo completo del test unitario es:
/// <summary> /// Test to ensure that the Car.ShouldMove property returns false where Location.Road.IsFree ret urns false. /// </summary> [TestMethod] public void Car_ShouldMoveProperty_ReturnsFalseIfLocationRoadIsFreeReturnsFalse() { var stubAlgorithm = new StubRoutingAlgorithm(); var testBrush = Brushes.AliceBlue; using (ShimsContext.Create()) { // Ensure any calls to Block.IsFree return false. ShimBlock.AllInstances.IsFreeInt32 = (block, position) => { return false; }; Car codeUnderTest = new Car(stubAlgorithm, testBrush); codeUnderTest.Location = new StubElementLocation { Road = new StubBlock() };
Page 80 of 82
Para tener esta parte del mtodo testeado, deberamos hacer un Shim de la clase DiscoveredRoutes. Sin embargo, viendo esta clase, veremos que es internal. Para este ejercicio, hemos decidido que el cdigo bajo test es inmutable; es decir, que no podemos aadir un atributo InternalsVisibleTo para el assembly Traffic.Core. Esto significa que los intentos de tener un cdigo testeado no han valido para nada? No necesariamente. Hemos ampliado la cobertura de nuestro cdigo desde cero hasta algo ms, esto aade valor a nuestro sistema. En este punto podemos optar por tener un cierto nivel de test de integracin para mitigar aquellas reas en las que el sistema no puede ser cubierto por tests unitarios.
REVISIN
En este ejercicio, hemos visto cmo podemos empezar a tener un sistema complejo testeado incrementando la cobertura de cdigo
Page 81 of 82
Conclusin
Aqu concluye nuestra aventura de testing unitario con Microsoft Fakes. Hemos tocado la teora, introducido los Shims y Stubs, y esperamos haber enseado cundo usarlos. Tambin hemos visto varios ejercicios en los Hands-on Lab, siguiendo varios escenarios, y hemos visto varias tcnicas avanzadas. Estamos al principio del ciclo de vida de Microsoft Fakes con ms actualizaciones que vendrn en el futuro en prximas versiones de Visual Studio. Esperamos que encuentres til esta tecnologa y esta gua. Atentamente,
Page 82 of 82