VENDAS: 1-800-867-1389

Orientação sobre testes eficientes de Soluções do Azure

Atualizado: janeiro de 2015

Autor: Suren Machiraju

Revisores: Jaime Alva Bravo e Steve Wilkins

Às vezes, após desenvolver, codificar e implantar sua solução do Azure, você percebe que ela não funciona. Este artigo fornece orientação sobre como testar seus aplicativos do Azure durante todo o ciclo de vida de desenvolvimento do software. O escopo de teste inclui a lógica de negócios e os testes abrangentes de cenário fim a fim. Este artigo demonstra como:

  • Desenvolver testes de unidade para os componentes da lógica de negócios enquanto elimina dependências em componentes do Azure.

  • Desenvolver testes fim a fim de integração.

  • Eliminar sobrecarga para configuração, inicialização, limpeza e desmontagem dos recursos do Serviço Azure (por exemplo, Filas) de toda execução de teste.

  • Eliminar a criação de infraestruturas duplicadas para namespaces ACS, Filas de Barramento de Serviço e outros recursos.

Além disso, este artigo fornece uma visão geral das várias tecnologias e técnicas de teste para testar seus aplicativos do Azure.

As alterações a seguir são novas neste artigo:

  1. Usamos o Visual Studio 2013.

  2. Microsoft Fakes no Visual Studio 2013 substitui o Moles. Descrevemos esse recurso no artigo "Isolamento de código sob teste com o Microsoft Fakes".

  3. No Visual Studio 2013, uma nova versão de Cobertura de Código e é acessada diretamente no Gerenciador de Testes. Para a descrição, leia o artigo “Usando cobertura de código para determinar quanto código está sendo testado”.

  4. A equipe Pex lançou agora uma versão leve do Pex chamado Code Digger. Para obter uma descrição, consulte o artigo “Microsoft Code Digger”.

  5. Começando com o Visual Studio 2012, não recomendamos testes com métodos privados. Para mais explicações, leia “Minha experiência em métodos privados de testes de unidade”.

Há dois tipos de testes:

  • Testes de unidades são testes estreitamente focados que têm uma função única e específica. Esses testes são mencionados como os Códigos sob Teste ou CUT. Você deve remover todas as dependências exigidas pelo CUT.

  • Testes de Integração são testes mais amplos que exercitam vários bits de funcionalidade simultaneamente. Em muitos casos, eles se parecem com testes de unidade, mas cobrem várias áreas de recursos e incluem várias dependências.

o geral, esses testes se concentram na criação e no uso de Duplas de Testes. Nós usamos os seguintes tipos de Duplas de Testes:

  • Fakes são objetos simulados que implementam a mesma interface do objeto que representam. Fakes retornam respostas predefinidas. Um Fake contém um conjunto de stubs de método e serve como substituição a o que você cria programaticamente.

  • Stubs simulam o comportamento de objetos de software.

  • Shims permitem que você isole seu código de assemblies que não fazem parte de sua solução. Eles também isolam os componentes de sua solução.

Quando executados, esses testes podem verificar o estado e o comportamento. Por exemplo, o estado inclui o resultado após chamar um método e retornar um valor específico. Um exemplo de comportamento é chamar um método em uma determinada ordem ou um determinado número de vezes.

Uma das principais metas do teste de unidade é eliminar dependências. Para a estrutura do Azure, essas dependências incluem o seguinte:

  • Filas do barramento de serviço.

  • Serviço de Controle de Acesso.

  • Cache.

  • Tabelas, Blobs e Filas do Azure.

  • Banco de dados SQL do Azure.

  • Unidade Azure (anteriormente, Unidade de Nuvem).

  • Outros serviços Web.

Ao desenvolver testes para aplicativos Azure, nós substituímos essas dependências para focar os testes no exercício da lógica.

Os exemplos de Filas de barramento de serviço (incluindo ferramentas e técnicas) que discutimos neste artigo também se aplicam a todas as outras dependências.

