Usar stubs para aislar las partes de la aplicación entre sí para las pruebas unitarias

Los tipos de código auxiliar son una de dos tecnologías que el marco de Microsoft Fakes proporciona para permitir fácilmente aislar un componente que está probando de otros componentes que llame a.Un código auxiliar es un fragmento de código que toma el lugar de otro componente durante la prueba.La ventaja de utilizar un código auxiliar es que devuelve los resultados coherentes, crear la prueba más fácil escribir.Y puede ejecutar pruebas aun cuando no estén trabajando los otros componentes todavía.

Para obtener información general y un guía de inicio rápido a Fakes, vea Aislar el código probado con Microsoft Fakes.

Para utilizar códigos auxiliares, tiene que escribir el componente de modo que solo utiliza interfaces, no clases, para hacer referencia a otras partes de la aplicación.Esto es conveniente de diseño porque realiza cambios en una parte menos probable requerir cambios en otra.Para probar, permite sustituir un código auxiliar para un componente real.

En el diagrama, el StockAnalyzer componente es el que deseamos probar.Utiliza normalmente a otro componente, RealStockFeed.Pero RealStockFeed devuelve resultados diferentes cada vez que se llaman a sus métodos, lo que hace que sea difícil probar StockAnalyzer.Durante la prueba, la se reemplaza con otra clase, StubStockFeed.

Clases Real y Stub según una interfaz.

Dado que los códigos auxiliares dependen del eficaz para estructurar el código de esta manera, normalmente utiliza código auxiliar para aislar una parte de la aplicación de otra.Para aislarla de otros ensamblados que no están bajo el control, como System.dll, utilizaría normalmente las cuñas.Vea Usar correcciones de compatibilidad (shim) para aislar la aplicación de otros ensamblados para las pruebas unitarias.

Requisitos

  • Visual Studio Ultimate

En este tema

Cómo utilizar códigos auxiliares

Hh549174.collapse_all(es-es,VS.110).gifDiseño para la inyección de dependencia

Para utilizar códigos auxiliares, la aplicación tiene que diseñar de modo que los distintos componentes no dependen en sí, pero sólo dependiente en definiciones de interfaz.En lugar de acoplarse en tiempo de compilación, los componentes están conectados en tiempo de ejecución.Este modelo ayuda a crear software que es eficaz y fácil de actualizar, porque los cambios suelen no propagarse a través de límites componentes.Se recomienda el seguimiento aunque no utiliza códigos auxiliares.Si está escribiendo un nuevo código, es fácil seguir inyección de dependencia el modelo.Si está escribiendo las pruebas para el software existente, quizá tenga que refactorizar él.Si eso no sería práctico, podría utilizar las cuñas en su lugar.

Comencemos esta junto con un ejemplo de la función, el del diagrama.La clase StockAnalyzer lee el precio de las acciones y genera algunos resultados interesantes.Tiene algunos métodos públicos, que deseamos probar.Para mantengamos cosas simples, solo tienen uno de esos métodos, muy simple que notifica el precio actual de una acción determinada.Es deseable escribir una prueba unitaria de ese método.A continuación se muestra el primer esbozo de una prueba:

        [TestMethod]
        public void TestMethod1()
        {
            // Arrange:
            var analyzer = new StockAnalyzer();
            // Act:
            var result = analyzer.GetContosoPrice();
            // Assert:
            Assert.AreEqual(123, result); // Why 123?
        }
    <TestMethod()> Public Sub TestMethod1()
        ' Arrange:
        Dim analyzer = New StockAnalyzer()
        ' Act:
        Dim result = analyzer.GetContosoPrice()
        ' Assert:
        Assert.AreEqual(123, result) ' Why 123?
    End Sub

Un problema con esta prueba está claro: los precios de las acciones varían, por lo que la aserción producirá un error general.

Otro problema podría ser que el componente de StockFeed, que es utilizado por el StockAnalyzer, todavía está en desarrollo.A continuación se muestra el primer esbozo de código del método en pruebas:

        public int GetContosoPrice()
        {
            var stockFeed = new StockFeed(); // NOT RECOMMENDED
            return stockFeed.GetSharePrice("COOO");
        }
    Public Function GetContosoPrice()
        Dim stockFeed = New StockFeed() ' NOT RECOMMENDED
        Return stockFeed.GetSharePrice("COOO")
    End Function

Tal y como están las cosas, este método no puede compilar ni podría producir una excepción porque el trabajo en la clase de StockFeed todavía no se ha completado.

