Usar correcciones de compatibilidad (shim) para aislar la aplicación de otros ensamblados para las pruebas unitarias

Tipos de correcciones de compatibilidad es una de dos tecnologías que el marco de Microsoft Fakes utilice para permitir fácilmente aislar componentes en pruebas del entorno.Las cuñas desvían llamadas a métodos específicos al código que escribe como parte de la prueba.Muchos métodos devuelven varios resultados dependientes en condiciones externas, pero un correcciones de compatibilidad está bajo el control de pruebas y puede devolver resultados coherentes en cada llamada.Esto hace las pruebas más fáciles escribir.

Utilice las cuñas para aislar el código de los ensamblados que no forman parte de la solución.Para aislar los componentes de la solución de uno en uno, recomendamos utilizar códigos auxiliares.

Para una guía de información general y del tutorial, vea Aislar el código probado con Microsoft Fakes

Requisitos

  • Visual Studio Ultimate

Vea Vídeo (1h16): Código O.N.U-comprobable de prueba con Fakes en Visual Studio 2012

En este tema

Esto es lo que se cubrirá en este tema:

Ejemplo: El error del año 2000

Cómo utilizar Shims

  • Agregar ensamblados de las falsificaciones

  • Utilice ShimsContext

  • Escriba las pruebas con Shims

Cuñas para los diferentes tipos de métodos

Cambiar el comportamiento predeterminado

Detectar accesos al entorno

Simultaneidad

Llamar al método original del método de calce

Limitaciones

Ejemplo: El error del año 2000

Veamos un método que produce una excepción el 1 de enero de 2000:

// code under test
public static class Y2KChecker {
    public static void Check() {
        if (DateTime.Now == new DateTime(2000, 1, 1))
            throw new ApplicationException("y2kbug!");
    }
}

Probar este método es especialmente problemático porque el programa depende de DateTime.Now, un método que depende del reloj del equipo, un método no determinista y dependiente del ambiente.Además, DateTime.Now es una propiedad estática por lo que no se puede utilizar aquí un tipo de código auxiliar.Este problema es sintomático del problema de aislamiento en las pruebas unitarias: programas que llaman directamente a la API de las bases de datos, se comunican con los servicios web, etc., son complejos para las pruebas unitarias porque su lógica depende del entorno.

Aquí es donde se deben utilizar las cuñas.Los tipos de cuñas proporcionan un mecanismo para desviar cualquier método .NET a un delegado definido por el usuario.Los tipos de cuñas son codificados por el generador de emulaciones y utilizan los delegados, que denominamos tipos de correcciones de compatibilidad, para especificar las nuevas implementaciones del método.

La prueba siguiente muestra cómo utilizar el tipo de cuña, ShimDateTime, para proporcionar una implementación personalizada de DateTime.Now:

//unit test code
// create a ShimsContext cleans up shims 
using (ShimsContext.Create()
    // hook delegate to the shim method to redirect DateTime.Now
    // to return January 1st of 2000
    ShimDateTime.NowGet = () => new DateTime(2000, 1, 1);
    Y2KChecker.Check();
}

Cómo utilizar Shims

Hh549176.collapse_all(es-es,VS.110).gifAgregar ensamblados de las falsificaciones

  1. En el explorador de soluciones, expanda References el proyecto de prueba unitaria.

    • Si está trabajando en Visual Basic, debe seleccionar mostrar todos los archivos de la barra de herramientas del explorador de soluciones, para ver la lista de referencias.
  2. Seleccione el ensamblado que contiene las definiciones de clases de las que desea crear las cuñas.Por ejemplo, si desea calzar DateTime, seleccione System.dll

  3. En el menú contextual, elija Agregue el ensamblado de las falsificaciones.

Hh549176.collapse_all(es-es,VS.110).gifUtilice ShimsContext

Al utilizar tipos de cuñas en un marco de pruebas unitarias, debe ajustar el código en un ShimsContext para controlar la duración de sus cuñas.Si no requiriéramos esto, las cuñas durarían hasta que el AppDomain se cerrara.La manera más fácil de crear un ShimsContext es mediante el método estático Create() tal y como se muestra en el siguiente código:

//unit test code
[Test]
public void Y2kCheckerTest() {
  using(ShimsContext.Create()) {
    ...
  } // clear all shims
}

Es fundamental desechar correctamente cada contexto de la cuña.En general, llame siempre al ShimsContext.Create dentro de una declaración using para garantizar el borrado adecuado de las cuñas registradas.Por ejemplo, podría registrar una cuña para un método de prueba que reemplaza al método DateTime.Now con un delegado que devuelve siempre el primero de enero de 2000.Si olvida borrar la cuña registrada en el método de prueba, el resto de la ejecución de pruebas devolvería siempre el primero de enero de 2000 como el valor de DateTime.Now.Esto podría ser sorprendente y confuso.