Para implementar a estrutura de testes em seus aplicativos do Microsoft Azure, você precisa de:

  • Uma estrutura de testes de unidade para definir e executar seus testes.

  • Uma estrutura fictícia para ajudar você a isolar dependências e desenvolver testes de unidade com escopo restrito.

  • Ferramentas para ajudar com a geração automática de testes de unidade para maior cobertura de código.

  • Outras estruturas que possam ajudar com projetos que podem ser testados, aproveitando a injeção de dependências e aplicando a Inversão do Padrão de Controle (loC).

O Visual Studio inclui um utilitário de linha de comando, chamado MS Teste , a fim de executar testes de unidade criados no Visual Studio. O Visual Studio também inclui um pacote de modelos de projetos e de itens para dar suporte ao teste. Normalmente, você cria um projeto de teste e depois acrescenta classes (conhecidas como acessórios de teste) adornados com o atributo [TestClass]. As classes contêm métodos adornados com o atributo [TestMethod]. No Teste MS, várias janelas no Visual Studio permitem que você execute testes de unidade definidos no projeto. Você também pode analisar os resultados após executá-los.

noteObservação
s edições Visual Studio 2013 Express, Professional e Test Professional não contêm o Teste MS.

No Teste MS, os testes de unidade seguem um padrão AAA - Organizar, Agir e Afirmar.

  • Organizar - desenvolva quaisquer objetos e configurações pré-requisitadas, e todas as outras pré-condições e entradas exigidas pelo CUT.

  • Agir - realize o teste real, com escopo restrito no código.

  • Afirmar - verifique se os resultados esperados ocorreram.

As bibliotecas da estrutura do Teste MS incluem as classes auxiliares PrivateObject e PrivateType. Essas classes usam reflexão para facilitar o acesso de membros de instância não pública ou membros estáticos de dentro do código de teste de unidade.

As edições Premium e Ultimate do Visual Studio incluem ferramentas aprimoradas de testes de unidade. Essas ferramentas integram com o Teste MS. As ferramentas também permitem que você analise a quantidade de código que seus testes de unidade exercem. Além disso, as ferramentas codificam por cores o código fonte para indicar cobertura. O recurso é chamado de Cobertura de Código.

Uma meta dos testes de unidade é testar em isolamento. No entanto, com frequência, você não consegue testar o código sob teste em isolamento. Às vezes, o código não é escrito para testes. Reescrever o código seria difícil, pois isso depende de outras bibliotecas que não são facilmente isoladas. Por exemplo, código que interage com ambientes externos não é isolado facilmente. Uma estrutura fictícia ajuda você a isolar os dois tipos de dependências.

Veja uma lista das estruturas fictícias que você pode considerar na seção de links ao final deste artigo. Este artigo foca em como usar o Microsoft Fakes.

O Microsoft Fakes ajuda você a isolar o código que você está testando substituindo outras partes do aplicativo por stubs ou shims. Stubs ou shims são pequenos pedaços de código que estão sob o controle de seus testes. Ao isolar seu código para teste, você saberá que, se o teste falhar, a causa está lá e não em outro lugar. Stubs e shims também deixam que você teste seu código mesmo que outras partes de seu aplicativo ainda não estejam funcionando.

O Fakes vem em duas formas:

  • Um stub substitui uma classe por um pequeno substituto que implementa a mesma interface. Para usar stubs, você precisa desenvolver seu aplicativo de modo que cada componente dependa apenas de interfaces e não de outros componentes. Por "componente", queremos dizer uma classe ou grupo de classes desenvolvidas e atualizadas juntas e são tipicamente contidas em um assembly.

  • Um shim modifica o código compilado de seu aplicativo em tempo de execução. Em vez de fazer uma chamada de método especificada, seu aplicativo executa o código de shim fornecido por seu teste. Você pode usar shims para substituir chamadas para assemblies que você não pode modificar, como assemblies .NET.

O Code Digger analisa os possíveis caminhos de execução por meio de seu código .NET. O resultado é uma tabela na qual cada linha mostra um comportamento exclusivo de seu código. A tabela ajuda você a compreender o comportamento do código e também pode revelar bugs ocultos.

Para analisar seu código no editor do Visual Studio, use o novo item do menu de contexto Gerar Tabela de Entradas/Saídas para acessar o Code Digger. O Code Digger calcula e exibe os pares de entrada e saída. O Code Digger busca sistematicamente por bugs, exceções e falhas de asserção.