Inserción de la interfaz trate ambos problemas.

La inyección de interfaz se aplica la regla siguiente:

  • El código de cualquier componente de aplicación nunca debe hacer referencia explícita a una clase de otro componente, en una declaración o en una instrucción de new.En su lugar, las variables y los parámetros se deben declarar con interfaces.Instancias de componentes deben crear solamente por el contenedor del componente.

    Por “componente” en este caso se una clase, o a un grupo de clases desarrollar y actualice juntas.Normalmente, un componente es el código en un proyecto de Visual Studio.Es menos importante desacoplar clases de un componente, porque se actualizan al mismo tiempo.

    Tampoco es tan importante desacoplar componentes de las clases de una plataforma relativamente estable como System.dll.Las interfaces de escritura para todas estas clases estorbarían el código.

El código de StockAnalyzer puede ser por consiguiente mejorar desemparejándolo de StockFeed mediante una interfaz como ésta:

    public interface IStockFeed
    {
        int GetSharePrice(string company);
    }

    public class StockAnalyzer
    {
        private IStockFeed stockFeed;
        public Analyzer(IStockFeed feed)
        {
            stockFeed = feed;
        }
        public int GetContosoPrice()
        {
            return stockFeed.GetSharePrice("COOO");
        }
    }
Public Interface IStockFeed
    Function GetSharePrice(company As String) As Integer
End Interface

Public Class StockAnalyzer
    ' StockAnalyzer can be connected to any IStockFeed:
    Private stockFeed As IStockFeed
    Public Sub New(feed As IStockFeed)
        stockFeed = feed
    End Sub  
    Public Function GetContosoPrice()
        Return stockFeed.GetSharePrice("COOO")
    End Function
End Class

En este ejemplo, StockAnalyzer se pasa una implementación de un IStockFeed cuando se construye.En la aplicación completa, el código de inicialización efectuaría la conexión:

analyzer = new StockAnalyzer(new StockFeed())

Hay maneras más flexibles de realizar esta conexión.Por ejemplo, StockAnalyzer podría aceptar un objeto generador que puede crear instancias diferentes implementaciones de IStockFeed en condiciones diferentes.

Hh549174.collapse_all(es-es,VS.110).gifGenere los códigos auxiliares

Ha desacoplado la clase que desea probar de los componentes que utiliza.Así como crear la aplicación más eficaz y flexible, se desacoplan le permite conectar el componente en pruebas a las aplicaciones de código auxiliar de interfaces de evaluación.

Podría escribir simplemente códigos auxiliares como clases de la forma habitual.Pero Microsoft Fakes le proporciona una manera más dinámica de crear el código auxiliar más adecuado para cada prueba.

Para utilizar códigos auxiliares, primero debe generar tipos de código auxiliar de las definiciones de interfaz.

Agregar un ensamblado de Fakes

  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 interfaz para las que desea crear códigos auxiliares.

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

Hh549174.collapse_all(es-es,VS.110).gifEscriba la prueba con códigos auxiliares

[TestClass]
class TestStockAnalyzer
{
    [TestMethod]
    public void TestContosoStockPrice()
    {
      // Arrange:

        // Create the fake stockFeed:
        IStockFeed stockFeed = 
             new StockAnalysis.Fakes.StubIStockFeed() // Generated by Fakes.
                 {
                     // Define each method:
                     // Name is original name + parameter types:
                     GetSharePriceString = (company) => { return 1234; }
                 };

        // In the completed application, stockFeed would be a real one:
        var componentUnderTest = new StockAnalyzer(stockFeed);

      // Act:
        int actualValue = componentUnderTest.GetContosoPrice();

      // Assert:
        Assert.AreEqual(1234, actualValue);
    }
    ...
}
<TestClass()> _
Class TestStockAnalyzer

    <TestMethod()> _
    Public Sub TestContosoStockPrice()
        ' Arrange:
        ' Create the fake stockFeed:
        Dim stockFeed As New StockAnalysis.Fakes.StubIStockFeed
        With stockFeed
            .GetSharePriceString = Function(company)
                                       Return 1234
                                   End Function
        End With
        ' In the completed application, stockFeed would be a real one:
        Dim componentUnderTest As New StockAnalyzer(stockFeed)
        ' Act:
        Dim actualValue As Integer = componentUnderTest.GetContosoPrice
        ' Assert:
        Assert.AreEqual(1234, actualValue)
    End Sub
