Idioma

Usar stubs para isolar partes do aplicativo umas das outras para teste de unidade

Os tipos de stub são uma tecnologia importante fornecida pela estrutura do Microsoft Fakes, permitindo o fácil isolamento do componente em que você está testando de outros componentes de que ele depende. Um stub atua como uma pequena parte do código que substitui outro componente durante o teste. Um dos principais benefícios do uso de stubs é a capacidade de obter resultados consistentes para facilitar a escrita de testes. Mesmo que os outros componentes ainda não estejam totalmente funcionais, você ainda poderá executar testes usando stubs.

Para aplicar stubs com eficiência, é recomendável projetar seu componente de uma maneira que ele dependa principalmente de interfaces em vez de classes concretas de outras partes do aplicativo. Essa abordagem de design promove a desassociação e reduz a probabilidade de alterações em uma parte que exige modificações em outra. Quando se trata de teste, esse padrão de design permite substituir uma implementação de stub por um componente real, facilitando o isolamento efetivo e o teste preciso do componente de destino.

Por exemplo, vamos considerar o diagrama que ilustra os componentes envolvidos:

Diagrama de classes Real e Stub de StockAnalyzer.

Neste diagrama, o componente em teste é StockAnalyzer, que normalmente depende de outro componente chamado RealStockFeed. No entanto, RealStockFeed representa um desafio para o teste porque retorna resultados diferentes cada vez que seus métodos são chamados. Essa variabilidade dificulta a garantia de testes consistentes e confiáveis de StockAnalyzer.

Para superar esse obstáculo durante o teste, podemos adotar a prática de injeção de dependência. Essa abordagem envolve escrever seu código de forma que ele não mencione explicitamente classes em outro componente do seu aplicativo. Em vez disso, você define uma interface que o outro componente e um stub podem implementar para fins de teste.

Aqui está um exemplo de como você pode usar a injeção de dependência em seu código:

public int GetContosoPrice(IStockFeed feed) => feed.GetSharePrice("COOO");

Limitações de stub

Examine as limitações a seguir para stubs.

Criando um Stub: Um Guia Passo a Passo

Vamos iniciar este exercício com um exemplo motivador: aquele mostrado no diagrama anterior.

Criar uma biblioteca de classes

Siga estas etapas para criar uma biblioteca de classes.

  1. Abra o Visual Studio e crie um projeto da Biblioteca de Classes .

    Captura de tela do projeto biblioteca de classes no Visual Studio.

  2. Configure os atributos do projeto:

    • Defina o nome do projeto como StockAnalysis.
    • Defina o nome da solução como StubsTutorial.
    • Defina a estrutura de destino do projeto como .NET 8.0 ou posterior.
  3. Exclua o arquivo Class1.cs padrão.

  4. Adicione um novo arquivo chamado IStockFeed.cs e copie na seguinte definição de interface:

    // IStockFeed.cs
    public interface IStockFeed
    {
        int GetSharePrice(string company);
    }
    
  5. Adicione outro novo arquivo chamado StockAnalyzer.cs e copie na seguinte definição de classe:

    // StockAnalyzer.cs
    public class StockAnalyzer
    {
        private IStockFeed stockFeed;
        public StockAnalyzer(IStockFeed feed)
        {
            stockFeed = feed;
        }
        public int GetContosoPrice()
        {
            return stockFeed.GetSharePrice("COOO");
        }
    }
    

Criar um projeto de teste

Crie o projeto de teste para o exercício.

  1. Clique com o botão direito do mouse na solução e adicione um novo projeto chamado MsTest Test Project.

  2. Defina o nome do projeto como TestProject.

  3. Defina a estrutura de destino do projeto como .NET 8.0 ou posterior.

    Captura de tela do projeto de teste no Visual Studio.

Adicionar Fakes assembly

Adicione o assembly 'Fakes' ao projeto.

  1. Adicionar uma referência de projeto a StockAnalyzer.

    Captura de tela do comando Adicionar Referência do Projeto.

  2. Adicione a biblioteca Fakes.

    1. No Gerenciador de Soluções, localize a referência do assembly:

      • Para um projeto mais antigo do .NET Framework (estilo não SDK), expanda o nó Referências do projeto de teste de unidade.

      • Para um projeto no estilo SDK direcionado ao .NET Framework, .NET Core ou .NET 5.0 ou posterior, expanda o nó Dependências para localizar o assembly que você deseja simular em Assemblies, Projetos ou Pacotes.

      • Se você estiver trabalhando no Visual Basic, selecione Mostrar Todos os Arquivos na barra de ferramentas do Gerenciador de Soluções para ver o nó Referências .

    2. Selecione o assembly que contém as definições de classe para as quais você deseja criar stubs.

    3. No menu de atalho, selecione Adicionar Fakes Assembly.

      Captura de tela do comando Add Fakes Assembly.

Criar um teste de unidade

