Usando stubs para eliminar dependências

Fala, galera!

Neste artigo, vou falar um pouco sobre testes de unidade, dando um pequeno exemplo de como usar stubs para remover dependências.

É muito comum, ao se fazer testes de unidade, que o método a ser testado possua dependências externas, ou seja, algo sobre o qual não temos controle ao fazer os testes, como, por exemplo, arquivos em disco, threads, webservices, banco de dados e assim por diante. Sendo assim, precisamos, de alguma forma, remover esta dependência, para que nosso teste DE UNIDADE realmente teste uma só “coisa”, ou seja, a lógica do método sob teste. É aí que entram os objetos falsos (fakes), entre eles, os stubs.

Vamos à definição de stub: “Um stub é um substituto controlável para uma dependência existente no sistema. Usando um stub, pode-se testar seu código sem lidar diretamente com a dependência.” (Osherove, 2009). Em outras palavras, usamos um objeto fake (o stub), o qual temos total controle – implementando/configurando como quisermos – no lugar do objeto real.

Para deixarmos mais claro, vamos ver um exemplo em C#. O cenário é o seguinte: temos uma classe AnalisadorDeLog, que gerencia arquivos de log gerados por outras aplicações. Esta classe possui o método EhValidoONomeDoArquivo, que verifica se um nome de arquivo é um nome válido para arquivos de log. Este método, para checar se a extensão do arquivo informado é permitida, busca as extensões válidas em um arquivo .config da aplicação. Temos aí uma dependência externa: o teste depende de um arquivo em disco.

1) A primeira providência é ajustar o design da aplicação, de forma que seja possível usarmos o stub:

public class AnalisadorDeLog
{
   public bool EhValidoONomeDoArquivo(string nomeArquivo)
   {
      // lógica local (sem dependência externa)
      bool tamanhoDoNomeEhValido = (nome.Length > 5);

      // aqui onde havia um acesso direto ao arquivo .config,
      // substituímos por uma classe que faz este acesso
      GerenciadorDeExtensao gerenciador = new GerenciadorDeExtensao();
      bool extensaoEhValida = gerenciador.EhExtensaoValida(nomeArquivo);

      return (tamanhoDoNomeEhValido && extensaoEhValida);
   }
}

2) Só isso não é o suficiente. Precisamos agora extrair uma interface da classe, para que possamos substituir o gerenciador real pelo nosso fake:

// classe agora implementa a interface IGerenciadorDeExtensao
public class GerenciadorDeExtensao : IGerenciadorDeExtensao
{
   public bool EhExtensaoValida(string nomeArquivo)
   {
      // aqui faz acesso ao disco para validar a extensão do arquivo
      // (implemente para se divertir ou simplesmente retorne true
      // já que ele está fora do escopo...)
   }
}

// criamos a interface
public interface IGerenciadorDeExtensao
{
   bool EhExtensaoValida(string nomeArquivo);
}

3) Agora, implementamos nosso stub (o gerenciador “de mentira”), que também implementa IGerenciadorDeExtensao:

public class StubGerenciadorDeExtensao : IGerenciadorDeExtensao
{
   // criamos aqui uma propriedade que nos permite configurar o stub no teste
   // simulando que o objeto real voltou true ou false
   public bool ExtensaoDeveSerValida { get; set;}

   public bool EhExtensaoValida(string nomeArquivo)
   {
      // aqui entra o "truque", nosso fake não valida nada
      // (buscando informações em disco). Ele simplesmente
      // retorna nossa propriedade (sob nosso controle lembra?)
      return ExtensaoDeveSerValida;
   }
}

4) Precisamos agora alterar a classe AnalisadorDeLog para que o método que iremos testar (EhValidoONomeDoArquivo) não chame a implementação real (…new GerenciadorDeExtensao();…). Utilizaremos aqui injeção de dependência via construtor:

public class AnalisadorDeLog
{
   private IGerenciadorDeExtensao _gerenciador;

   // construtor padrão cria objeto real
   public AnalisadorDeLog()
   {
      _gerenciador = new GerenciadorDeExtensao();
   }

   // novo construtor para ser chamado pelo teste
   public AnalisadorDeLog(IGerenciadorDeExtensao gerenciador)
   {
      _gerenciador = gerenciador;
   }

   // nova versão do método que será testado
   public bool EhValidoONomeDoArquivo(string nomeArquivo)
   {
      // lógica local (sem dependência externa)
      bool tamanhoDoNomeEhValido = (nome.Length > 5);

      // parte modificada - gerenciador não é mais instanciado aqui
      bool extensaoEhValida = _gerenciador.EhExtensaoValida(nomeArquivo);

      return (tamanhoDoNomeEhValido && extensaoEhValida);
   }
}

5) Nosso código está pronto para ser testado! Então nosso método de teste poderia ser algo assim:

[TestClass]
public class AnalisadorDeLogTeste
{
   [TestMethod]
   public void DeveInformarQueONomeDoArquivoEhValido()
   {
      // preparamos o teste (ARRANGE)
      StubGerenciadorDeExtensao gerenciadorFake = new StubGerenciadorDeExtensao();
      // definimos que a extensão é valida pelo fake (não vamos ao arquivo em disco checar isso)
      gerenciadorFake.ExtensaoDeveSerValida = true;
      // definimos um nome com mais de 5 caracteres
      string nomeArquivo = "arquivo.ext";
      // passamos o gerenciador fake para a classe ao invés do real
      AnalisadorDeLog log = new AnalisadorDeLog(gerenciadorFake);

      // chamamos o método que queremos testar (ACT)
      bool resultadoObtido = log.EhValidoONomeDoArquivo(nomeArquivo);

      // verificamos se o resultado foi verdadeiro (ASSERT)
      Assert.IsTrue(resultadoObtido);
   }
}

E pronto! O teste TEM QUE passar!
É básico!!

Com isso, vimos um exemplo simples de como se implementar um stub para remover dependências externas ao se fazer testes de unidade.

É claro, isto é apenas “o começo da história” e teve como propósito introduzir o assunto, o qual é bastante extenso. Existem ainda os mocks (fakes um pouco mais “espertos” que stubs), frameworks de isolamento (os quais criam automaticamente stubs e mocks pra gente) e inúmeras questões a serem tratadas no que se refere a testes de unidade.

Espero que tenha conseguido passar o “espírito da coisa”.

Críticas e sugestões são bem vindas.

Até a próxima!

Referência:
Osherove, Roy. 2009. The Art of Unit Testing with examples in .NET. Manning.

Anúncios

4 comentários em “Usando stubs para eliminar dependências

Participe! Vamos trocar uma ideia sobre desenvolvimento de software!

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