Páginas

sábado, 31 de março de 2012

"Novidades" no Visual Studio 11 Beta - Microsoft Fakes

Olá, pessoal!

Hoje vou falar de uma das "novidades" que vieram na versão beta do Visual Studio 11: Microsoft Fakes. O Fakes é um framework de geração de objetos dublês para testes, ou seja, ele serve para criar classes que possam substituir outras para facilitar a execução de testes de caixa branca. Testes de unidade devem verificar apenas o comportamento da classe que estão testando, e não de várias classes. Por exemplo, se para testar uma classe de negócio precisamos da sua classe de dados, para retornar algumas informações, então isso não é um teste de unidade...

É mais fácil mostrar a utilidade do Fakes com um exemplo. Antes de tudo, você deve baixar a versão beta do Visual Studio 11 neste link http://www.microsoft.com/visualstudio/11/en-us.

Como exemplo, eu criei um projeto do tipo "Unit Test Project", para C#. Vou colocar o próprio código da classe de negócios que será testada neste projeto. Numa solução real, isso não poderia ser feito, mas como é apenas uma demonstração, eu tomei a liberdade de cometer essa atrocidade.

No projeto, eu criei uma classe chamada ClasseDeNegocio. Essa classe será responsável por excluir um arquivo do disco caso ele tenha sido criado há mais de 2 meses. Abaixo está o código dessa classe, com o método ExcluirArquivoAntigo.

using System;
using System.IO;

namespace ExemploMicrosoftFakes
{
    public class ClasseDeNegocio
    {
        public void ExcluriArquivoAntigo(string arquivo)
        {
            // verifica se o arquivo existe
            if (File.Exists(arquivo))
            {
                // verifica se é mais velho que 2 meses atrás (regra do sistema)
                if (File.GetCreationTime(arquivo) < DateTime.Now.AddMonths(-2))
                {
                    // sendo assim, exclui
                    File.Delete(arquivo);
                }
            }
        }
    }
}

Em resumo, este método verifica se um determinado arquivo existe e se ele é mais velho que dois meses. Se for, então ele é excluído. Veja um detalhe: ele utiliza vários métodos estáticos de classes do .NET Framework, como File e DateTime. Como poderíamos isolar este tipo de chamada, colocando um objeto dublê de testes? A resposta está no "novo" framework de testes da Microsoft, o Fakes. Veja os três testes de unidade abaixo, que testam as seguintes situações:
  1. Se o arquivo não existe, então ele não deve ser excluído.
  2. Se o arquivo existe e é mais velho que dois meses, então deve ser excluído.
  3. Se o arquivo existe mas não é mais velho que dois meses, então deve ser mantido.

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Microsoft.QualityTools.Testing.Fakes; // para ShimsContext
using System.IO.Fakes;  // para o ShimFile
using System.Fakes; // para o ShimDateTime


namespace ExemploMicrosoftFakes
{
    [TestClass]
    public class TesteDeUnidade
    {
        [TestMethod]
        public void ClasseDeNegocio_ConsultarConteudoArquivo_NaoDeveExcluirArquivoInexistente()
        {
            using (ShimsContext.Create())
            {
                // Arrange
                bool excluido = false;
                ShimFile.ExistsString = (s) => false;
                ShimFile.DeleteString = (s) => excluido = true;
                var alvo = new ClasseDeNegocio();
                // Act
                alvo.ExcluriArquivoAntigo("xxx");
                // Assert
                Assert.AreEqual(false, excluido);
            }
        }

        [TestMethod]
        public void ClasseDeNegocio_ConsultarConteudoArquivo_DeveExcluirArquivoMaisAntigoQue2MesesDaDataAtual()
        {
            using (ShimsContext.Create())
            {
                // Arrange
                ShimDateTime.NowGet = () => DateTime.Parse("2011-04-01");
                bool excluido = false;
                ShimFile.ExistsString = (s) => true;
                ShimFile.GetCreationTimeString = (s) => DateTime.Parse("2011-01-01");
                ShimFile.DeleteString = (s) => excluido = true;
                var alvo = new ClasseDeNegocio();
                // Act
                alvo.ExcluriArquivoAntigo("xxx");
                // Assert
                Assert.AreEqual(true, excluido);
            }
        }

        [TestMethod]
        public void ClasseDeNegocio_ConsultarConteudoArquivo_NaoDeveExcluirComAte2MesesDaDataAtual()
        {
            using (ShimsContext.Create())
            {
                // Arrange
                ShimDateTime.NowGet = () => DateTime.Parse("2011-04-01");
                bool excluido = false;
                ShimFile.ExistsString = (s) => true;
                ShimFile.GetCreationTimeString = (s) => DateTime.Parse("2011-03-01");
                ShimFile.DeleteString = (s) => excluido = true;
                var alvo = new ClasseDeNegocio();
                // Act
                alvo.ExcluriArquivoAntigo("xxx");
                // Assert
                Assert.AreEqual(false, excluido);
            }
        }
    }
}