Hh549176.collapse_all(es-es,VS.110).gifEscriba una prueba con las cuñas

En el código de prueba, inserte un desvío para el método que desee imitar.Por ejemplo:

[TestClass]
public class TestClass1
{ 
        [TestMethod]
        public void TestCurrentYear()
        {
            int fixedYear = 2000;

            using (ShimsContext.Create())
            {
              // Arrange:
                // Detour DateTime.Now to return a fixed date:
                System.Fakes.ShimDateTime.NowGet = 
                () =>
                { return new DateTime(fixedYear, 1, 1); };

                // Instantiate the component under test:
                var componentUnderTest = new MyComponent();

              // Act:
                int year = componentUnderTest.GetTheCurrentYear();

              // Assert: 
                // This will always be true if the component is working:
                Assert.AreEqual(fixedYear, year);
            }
        }
}
<TestClass()> _
Public Class TestClass1
    <TestMethod()> _
    Public Sub TestCurrentYear()
        Using s = Microsoft.QualityTools.Testing.Fakes.ShimsContext.Create()
            Dim fixedYear As Integer = 2000
            ' Arrange:
            ' Detour DateTime.Now to return a fixed date:
            System.Fakes.ShimDateTime.NowGet = _
                Function() As DateTime
                    Return New DateTime(fixedYear, 1, 1)
                End Function

            ' Instantiate the component under test:
            Dim componentUnderTest = New MyComponent()
            ' Act:
            Dim year As Integer = componentUnderTest.GetTheCurrentYear
            ' Assert: 
            ' This will always be true if the component is working:
            Assert.AreEqual(fixedYear, year)
        End Using
    End Sub
End Class

Los nombres de clase de correcciones de compatibilidad son compuestos anteponiendo Fakes.Shim al nombre de tipo original.

Las cuñas funcionan incrustando desvíos en el código de la aplicación en pruebas.Una llamada al método original se produce siempre que, el sistema de Fakes realiza un desvío, en lugar de la llamada al método real, el código del código.

Observe que desvíos se crean y eliminar en tiempo de ejecución.Debe crear un desvío dentro de la vida de ShimsContext.Cuando se elimina, cualquier cuñas crearla mientras estaba activo se quita.La mejor manera de hacer esto está dentro de una instrucción de using.

Puede aparecer un error de compilación que decía que no existe el espacio de nombres Fakes.Este error aparece a veces cuando hay otros errores de compilación.Corrija otros errores y desaparecerá.

Cuñas para los diferentes tipos de métodos

Los tipos de correcciones de compatibilidad permiten reemplace cualquier método.NET, incluidos los métodos estáticos o métodos no virtuales, con los propios delegados.

Hh549176.collapse_all(es-es,VS.110).gifMétodos estáticos

Las propiedades para asociar las cuñas a los métodos estáticos se colocan en un tipo de corrección de compatibilidad.Cada propiedad tiene sólo un establecedor que puede utilizar para asociar un delegado al método de destino.Por ejemplo, dada una clase MyClass con un método estático MyMethod:

//code under test
public static class MyClass {
    public static int MyMethod() {
        ...
    }
}

Podemos adjuntar una cuña a MyMethod que siempre devuelve 5:

// unit test code
ShimMyClass.MyMethod = () =>5;

Hh549176.collapse_all(es-es,VS.110).gifMétodos de instancia (para todas las instancias)

De forma similar a los métodos estáticos, los métodos de instancia se pueden ser calzados para todas las instancias.Las propiedades para asociar dichas cuñas se colocan en un tipo anidado denominado AllInstances para evitar confusiones.Por ejemplo, dada una clase MyClass con un método de la instancia MyMethod:

// code under test
public class MyClass {
    public int MyMethod() {
        ...
    }
}

Puede adjuntar un correcciones de compatibilidad a MyMethod que siempre devuelve 5, independientemente de la instancia:

// unit test code
ShimMyClass.AllInstances.MyMethod = () => 5;

La estructura de tipos generada de ShimMyClass se parece al código siguiente:

// Fakes generated code
public class ShimMyClass : ShimBase<MyClass> {
    public static class AllInstances {
        public static Func<MyClass, int>MyMethod {
            set {
                ...
            }
        }
    }
}

Observe que el simulador pasa la instancia del runtime como primer argumento del delegado en este caso.

Hh549176.collapse_all(es-es,VS.110).gifMétodos de instancia (para una instancia del runtime)

Los métodos de instancia también se pueden calzar por varios delegados, según el receptor de la llamada.Esto permite al mismo método de instancia tener distintos comportamientos por instancia del tipo.Las propiedades para configurar esas cuñas son a su vez métodos instanciados del mismo tipo de corrección de compatibilidad.Cada tipo de cuña instanciada también esta asociada a una instancia sin formato de un tipo con cuña.

