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!
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.
Referência:
Osherove, Roy. 2009. The Art of Unit Testing with examples in .NET. Manning.
Grande Robson, começando com o pé direito… Tema da hora, Parabéns!!!
CurtirCurtir
Muito bom, prático. simples e objetivo.
CurtirCurtir
Obrigado, cara
[]s
CurtirCurtir