End Class

El fragmento especial de magia aquí es la clase StubIStockFeed.Para cada tipo público en el ensamblado hace referencia, el mecanismo de Microsoft Fakes genera una clase de código auxiliar.El nombre de la clase de código auxiliar es los nombres derivados del nombre de la interfaz, con “Fakes.Stub” como prefijo, y el tipo de parámetro anexados.

Los códigos auxiliares también se generan para captadores y los establecedores de propiedades, para los eventos, y para métodos genéricos.

Hh549174.collapse_all(es-es,VS.110).gifComprobar valores de parámetro

Puede comprobar que cuando el componente realiza una llamada a otro componente, pase los valores correctos.Puede o colocar una aserción en el código auxiliar, o almacenar el valor y comprobar en el cuerpo principal de la prueba.Por ejemplo:

[TestClass]
class TestMyComponent
{
       
    [TestMethod]
    public void TestVariableContosoPrice()
    {
     // Arrange:
        int priceToReturn;
        string companyCodeUsed;
        var componentUnderTest = new StockAnalyzer(new StubIStockFeed()
            {
               GetSharePriceString = (company) => 
                  { 
                     // Store the parameter value:
                     companyCodeUsed = company;
                     // Return the value prescribed by this test:
                     return priceToReturn;
                  };
            };
        // Set the value that will be returned by the stub:
        priceToReturn = 345;

     // Act:
        int actualResult = componentUnderTest.GetContosoPrice(priceToReturn);

     // Assert:
        // Verify the correct result in the usual way:
        Assert.AreEqual(priceToReturn, actualResult);

        // Verify that the component made the correct call:
        Assert.AreEqual("COOO", companyCodeUsed);
    }
...}
<TestClass()> _
Class TestMyComponent
    <TestMethod()> _
    Public Sub TestVariableContosoPrice()
        ' Arrange:
        Dim priceToReturn As Integer
        Dim companyCodeUsed As String = ""
        Dim stockFeed As New StockAnalysis.Fakes.StubIStockFeed()
        With stockFeed
            ' Implement the interface's method:
            .GetSharePriceString = _
                Function(company)
                    ' Store the parameter value:
                    companyCodeUsed = company
                    ' Return a fixed result:
                    Return priceToReturn
                End Function
        End With
        ' Create an object to test:
        Dim componentUnderTest As New StockAnalyzer(stockFeed)
        ' Set the value that will be returned by the stub:
        priceToReturn = 345

        ' Act:
        Dim actualResult As Integer = componentUnderTest.GetContosoPrice()

