Esta é o primeiro de uma série de posts sobre padrões de teste (test patterns) que sempre encontramos em testes unitários. É muito importante conhecer estes padrões, da mesma maneira que os design patterns, eles contém soluções já empregadas com sucesso em um número imenso de problemas e permitem que todos se comuniquem de forma consistente.
O primeiro passo é entender o que são cada um destes tipos de objeto e quando devemos usar cada um deles. Todos estes tipos de objetos vêm solucionar dois grandes problemas ao se trabalhar com unit tests:
Testes não confiáveis devido a dependências que não podem ser utilizadas
Muitas vezes é díficil criar unit tests para um sistema porque ele contém dependências que não podem ser utilizadas no ambiente de testes.
Estas dependências podem causar diversos problemas, por exemplo, utilizar bancos de dados ou arquivos pode impedir que os testes retornem sempre valores consistentes (a não ser que seja criada lógica para restaurar as fontes de dados a um estado conhecido). Outro problema é simular exceções, é relativamente complexo por exemplo simular uma exceção de concorrência entre dois usuários tentando acessar um recurso, quando o único usuário é o desenvolvedor executando os testes!
Outra problema é quando temos códigos não testados*, como um Web Service de uma outra equipe ou ainda não construídos, nos quais é necessário esperar um certo tipo de comportamento consistente com as entradas informadas pelo sistema. Neste caso também se encaixa código que simplesmente não roda na máquina do desenvolvedor, como um código de acesso ao Active Directory (que necessitaria um infraestrutura imensa somente para executar um teste que deve ser reproduzível desde que mantidos os parâmetros de entrada).
*Não é recomendado criar testes para todo e qualquer tipo de código. Por exemplo, é insano querer criar qualquer teste para o .NET Framework. Código não testado aqui significa código cuja qualidade não foi verificada.
Testes muito lentos
Testes demorados significam que os desenvolvedovedores não os executarão cada vez que uma mudança é realizada no sistema, ponto.
Este único fator já deveria ser suficiente para rever os seus testes unitários já que uma das maiores vantagens de se desenvolverem tais testes é que qualquer bug inserido no código é rapidamente identificado quando os testes falham. Além disso, existe o custo óbvio de produtividade do desenvolvedor que precisa esperar que os testes terminem de executar antes de realizar qualquer alteração no código.
Nestas situações, quando escrevemos testes para um sistema e não podemos (ou não queremos) utilizar um objeto real, nós simplesmente escrevemos um substituto para ele. De modo que o sistema testado acredite que este objeto falso é real.
É claro que este objeto falso não precisa ser a cópia do original (implementar todos os membros de uma interface ou sobrescrever todos os métodos de uma classe base) ele somente precisa conter a implementação necessária para executar o conjunto de testes. Muitas vezes até um valor hard-coded é suficiente, em outras, teremos que implementar algum tipo de funcionalidade rudimentar para o teste ter algum valor.
A mensagem importante é que não importa o tipo de objeto falso que você deseje criar, ele não pode executar um caminho diferente do objeto real no código! Por isso o objeto falso deve ser "instalado" no sistema a ser testado no lugar do objeto real, abaixo temos um exemplo.
class ServicoCadastroCliente
{
void CadastrarCliente(string nome, string rg) {
CadastroCliente cad = new CadastroCliente();
cad.Adicionar(nome, rg);
}
}
class CadastroCliente
{
public void Adicionar(string nome, string rg)
{
//Lógica omitida para simplificar exemplo
}
}
Como eu criaria um objeto falso sem interferir no código de produção? O código abaixo contém um exemplo:
class ServicoCadastroCliente
{
ICadastroCliente _cadastroCliente;
// Esse é o construtor utilizado pelo código de produção, instancia a classe real CadastroCliente
void ServicoCadastroCliente() {
_cadastroCliente = new CadastroCliente();
}
// Esse é o construtor utilizado pelo código de teste, o código de teste apenas precisa passar uma
// instancia de CadastroClienteMock para trocar a classe real pela falsa
void ServicoCadastroCliente(ICadastroCliente cadastroCliente)
{
_cadastroCliente = cadastroCliente;
}
void CadastrarCliente(string nome, string rg) {
_cadastroCliente.Adicionar(nome, rg);
}
}
class CadastroCliente : ICadastroCliente
{
public void Adicionar(string nome, string rg)
{
//Lógica omitida para simplificar exemplo
}
}
interface ICadastroCliente
{
void Adicionar(string nome, string rg);
}
class CadastroClienteMock : ICadastroCliente
{
public void Adicionar(string nome, string rg)
{
//Pronto, aqui posso realizar qualquer tipo de checagem que garanta que
// tanto nome como rg foram passados corretamente pelo sistema testado
}
}
No próximo post entrarei em detalhes de cada tipo destes objetos falsos.