Vejam as classes ShimDateTime e ShimFile, elas são o segredo para fazer os testes funcionarem. Vamos olhar a linha ShimFile.ExistsString = (s) => false;. O que estamos fazendo aqui é substituir (isso mesmo, substituir) o método File.Exists para que ele tenha outro comportamento. No caso, o que queremos  é que ele simplesmente retorne false para que possamos testar o cenário onde o arquivo não existe, por isso estamos passando para ShimFile.ExistsString um delegate que retorne false.

Ainda comentando sobre o primeiro exemplo, como podemos garantir que nossa classe de negócio realmente funciona e quando o arquivo não existe ela realmente não faz a exclusão? Para isso, podemos interceptar também o método File.Delete, fazendo com que caso ele seja chamado, nós ajustamos o valor da varíável excluido para true. Ao final do teste, quando fazemos o "Assert", se excluido continuar com o valor false, quer dizer que o método File.Delete não foi chamado, confirmando que o cenário está ok. Se File.Delete foi chamado, então há algo de errado com nossa classe de negócios...

Ok, você entendeu que as classes Shim servem para interceptar as chamadas de métodos reais, mas de onde elas vêm? Bom, para que essas classes estejam disponíveis, é necessário criá-las. Mas calma, quem vai fazer esse trabalho é o Visual Studio. Veja como é simples: basta clicar com o botão direito sobre o componente do qual queremos interceptar os métodos de suas classes, e escolher a opção "Add Fakes Assembly". No nosso exemplo, nós adicionamos as classes Shim para todos as classes do namespace System do próprio .NET Framework (File e DateTime vão junto neste pacote...).

Após esse passo, veja que foi criada uma pasta chamada Fakes, contendo os arquivos mscorlib.fakes e System.fakes. São esses arquivos que são usados para a criação dos assemblies fakes. Note que com isso, duas novas referências aparecem no projeto: mscorlib.4.0.0.0.Fakes.dll e System.4.0.0.0.Fakes.dll.

Bom, agora só falta executar os métodos de testes para você acreditar no que eu estou dizendo. Para isso, basta ir na janela Unit Test Explorer do Visual Studio 11 beta, e clicar em "Run All". Outra opção para fazer isso é clicar com o botão direito sobre a classe de testes e clicar em "Run Unit Tests".


Com isso, vimos como é fácil criar testes de unidade no Visual Studio, isolando as nossas dependências e permitindo que os testes realmente validem apenas um pedaço do código. Mas ainda sobram algumas perguntas a serem respondidas...

Eu já não vi isso antes?


Sim, interceptação e substituição de métodos não são novidades no Visual Studio. O framework Moles (http://research.microsoft.com/en-us/projects/moles/ e http://galorebr.blogspot.com.br/2011/07/artigo-de-moles-net-magazine-87.html) já fazia isso! É praticamente a mesma coisa! Foi por isso que no título desse post eu coloquei a palavra novidades entre aspas...

Mas uma diferença que notei está relacionado a forma com que a interceptação é feita. Enquanto no Moles era necessário um processo exclusivo para rodar o código dos testes de unidade (Microsoft.Moles.VsHost.x86.exe), e dessa forma poder usar o profiler para interceptar as chamadas no .NET, no Fakes é utilizado o profiler do IntelliTrace para isso. Na minha opinião isso é uma boa melhoria, pois ficamos com apenas um profiler ao invés de dois. Além disso, até onde testei, a execução do Fakes não tem alguns problemas que existiam com o Moles: às vezes davam alguns erros na execução dos testes, e o processo Microsoft.Moles.VsHost.x86.exe não era finalizado, gerando problemas na hora de executar os testes novamente.

Bom, na própria página do Moles há a mensagem de que ele será descontinuado para a entrada do Microsoft Fakes. Mas aparentemente não há muitas dificuldades em converter o código que usava Moles para usar o Fakes. O seguinte link mostra com mais detalhes como utilizar o Shim do Fakes para outras situações, além dos Stubs (que também já estavam disponíveis no Moles): http://msdn.microsoft.com/en-us/library/hh549175(v=vs.110).aspx.

O código de exemplo que fiz para este post pode ser baixado em https://github.com/fabiogouw/Exemplos

[]'s

Nenhum comentário:

Postar um comentário