Microsoft Fakes no Visual Studio 2011


Ao escrever testes unitários antes do código que define as regras dos negócios, conseguimos identificar uma série de detalhes que nos ajuda a definir uma interface clara e consistente para nossos tipos, afinal, já estamos tendo uma visão do consumidor dos mesmos.

Além disso, essa técnica nos ajuda a identificar eventuais dependências que nossos tipos exigem e, consequentemente, isolá-las para que tenham certa flexibilidade, que com isso conseguiremos alternar facilmente entre uma implementação dummy e outra real. E mesmo durante a execução dos testes, é comum utilizarmos uma implementação fictícia, para que ela nos auxilie a testar uma regra específica, onde é irrelevante o uso de uma dependência real.

Outra questão que geralmente ocorre ao escrever testes, é o uso (dependência) de recursos estáticos ou de alguma outra coisa que é díficil simular, como uma classe que não permite sobrescrever seus membros. Um outro grande exemplo dessa dificuldade, é quando temos um código a ser testado que é sensível à data/hora. Como a propriedade Now da estrutura DateTime sempre retorna o horário atual, chamá-la duas vezes, sempre retornará valores diferentes, o que fica difícil definir se o teste sucedeu ou não.

Como parte de diversas melhorias que foram adicionadas na parte de testes do Visual Studo 2011, a Microsoft também incluiu novos recursos para resolver estes pequenos problemas que vimos acima. Microsoft Fakes é framework que ajuda na criação de implementações dummies em nossos testes, onde podemos facilmente simular certas situações injetando um código customizado.

Primeiramente vamos analisar um cenário em que essas novas funcionalidades podem ser interessantes. A classe abaixo, Log, define a estrutura de um item a ser logado. Recebemos em seu construtor a mensagem, e na sequência ele atribui a data atual à propriedade Data.

public class Log
{
    public Log(string mensagem)
    {
        this.Mensagem = mensagem;
        this.Data = DateTime.Now;
    }

    public string Mensagem { get; private set; }

    public DateTime Data { get; set; }
}

Ao escrever um teste que avalie se a data é mesmo atribuída, ele não vai passar. Note que como recorremos à propriedade Now, ela sempre retorna uma data diferente, e por mais que sejam poucos milisegundos, é o suficiente para ser diferente e, consequentemente, o teste falhará.

[TestMethod]
public void DeveAtribuirDataAtualAoLogCriado()
{
    Assert.AreEqual(DateTime.Now, new Log(“Mensagem de Log”).Data);
}

Eis que entra em cena os Shims. Esse tipo de fake nos permite fornecer uma implementação alternativa a membros estáticos ou tipos que não podem ser facilmente customizados (via herança, por exemplo). Para habilitá-o, temos que ir até o projeto de testes, onde o assembly com os tipos que estão sendo testados está referenciado, e clicar com o botão direito em cima daquele que queremos criar os fakes, e em seguida, na opção “Add Fakes Assembly”, assim como vemos na imagem abaixo:

Isso pode ser feito em nossos próprios assemblies ou naqueles do .NET Framework, e independente de qual você escolha, um novo assembly será criado com a infraestrutura necessária para guiar e realizar essa simulação. Os shims são baseados em delegates, o que nos permitirá interceptar a chamada para qualquer método/propriedade, definindo uma “nova” implementação para eles, e que durante a execução, ela será executada, retornando assim, o resultado de acordo com a nossa necessidade. Abaixo a imagem ilustra os assemblies fakes que foram criados para os exemplos:

Logicamente que com esse procedimento, novos tipos foram criados. Como precisamos intervir no retorno da data atual, um novo tipo está disponível: ShimDateTime. A estrutura DateTime fornece a propriedade Now, e neste novo tipo, temos a propriedade NowGet (Get porque Now é somente leitura). Essa propriedade recebe a instância de um delegate do tipo Func<DateTime>, ou seja, deve apenas retornar um DateTime. Abaixo temos o código que exibe como configurá-la para retornar uma data específica.