Agora, crie o teste de unidade.

  1. Modifique o arquivo padrão UnitTest1.cs para adicionar a definição a seguir Test Method .

    [TestClass]
    class UnitTest1
    {
        [TestMethod]
        public void TestContosoPrice()
        {
            // Arrange:
            int priceToReturn = 345;
            string companyCodeUsed = "";
            var componentUnderTest = new StockAnalyzer(new StockAnalysis.Fakes.StubIStockFeed()
            {
                GetSharePriceString = (company) =>
                {
                    // Store the parameter value:
                    companyCodeUsed = company;
                    // Return the value prescribed by this test:
                    return priceToReturn;
                }
            });
    
            // Act:
            int actualResult = 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);
        }
    }
    

    A peça especial de magia aqui é a StubIStockFeed classe. Para cada interface no assembly referenciado, o mecanismo Microsoft Fakes gera uma classe stub. O nome da classe stub é derivado do nome da interface, com "Fakes.Stub" como um prefixo e os nomes de tipo de parâmetro acrescentados.

    Os stubs também são gerados para os getters e setters de propriedades, para eventos e para métodos genéricos. Para obter mais informações, consulte Usar stubs para isolar partes do aplicativo umas das outras para teste de unidade.

    Captura de tela do Gerenciador de Soluções mostrando todos os arquivos.

  2. Abra o Gerenciador de Testes e execute o teste.

    captura de tela do Gerenciador de Testes.

Stubs para diferentes categorias de componentes de tipo

Há stubs para diferentes tipos de membros.

Methods

No exemplo fornecido, os métodos podem ser usados anexando um delegado a uma instância da classe stub. O nome do tipo stub é derivado dos nomes do método e dos parâmetros. Por exemplo, considere a seguinte IStockFeed interface e seu método GetSharePrice:

// IStockFeed.cs
interface IStockFeed
{
    int GetSharePrice(string company);
}

Anexamos um stub a GetSharePrice usando GetSharePriceString:

// unit test code
var componentUnderTest = new StockAnalyzer(new StockAnalysis.Fakes.StubIStockFeed()
        {
            GetSharePriceString = (company) =>
            {
                // Store the parameter value:
                companyCodeUsed = company;
                // Return the value prescribed by this test:
                return priceToReturn;
            }
        });

Se você não fornecer um stub para um método, o Fakes gerará uma função que retorna o default value tipo de retorno. Para números, o valor padrão é 0. Para tipos de classe, o padrão está null em C# ou Nothing no Visual Basic.

Propriedades

Os métodos de acesso (getters) e modificação (setters) de propriedades são expostos como delegados separados e podem ser substituídos individualmente. Por exemplo, considere a Value propriedade de IStockFeedWithProperty:

interface IStockFeedWithProperty
{
    int Value { get; set; }
}

Para criar um stub para o getter e o Value setter e simular uma propriedade automática, você pode usar o seguinte código:

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

Se você não fornecer métodos stub para o setter ou o getter de uma propriedade, o Fakes gerará um stub que armazena valores, fazendo com que a propriedade stub se comporte como uma variável simples.

Eventos

Os eventos são expostos como campos delegados, permitindo que qualquer evento simulado seja gerado simplesmente invocando o campo de backup do evento. Vamos considerar a seguinte interface para fazer um stub:

interface IStockFeedWithEvents
{
    event EventHandler Changed;
}

Para acionar o Changed evento, invoque o delegado de suporte:

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

Métodos genéricos

Você pode fazer stub de métodos genéricos fornecendo um delegado para cada instanciação desejada do método. Por exemplo, dada a seguinte interface com um método genérico:

interface IGenericMethod
{
    T GetValue<T>();
}

Você pode stubar a instanciação GetValue<int> da seguinte maneira:

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

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

Se o código chamar GetValue<T> com qualquer outra instanciação, o stub executará o comportamento.

Stubs de classes virtuais

Nos exemplos anteriores, os stubs foram gerados a partir de interfaces. No entanto, você também pode gerar stubs de uma classe que tem membros virtuais ou abstratos. Por exemplo:

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

No stub gerado a partir dessa classe, você pode definir métodos delegados para DoAbstract() e DoVirtual(), mas não DoConcrete().

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

Se você não fornecer um delegado para um método virtual, o Fakes poderá fornecer o comportamento padrão ou chamar o método na classe base. Para ter o método base chamado, defina a CallBase propriedade:

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

Alterar o comportamento padrão de stubs

Cada tipo de stub gerado contém uma instância da interface IStubBehavior por meio da propriedade IStub.InstanceBehavior. Esse comportamento é chamado sempre que um cliente chama um membro sem nenhum delegado personalizado anexado. Se o comportamento não estiver definido, ele usará a instância retornada pela StubsBehaviors.Current propriedade. Por padrão, essa propriedade retorna um comportamento que gera uma NotImplementedException exceção.

Você pode alterar o comportamento a qualquer momento definindo a InstanceBehavior propriedade em qualquer instância de stub. Por exemplo, o código a seguir altera o comportamento para que o stub não faça nada ou retorne o valor padrão do tipo de retorno default(T):

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

O comportamento também pode ser alterado globalmente para todos os objetos stub em que o comportamento não está definido com a StubsBehaviors.Current propriedade:

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