Por padrão, o Code Digger funciona apenas em código .NET público que reside em Bibliotecas de Classe Portáteis. Posteriormente neste artigo, discutiremos como configurar o Code Digger para explorar outros projetos .NET.

O Code Digger usa o mecanismo Pex e o solucionador de restrições Z3 do Microsoft Research para analisar sistematicamente todas as ramificações no código. O Code Digger tenta gerar um pacote de testes que consegue uma alta cobertura de código.

Você pode usar o Microsoft Unity para injeção de dependência (DI) extensível e inversão de contêineres de controle (IoC). Ele suporta a intercepção, injeção de construtor, injeção de propriedade e injeção de chamada de método. O Microsoft Unity e ferramentas semelhantes o ajudam a desenvolver projetos que podem ser testados que, por sua vez, permitem que você insira suas dependências em todos os níveis de seu aplicativo. (Nós supomos que seu aplicativo tenha sido desenvolvido e construído considerando a injeção de dependência e uma dessas estruturas.)

Essas estruturas são ótimas para escrever código que pode ser testado e, em última análise, um código de qualidade. No entanto, elas podem ser exigentes com relação aos requisitos de seu projeto inicial. Nós não discutimos DI e contêineres IoC neste artigo.

Nesta seção, descrevemos uma solução que inclui um site hospedado em uma Função Web. O site envia mensagens para uma Fila. Em seguida, uma Função de Trabalho processa as mensagens da Fila. Estamos interessados nos aspectos de teste de todos os três.

Imagine que você tem um site que cria pedidos e uma Fila do barramento de serviço cria uma fila para processar esses pedidos. A página da Web se parece com a Figura 1:

Figura 1

Figura 1

Quando o usuário clica em Criar, a Fila do barramento de serviço posta o novo pedido para a ação Criar no controlador associado. A ação é implementada da seguinte maneira:

noteObservação
A classe MicrosoftAzureQueue é uma classe wrapper que usa o os APIs .NET do Barramento de Serviço (como MessageSender e MessageReceiver) para interagir com Filas do barramento de serviço.

private IMicrosoftAzureQueue queue;
public OrderController()
{
    queue = new MicrosoftAzureQueue();
}
public OrderController(IMicrosoftAzureQueue queue)
{
    this.queue = queue;
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "OrderId,Description")] Order order)
{
    try
    {
        if (ModelState.IsValid)
        {
            string connectionString = CloudConfigurationManager.GetSetting("Microsoft.ServiceBus.ConnectionString");
            string queueName = "ProcessingQueue";
            queue.InitializeFromConnectionString(connectionString, queueName);
            queue.Send(order);
        }
        return View("OrderCreated");
    }
    catch (Exception ex)
    {
        Trace.TraceError(ex.Message);
        return View("Error");
    }            
}

Observe que o código recupera as configurações do CloudConfigurationManager e envia uma mensagem contendo o pedido para a fila. Observe também que a ação Criar usa os métodos a seguir:

  • InitializeFromConnectionString (cadeia ConnectionString, cadeia QueueName)

  • Enviar (classe MicrosoftAzureQueue)

Queremos desenvolver desvios para esses métodos usando o Fakes, a fim de controlar seu comportamento e remover dependências no ambiente real. Usar o Fakes elimina a necessidade de executar testes no emulador do Azure ou chamar a Fila do barramento de serviço. Nós exercemos a ação Criar no controlador para verificar se a entrada Pedido é aquela enviada para a fila. O método Enviar verifica se a entrada tem a ID e Descrição do Pedido como entrada para a ação. Em seguida, ele verifica se a exibição OrderCreated aparece como resultado.

Desenvolver um teste de unidade para a implementação acima usando Fakes é fácil. No projeto Teste, clique com o botão direito do mouse no assembly que contém os tipos que você deseja simular. Depois, selecione Acrescentar Assembly de Fakes.