[TestMethod]
public void DeveAtribuirDataAtualAoLogCriado()
{
    using (ShimsContext.Create())
    {
        ShimDateTime.NowGet = () => new DateTime(2012, 4, 18);

        Assert.AreEqual(DateTime.Now, new Log(“Mensagem de Log”).Data);
    }
}

Além dos tipos que foram criados durante o procedimento de criação dos fakes, existem alguns outros tipos que dão suporte à configuração e execução destes testes. O principal deles é ShimContext, que controla o tempo de vida dos shims. A forma mais fácil de criá-la é através do método estático Create, mas é extremamente importante envolve-lo em um bloco using, para delimitar claramente o bloco onde eles serão utilizados e, principalmente, encerrar o contexto quando os shims não forem mais necessários. Se não fizer isso, a propriedade NowGet retornará sempre 18 de abril de 2012.

Em uma outra situação, imaginemos uma classe que é responsável por avaliar certos índices, e quando eles ultrapassarem um certo valor, uma mensagem deve ser logada para posterior análise. O nosso monitor deverá depender de uma definição e não de uma implementação do repositório de logs, ou em outras palavras, a ideia é durante os testes, injetar um repositório que armazena os logs em memória, mas ao consumir isso por uma aplicação, utilizamos um repositório real, onde por exemplo, poderia gravar isso em um banco de dados. Abaixo temos a estrutura da interface IRepositorioDeLogs e da classe Monitor:

public interface IRepositorioDeLogs
{
    void Adicionar(Log log);

    IEnumerable<Log> Todos { get; }
}

public class Monitor
{
    private IRepositorioDeLogs repositorioDeLogs;

    public Monitor(IRepositorioDeLogs repositorioDeLogs)
    {
        this.repositorioDeLogs = repositorioDeLogs;
    }

    public void AvaliarIndice(double indice)
    {
        if (indice > 75)
            repositorioDeLogs.Adicionar(
                new Log(string.Format(“O índice está acima dos 75.”, indice)));
    }
}

Para efetuar o teste, precisamos passar uma classe que implemente a interface IRepositorioDeLogs, onde neste caso, adicionará os logs em uma lista na memória. Vale lembrar aqui, que os testes garantem que se o índice estiver acima, ele deve gravar o log, independente se está armazenado na memória ou não.

Ao invés de criar uma implementação dummy, podemos recorrer aos stubs, que também são fornecidos por este novo recurso do Visual Studio 2011. Stubs fornecem uma implementação padrão para cada um dos membros fornecidos, neste caso, pela interface IRepositorioDeLogs. Novamente, aqui também recorreremos ao uso de delegates para customizar a funcionalidade de cada membro.

Como criamos também os fakes para o assembly que contém nossos tipos, ele já criou a classe StubIRepositorioDeLogs. Essa classe fornece os campos AdicionarLog e TodosGet. O primeiro recebe um delegate do tipo Action<Log>, enquanto o segundo define um delegate do tipo Func<IEnumerable<Log>>. Com uma implementação simples, conseguiremos simular um repositório que conseguirá acomodar o log que será adicionado ao mesmo, mas isso se a regra da classe Monitor, alvo do teste, estiver em correta.

Para o exemplo, criamos uma coleção do tipo List<Log>, e no delegate do campo AdicionarLog, invocamos o método Add da coleção, enquando ao acessar a propriedade Todos, irá retorná-la na íntegra, e é justamente a propriedade Count que determinará o sucesso ou a falha do teste. O código abaixo ilustra esse procedimento utilizando o stub recém criado:

[TestMethod]
public void SeIndiceEstiverAcimaDe75DeveIncluirLog()
{
    var repositorio = new List<Log>();
    var stub = var StubIRepositorioDeLogs()
    {
        AdicionarLog = log => repositorio.Add(log),
        TodosGet = () => repositorio
    };

    var indice = 78D;
    new Monitor(stub).AvaliarIndice(indice);

    Assert.AreEqual(1, repositorio.Count);
}

Anúncios

Deixe uma resposta

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s