Share via


Use stubs para isolar partes de seu aplicativo umas das outras para teste de unidade

Os tipos de stub são uma tecnologia importante fornecida pela estrutura Microsoft Fakes, permitindo o isolamento fácil do componente que você está testando de outros componentes dos quais ele depende. Um stub funciona como um pequeno trecho de 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 do teste. Mesmo que os outros componentes ainda não estejam totalmente funcionais, você ainda poderá executar testes usando stubs.

Para aplicar os 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 desacoplamento e reduz a probabilidade de alterações em uma parte que exigem 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:

Diagram of Real and Stub classes of 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 do 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 classes explicitamente em outro componente do aplicativo. Em vez disso, defina uma interface que possa ser implementada pelo outro componente e por um stub 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 stubs

Analise as limitações a seguir para stubs.

Criando um stub: um guia passo a passo

Vamos começar com um exemplo motivador: o que é 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 de Biblioteca de classes.

    Screenshot of Class Library project in 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.
  3. Exclua o arquivo padrão Class1.cs.

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

    // IStockFeed.cs
    public interface IStockFeed
    {
        int GetSharePrice(string company);
    }
    
  5. Adicione outro novo arquivo chamado StockAnalyzer.cs e copie a 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 Projeto de teste MSTest.

  2. Defina o nome do projeto como TestProject.

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

    Screenshot of Test project in Visual Studio.

Adicionar assembly do Fakes

Adicione o assembly do Fakes para o projeto.

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

    Screenshot of the command Add Project Reference.

  2. Adicionar um assembly do Fakes.

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

      • Para um projeto de .NET Framework mais antigo (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.

      • Caso esteja 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 deseja criar stubs.

    3. No menu de atalhos, selecione Adicionar Assembly do Fakes.

      Screenshot of the command 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 Test Method a seguir.

    [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);
        }
    }
    

    E a mágica especial é que aqui está a classe StubIStockFeed. Para cada interface no assembly referenciado, o mecanismo Microsoft Fakes gera uma classe de stub. O nome da classe de stub é derivado do nome da interface, com "Fakes.Stub" como prefixo e os nomes dos tipos de parâmetro anexados.

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

    Screenshot of Solution Explorer showing all files.

  2. Abra Explorador de Teste e execute o teste.

    Screenshot of Test Explorer.

Stubs para tipos diferentes de membros de tipo

Há stubs para tipos diferentes de membros de tipo.

Métodos

No exemplo fornecido, é possível fazer o stub dos métodos anexando um delegado a uma instância da classe stub. O nome do tipo de stub é derivado dos nomes do método e dos parâmetros. Por exemplo, considere a interface IStockFeed a seguir e o seu método GetSharePrice:

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

Anexamos um stub ao 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 gera uma função que retorna o default value do tipo de retorno. Para números, o valor padrão é 0. Para tipos de classe, o padrão é null em C# ou Nothing no Visual Basic.

Propriedades

Os getters e setters de propriedade são expostos como delegados separados e podem passar por stub individualmente. Por exemplo, considere a propriedade Value de IStockFeedWithProperty:

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

Para fazer stub do getter e do setter de Value 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 gera um stub que armazena valores, fazendo a propriedade stub funcionar como uma variável simples.

Eventos

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

interface IStockFeedWithEvents
{
    event EventHandler Changed;
}

Para gerar o evento Changed, você invoca 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 o stub de métodos genéricos fornecendo um delegado para cada criação de instância desejada do método. Por exemplo, dada a seguinte interface com um método genérico:

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

Você pode criar um stub da 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 a partir de uma classe que tenha 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 propriedade 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));

Alteração do comportamento padrão de stubs

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

Você pode alterar o comportamento a qualquer momento definindo a propriedade InstanceBehavior em qualquer instância do stub. Por exemplo, o seguinte snippet 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 modificado globalmente para todos os objetos stub em que o comportamento não foi definido com a propriedade StubsBehaviors.Current:

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