No exemplo, nós selecionamos Microsoft.ServiceBus e Acrescentar Assembly de Fakes. Um arquivo XML chamado "Microsoft.ServiceBus.Fakes" é acrescentado ao projeto de teste. Repita a ação para o assembly Microsoft.Windows.Azure.Configuration.

Quando você compilar o projeto Teste, as referências serão acrescentadas às versões fictícias geradas automaticamente dessas assemblies. No exemplo, as assemblies geradas são “Microsoft.ServiceBus.Fakes” e “Microsoft.WindowsAzure.Configuration”.

Crie um método de teste de unidade e aplique o atributo [TestCategory("Com fakes")]. No teste de unidade, você usa shims para isolar partes de seu aplicativo umas das outras.

Uso de shims para isolar seu aplicativo de outras assemblies para testes de unidade

Shims são uma de duas tecnologias que o Microsoft Fakes Framework fornece e que permitem que você isole facilmente os componentes em teste do ambiente. Shims desviam chamadas para métodos específicos para o código que você escreve como parte de seu teste. Muitos métodos retornam resultados diferentes dependendo de condições externas. No entanto, um shim fica sob o controle de seu teste e pode retornar resultados consistentes em cada chamada. Isso deixa seus testes muito mais fáceis de escrever. Use Shims para isolar seu código de assemblies que não fazem parte de sua solução. Para isolar componentes da sua solução uns dos outros, recomendamos que você use stubs.

Uso de Shims para isolar partes do seu aplicativo para testes de unidade

Stubs são uma das duas tecnologias que o Microsoft Fakes Framework fornece e que permitem que você isole facilmente um componente que você está testando de outros componentes que ele chama. Um stub é uma pequeno pedaço de código que assume o lugar de outro componente durante os testes. O benefício de usar um stub é que ele retorna resultados consistentes, tornando o teste mais fácil de escrever. Além disso, você pode executar testes mesmo se os outros componentes ainda não estiverem funcionando.

Em nosso caso de teste, usaremos shims para CloudConfigurationManager e BrokeredMessage das assemblies do Azure. Usaremos stubs para MicrosoftAzureQueue, o que é uma classe em nossa solução.

[TestMethod]
[TestCategory("With fakes")]
public void Test_Home_CreateOrder()
{
    // Shims can be used only in a ShimsContext
    using (ShimsContext.Create())
    {
        // Arrange
        // Use shim for CloudConfigurationManager.GetSetting
        Microsoft.WindowsAzure.Fakes.ShimCloudConfigurationManager.GetSettingString = (key) =>
        {
            return "mockedSettingValue";
        };
                
        // Create the fake queue:
        // In the completed application, queue would be a real one:
        bool wasCreateFromConnString = false;
        Order orderSent = null;
        IMicrosoftAzureQueue queue =
                new OrderWebRole.Queue.Fakes.StubIMicrosoftAzureQueue() // Generated by Fakes.
                {
                    // Define each method:
                    // Name is original name + parameter types:
                    InitializeFromConnectionStringStringString = (connectionString, queueName) => {
                        wasCreateFromConnString = true;
                    },
                    SendOrder = (order) => {
                    orderSent = order;
                    }
                };

        // Component under test
        OrderController controller = new OrderController(queue);

        // Act
        Order inputOrder = new Order()
        {
            OrderId = System.Guid.NewGuid(),
            Description = "A mock order"
        };
        ViewResult result = controller.Create(inputOrder) as ViewResult;

        //Assert
        Assert.IsTrue(wasCreateFromConnString);
        Assert.AreEqual("OrderCreated", result.ViewName);
        Assert.IsNotNull(orderSent);
        Assert.AreEqual(inputOrder.OrderId, orderSent.OrderId);
        Assert.AreEqual(inputOrder.Description, orderSent.Description);
    }
}

A Função Web é responsável por acrescentar um pedido à fila. Agora, considere como testar uma Função de Trabalho que processaria os pedidos recuperando-os da Fila do barramento de serviço. O método Executar em nossa Função de Trabalho pesquisa periodicamente a fila em busca de pedidos e os processa.

private IMicrosoftAzureQueue queue;
public WorkerRole()
{
    queue = new MicrosoftAzureQueue();
}