Por ejemplo, dada una clase MyClass con un método de la instancia MyMethod:

// code under test
public class MyClass {
    public int MyMethod() {
        ...
    }
}

Podemos configurar dos tipos de cuñas de MyMethod tales que el primero siempre devuelve 5 y el segundo siempre devuelve 10:

// unit test code
var myClass1 = new ShimMyClass()
{
    MyMethod = () => 5
};
var myClass2 = new ShimMyClass { MyMethod = () => 10 };

La estructura de tipos generada de ShimMyClass se parece al código siguiente:

// Fakes generated code
public class ShimMyClass : ShimBase<MyClass> {
    public Func<int> MyMethod {
        set {
            ...
        }
    }
    public MyClass Instance {
        get {
            ...
        }
    }
}

Se puede obtener acceso a la instancia real de tipo con cuña mediante la propiedad de instancia:

// unit test code
var shim = new ShimMyClass();
var instance = shim.Instance;

El tipo de cuña también tiene una conversión implícita al tipo con cuña, por lo que puede usar normalmente simplemente el tipo de cuña tal como es:

// unit test code
var shim = new ShimMyClass();
MyClass instance = shim; // implicit cast retrieves the runtime
                         // instance

Hh549176.collapse_all(es-es,VS.110).gifConstructores

Los constructores también pueden ser calzados para asociar tipos de calce a los objetos futuros.Cada constructor esta expuesto como un método estático Constructor en el tipo de cuña.Por ejemplo, dada una clase MyClass con un constructor que toma un entero:

// code under test
public class MyClass {
    public MyClass(int value) {
        this.Value = value;
    }
    ...
}

Configuramos el tipo de calce del constructor para que cada instancia futura devuelva -5 cuando se invoca al captador de valor, independientemente del valor en el constructor:

// unit test code
ShimMyClass.ConstructorInt32 = (@this, value) => {
    var shim = new ShimMyClass(@this) {
        ValueGet = () => -5
    };
};

Observe que cada tipo de correcciones de compatibilidad expone dos constructores.El constructor predeterminado se debe usar cuando una nueva instancia es necesaria, mientras que el constructor que toma una instancia calzada como argumento se utiliza solo en cuñas de constructor :

// unit test code
public ShimMyClass() { }
public ShimMyClass(MyClass instance) : base(instance) { }

La estructura generada del tipo de ShimMyClass se parece al siguiente código:

// Fakes generated code
public class ShimMyClass : ShimBase<MyClass>
{
    public static Action<MyClass, int> ConstructorInt32 {
        set {
            ...
        }
    }

    public ShimMyClass() { }
    public ShimMyClass(MyClass instance) : base(instance) { }
    ...
}

Hh549176.collapse_all(es-es,VS.110).gifMiembros base

Se puede obtener acceso a las propiedades de calce de los miembros base creando una cuña para el tipo base y pasando la instancia secundaria como un parámetro al constructor de la clase base del calce.

Por ejemplo, dada una clase MyBase con un método de instancia MyMethod y un subtipo MyChild:

public abstract class MyBase {
    public int MyMethod() {
        ...
    }
}

public class MyChild : MyBase {
}

Podemos configurar una cuña de MyBase creando un nuevo calce de ShimMyBase:

// unit test code
var child = new ShimMyChild();
new ShimMyBase(child) { MyMethod = () => 5 };

Observe que el tipo secundario de calce se convierte implícitamente en la instancia secundaria cuando se pasa como un parámetro al constructor base del calce.

La estructura de tipo generada de ShimMyChild y de ShimMyBase es similar al código siguiente:

// Fakes generated code
public class ShimMyChild : ShimBase<MyChild> {
    public ShimMyChild() { }
    public ShimMyChild(Child child)
        : base(child) { }
}
public class ShimMyBase : ShimBase<MyBase> {
    public ShimMyBase(Base target) { }
    public Func<int> MyMethod
    { set { ... } }
}

Hh549176.collapse_all(es-es,VS.110).gifConstructores estáticos

Los tipos de calce exponen un método estático StaticConstructor para calzar el constructor estático de un tipo.Puesto que los constructores estáticos se ejecutan sólo una vez, deberá asegurarse de que el calce está configurado antes de que se obtenga acceso a cualquier miembro de ese tipo.

Hh549176.collapse_all(es-es,VS.110).gifFinalizadores

Los finalizadores no se admiten en emulación.

Hh549176.collapse_all(es-es,VS.110).gifMétodos privados

El generador de código de emulación creará las propiedades de calce para los métodos privados que tienen sólo tipos visibles en la firma, los tipos de parámetro i.e. y el tipo de valor devuelto visible.

Hh549176.collapse_all(es-es,VS.110).gifEnlazar interfaces

