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