public WorkerRole(IMicrosoftAzureQueue queue)
{
    this.queue = queue;
}
public override void Run()
{
    try
    {
        string connectionString = CloudConfigurationManager.GetSetting("Microsoft.ServiceBus.ConnectionString");
        string queueName = "ProcessingQueue";
               
        queue.InitializeFromConnectionString(connectionString, queueName);
            
        queue.CreateQueueIfNotExists();
              
        while (true)
        {
            Thread.Sleep(2000);
            //Retrieve order from Service Bus Queue  
            TryProcessOrder(queue);
        }
    }
    catch (Exception ex)
    {
        if (queue != null)
            queue.Close();
        System.Diagnostics.Trace.TraceError(ex.Message);
    }
}

Nós queremos um teste para verificar se a rotina recupera corretamente uma mensagem. A seguir está o teste de unidade completo para o método Executar da Função de Trabalho.

[TestMethod]
public void Test_WorkerRole_Run()
{
    // Shims can be used only in a ShimsContext:
    using (ShimsContext.Create())
    {
        Microsoft.WindowsAzure.Fakes.ShimCloudConfigurationManager.GetSettingString = (key) =>
        {
            return "mockedSettingValue";
        };

        // Arrange 
        bool wasEnsureQueueExistsCalled = false;
        int numCallsToEnsureQueueExists = 0;

        // Create the fake queue:
        // In the completed application, queue would be a real one:
        bool wasConnectionClosedCalled = false;
        bool wasCreateFromConnString = false;
        bool wasReceiveCalled = false;
        int numCallsToReceive = 0;

        bool wasCompleteCalled = false;
        int numCallsToComplete = 0;
        IMicrosoftAzureQueue queue =
                new OrderWebRole.Queue.Fakes.StubIMicrosoftAzureQueue() // Generated by Fakes.
                {

                    // Define each method:
                    // Name is original name + parameter types:
                    InitializeFromConnectionStringStringString = (connectionString, queueName) =>
                    {
                        wasCreateFromConnString = true;
                    },
                    CreateQueueIfNotExists = () =>
                    {
                        wasEnsureQueueExistsCalled = true;
                        numCallsToEnsureQueueExists++;
                    },
                    Receive = () =>
                {
                    wasReceiveCalled = true;
                    if (numCallsToReceive >= 3) throw new Exception("Aborting Run");
                    numCallsToReceive++;
                    Order inputOrder = new Order()
                    {
                        OrderId = System.Guid.NewGuid(),
                        Description = "A mock order"
                    };
                    return new BrokeredMessage(inputOrder);
                },
                    Close = () =>
                    {
                        wasConnectionClosedCalled = true;
                    }

                };


        Microsoft.ServiceBus.Messaging.Fakes.ShimBrokeredMessage.AllInstances.Complete = (message) =>
        {
            wasCompleteCalled = true;
            numCallsToComplete++;
        };

        WorkerRole workerRole = new WorkerRole(queue);

        //Act
        workerRole.Run();

        //Assert
        Assert.IsTrue(wasCreateFromConnString);
        Assert.IsTrue(wasConnectionClosedCalled);
        Assert.IsTrue(wasEnsureQueueExistsCalled);
        Assert.IsTrue(wasReceiveCalled);
        Assert.AreEqual(1, numCallsToEnsureQueueExists);
        Assert.IsTrue(numCallsToReceive > 0);
        Assert.IsTrue(wasCompleteCalled);
        Assert.IsTrue(numCallsToComplete > 0);
        Assert.AreEqual(numCallsToReceive, numCallsToComplete);

    }
}

Você deve definir o representante da propriedade AllInstances do tipo Fake gerado. Usando o representante, qualquer instância que você criar do tipo de Fake real é desviada por meio de qualquer um dos métodos para os quais você definiu os representantes.

No exemplo, queremos usar o método Executar da instância original, mas fornecer desvios para os métodos das instâncias CreateQueue e TryProcessOrder. No código, lançamos uma exceção de modo a sairmos do loop infinito que o método Executar mantém em um momento pré-determinado.