Cuando un tipo calzado implementa una interfaz, el generador de código emite un método que permite enlazar a todos los miembros de esa interfaz inmediatamente.

Por ejemplo, dada una clase MyClass que implementa IEnumerable<int>:

public class MyClass : IEnumerable<int> {
    public IEnumerator<int> GetEnumerator() {
        ...
    }
    ...
}

Podemos calzar las implementaciones de IEnumerable<int> en MyClass llamando al método bind:

// unit test code
var shimMyClass = new ShimMyClass();
shimMyClass.Bind(new List<int> { 1, 2, 3 });

La estructura de tipo generada de ShimMyClass se parece al siguiente código:

// Fakes generated code
public class ShimMyClass : ShimBase<MyClass> {
    public ShimMyClass Bind(IEnumerable<int> target) {
        ...
    }
}

Cambiar el comportamiento predeterminado

Cada tipo de calce generado contiene una instancia de la interfaz IShimBehavior mediante la propiedad ShimBase<T>.InstanceBehavior.Se utiliza el comportamiento cuando un cliente llama a un miembro de instancia que no se ha calzado explícitamente.

Si el comportamiento no se ha establecido explícitamente, utiliza la instancia devuelta por la propiedad estática ShimsBehaviors.Current.De forma predeterminada, esta propiedad devuelve un comportamiento que produce una excepción de NotImplementedException.

El comportamiento se puede cambiar en cualquier momento estableciendo la propiedad InstanceBehavior en cualquier instancia de calce.Por ejemplo, el siguiente fragmento de código cambia la cuña a un comportamiento que no hace nada ni devuelve el valor predeterminado de retorno tipo- que es, valor predeterminado (T):

// unit test code
var shim = new ShimMyClass();
//return default(T) or do nothing
shim.InstanceBehavior = ShimsBehaviors.DefaultValue;

El comportamiento también se puede cambiar globalmente para todas las instancias calzadas para las que la propiedad InstanceBehavior no se estableció la propiedad explícitamente fijando la propiedad estática de ShimsBehaviors.Current:

// unit test code
// change default shim for all shim instances
// where the behavior has not been set
ShimsBehaviors.Current = 
    ShimsBehaviors.DefaultValue;

Detectar accesos al entorno

Es posible asociar un comportamiento a todos los miembros, incluidos los métodos estáticos, de un tipo determinado asignando el comportamiento ShimsBehaviors.NotImplemented a la propiedad estática Behavior del tipo de calce correspondiente:

// unit test code
// assigning the not implemented behavior
ShimMyClass.Behavior = ShimsBehaviors.NotImplemented;
// shorthand
ShimMyClass.BehaveAsNotImplemented();

Simultaneidad

Los tipos de calce se aplican a todos los subprocesos de AppDomain y no tienen afinidad con subprocesos.Esto es un hecho importante si piensa utilizar un ejecutor de pruebas que soporta pruebas de concurrencia: las pruebas que implican tipos de calce no se pueden ejecutar simultáneamente.Esta propiedad no la cumple el runtime de emulación.

Llamar al método original del método de calce

Imagine que deseamos escribir realmente el texto para el sistema de archivos después de validar el nombre de archivo pasado al método.En ese caso, desearíamos llamar al método original en medio del método de calce.

El primer enfoque para solucionar este problema es incluir una llamada al método original utilizando un delegado y ShimsContext.ExecuteWithoutShims() como en el código siguiente:

// unit test code
ShimFile.WriteAllTextStringString = (fileName, content) => {
  ShimsContext.ExecuteWithoutShims(() => {

      Console.WriteLine("enter");
      File.WriteAllText(fileName, content);
      Console.WriteLine("leave");
  });
};

Otro enfoque es establecer la cuña en null, llamar al método original y restaurar el calce.

// unit test code
ShimsDelegates.Action<string, string> shim = null;
shim = (fileName, content) => {
  try {
    Console.WriteLine("enter”);
    // remove shim in order to call original method
    ShimFile.WriteAllTextStringString = null;
    File.WriteAllText(fileName, content);
  }
  finally
  {
    // restore shim
    ShimFile.WriteAllTextStringString = shim;
    Console.WriteLine("leave");
  }
};
// initialize the shim
ShimFile.WriteAllTextStringString = shim;

Limitaciones

Las cuñas no se pueden utilizar con todos los tipos de las bibliotecas mscorlib y Sistemade clases base de.NET.

Recursos Externos

Hh549176.collapse_all(es-es,VS.110).gifGuía

Prueba para la entrega continuo con Visual Studio 2012 – Capítulo 2: Pruebas unitarias: Probando el interior

Vea también

Conceptos

Aislar el código probado con Microsoft Fakes

Otros recursos

Blog de Peter Provost: Visual Studio 2012 cuñas

Vídeo (1h16): Código O.N.U-comprobable de prueba con Fakes en Visual Studio 2012