        ' Assert:
        ' Verify the correct result in the usual way:
        Assert.AreEqual(priceToReturn, actualResult)
        ' Verify that the component made the correct call:
        Assert.AreEqual("COOO", companyCodeUsed)
    End Sub
...
End Class

Códigos auxiliares para los diferentes tipos de miembros de tipo

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

Como se describe en el ejemplo, llamar a métodos tropezados asociar un delegado a una instancia de la clase de código auxiliar.El nombre del tipo de código auxiliar se deriva de los nombres de método y los parámetros.Por ejemplo, dada la siguiente interfaz IMyInterface y el método MyMethod:

// application under test
interface IMyInterface 
{
    int MyMethod(string value);
}

Adjuntamos un código auxiliar a MyMethod que devuelve siempre 1:

// unit test code
  var stub = new StubIMyInterface ();
  stub.MyMethodString = (value) => 1;

Si no proporciona un código auxiliar para una función, Fakes generará una función que devuelve el valor predeterminado del tipo de valor devuelto.Para los números, el valor predeterminado es 0, y para los tipos de clase es null (C#) o Nothing (Visual Basic).

Hh549174.collapse_all(es-es,VS.110).gifPropiedades

Los captadores y establecedores de propiedad se exponen como delegados separados y pueden fragmentarse por separado.Por ejemplo, considere la propiedad Value de IMyInterface:

// code under test
interface IMyInterface 
{
    int Value { get; set; }
}

Adjuntamos delegados en los métodos captadores y establecedores de Value para simular una autopropiedad:

// unit test code
int i = 5;
var stub = new StubIMyInterface();
stub.ValueGet = () => i;
stub.ValueSet = (value) => i = value;

Si no proporciona métodos de código auxiliar para el método set o el captador de una propiedad, Fakes generará un código auxiliar que almacena valores, de modo que la propiedad de código auxiliar funcione como una variable simple.

Hh549174.collapse_all(es-es,VS.110).gifEventos

Los eventos se exponen como campos de delegado.Como resultado, cualquier evento fragmentado puede activarse simplemente invocando el campo de respaldo de eventos.Veamos la interfaz siguiente que se fragmenta:

// code under test
interface IWithEvents 
{
    event EventHandler Changed;
}

Para generar el evento Changed, invocamos simplemente el delegado de respaldo:

// unit test code
  var withEvents = new StubIWithEvents();
  // raising Changed
  withEvents.ChangedEvent(withEvents, EventArgs.Empty);

Hh549174.collapse_all(es-es,VS.110).gifMétodos genéricos

Es posible fragmentar métodos genéricos proporcionando un delegado para cada instancia deseada del método.Por ejemplo, dada la siguiente interfaz que contiene un método genérico:

// code under test
interface IGenericMethod 
{
    T GetValue<T>();
}

puede escribir una prueba que tropieza la creación de GetValue<int>:

// unit test code
[TestMethod]
public void TestGetValue() 
{
    var stub = new StubIGenericMethod();
    stub.GetValueOf1<int>(() => 5);

    IGenericMethod target = stub;
    Assert.AreEqual(5, target.GetValue<int>());
}

Si el código externo llamar GetValue<T> con cualquier otra instancia, el código auxiliar llamaría simplemente el comportamiento.

Hh549174.collapse_all(es-es,VS.110).gifCódigos auxiliares de clases virtuales

En los ejemplos anteriores, los códigos auxiliares se han generado de interfaces.También puede generar código auxiliar de una clase que tiene miembros virtuales o abstractos.Por ejemplo:

// Base class in application under test
    public abstract class MyClass
    {
        public abstract void DoAbstract(string x);
        public virtual int DoVirtual(int n)
        { return n + 42; }
        public int DoConcrete()
        { return 1; }
    }

En el código auxiliar generado de esta clase, puede establecer los métodos delegados para DoAbstract() y DoVirtual(), pero no DoConcrete().

// unit test
  var stub = new Fakes.MyClass();
  stub.DoAbstractString = (x) => { Assert.IsTrue(x>0); };
  stub.DoVirtualInt32 = (n) => 10 ;
  

Si no proporciona un delegado para un método virtual, Fakes puede o proporcionar el comportamiento predeterminado, o puede llamar al método en la clase base.Para hacer el método base llamar, establezca la propiedad de CallBase:

// unit test code
var stub = new Fakes.MyClass();
stub.CallBase = false;
// No delegate set – default delegate:
Assert.AreEqual(0, stub.DoVirtual(1));

stub.CallBase = true;
//No delegate set - calls the base:
Assert.AreEqual(43,stub.DoVirtual(1));

Códigos auxiliares de depuración

Los tipos de código auxiliar se diseñan para proporcionar una depuración fluida.De forma predeterminada, el depurador pasa por alto cualquier código generado, por lo que se debe pasar directamente a las implementaciones personalizadas del miembro que estaban asociadas al código auxiliar.

Limitaciones del código auxiliar

  1. Las firmas de método con punteros no se admiten.

  2. Las clases o métodos estáticos sellados no pueden fragmentarse porque los tipos de código auxiliar dependen del envío del método virtual.Para estos casos, utilice tipos de correcciones de compatibilidad como se describe en Usar correcciones de compatibilidad (shim) para aislar la aplicación de otros ensamblados para las pruebas unitarias

Cambiar el comportamiento predeterminado de códigos auxiliares

Cada tipo generado de código auxiliar contiene una instancia de la interfaz IStubBehavior (mediante la propiedad IStub.InstanceBehavior ).Se llama al comportamiento cuando un cliente llama a un miembro sin delegado personalizado asociado.Si el comportamiento no se ha establecido, se utiliza la instancia devuelta por la propiedad StubsBehaviors.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 código auxiliar.Por ejemplo, el siguiente fragmento cambia un comportamiento que no hace nada ni devuelve el valor predeterminado del tipo de valor devuelto: default(T):

// unit test code
var stub = new StubIFileSystem();
// return default(T) or do nothing
stub.InstanceBehavior = StubsBehaviors.DefaultValue;

El comportamiento también se puede cambiar globalmente para todos los objetos de código auxiliar para los que el comportamiento no se ha establecido mediante la propiedad StubsBehaviors.Current:

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

Recursos Externos

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