Você pode perguntar, por que simplesmente não usamos a classe MessageSender/MessageReceiver e classes relacionadas do SDK do Barramento de serviço diretamente em vez de inserir um tipo auxiliar? Para isolar o código completamente de modo que ele não chame o Barramento de serviço do mundo real, existem duas escolhas:

  • Escreva Fakes que herdam das classes abstratas no namespace Microsoft.ServiceBus.

  • Deixe que o Fakes crie tipos simulados para todos eles.

O problema com qualquer uma das abordagens é a complexidade. Com as duas abordagens, você finalmente se vê refletindo em classes como TokenProvider e QueueClient. A reflexão causa os seguintes problemas:

  • Você deve criar tipos derivados desses tipos abstratos que expõem todos os seus substitutos necessários.

  • Você deve expor os tipos internos dos quais versões reais dessas classes realmente dependem.

  • Para os tipos internos, você deve recriar seus métodos do construtor ou de fábrica de formas inteligentes, a fim de exercer a dependência no Barramento de serviço real.

A melhor opção é inserir seu próprio tipo auxiliar. Isso é tudo que você precisa para simular, desviar e isolar o código do Barramento de serviço do mundo real.

Para analisar o que esses testes de unidade verificaram, podemos examinar dados de cobertura de código. Se executarmos as duas unidades de testes usando o MS Test, podemos perceber que são aprovados. Vemos também detalhes de execução relacionados na caixa de diálogo Gerenciador de Testes.

Figura 2

Figura 2

Você pode executar a cobertura de código para seus testes no Gerenciador de Testes. Clique com o botão direito do mouse no teste e selecione Analisar Cobertura de Código para testes selecionados. Os resultados aparecerão na janela Resultados da Cobertura de Código. Para ativar a coleta de dados para a cobertura de código, configure o arquivo Local.testsettings em Itens da Solução. Abrir este arquivo inicia o editor Configurações de Teste..

Se você tiver uma solução que não inclui o arquivo Local.testsettings, acrescente-o à solução usando o procedimento a seguir.

  1. Clique no botão Adicionar Novo Item.

  2. Selecione Testar e depois clique em Configurações de Teste.

    Figura 3
    Figura 3

  3. Clique na guiaDados e Diagnósticos e marque a caixa de seleção à direita da linha Cobertura de Código.

  4. Em seguida, clique no botão ConfigurarFigura A.

  5. Na janela Detalhe da Cobertura de Código, selecione todas os assemblies que você testará e clique em OK.

    Figura 4
    Figura 4

  6. Para ignorar o editor Configurações de Teste, clique em Aplicar e Fechar.

  7. Execute seus testes novamente e clique no botão Cobertura de Código. O Resultados da Cobertura de Código devem ter a aparência da Figura 5.

    Figura 5
    Figura 5

  8. Clique no íconeMostrar Cores da Cobertura de Código e navegue até um método na grade.

  9. Clique duas vezes no método. Seu código fonte terá uma cor que indica as áreas que foram testadas. Uma cor verde indica que o código foi testado. Cinza indica que o código foi parcialmente testado ou não testado. Cinza indica que o código foi parcialmente testado ou não testado.

    Figura 6
    Figura 6

Criar testes de unidade manualmente é valioso, mas o Code Digger também pode ajudar você a atualizar de modo inteligente seus testes de unidade. Ele faz isso experimentando valores de parâmetros que talvez você não tenha considerado. Após instalar o Code Digger, você pode explorar um método clicando com o botão direito do mouse no método no editor de código e selecionando Gerar Tabela de Entradas/Saídas..

Figura 7

Figura 7

pós um instante, o Code Digger finalizará seu processamento e os resultados se estabilizarão. Por exemplo, a Figura 8 mostra os resultados da execução do Code Digger no método TryProcessOrder da Função de Trabalho. Observe que o Code Digger foi capaz de criar um teste que resultou em uma exceção. O Code Digger também mostra as entradas que ele criou para gerar essa exceção (um valor nulo para o parâmetro de fila), o que ainda é mais importante.

Figura 8

Figura 8

Isso foi útil para você?
(1500 caracteres restantes)
Agradecemos os seus comentários
Mostrar:
© 2015 Microsoft