Fast!

  • Rafael Miceli
  • 16 Abr 2014

“Como ter um teste unitário rápido? Como??? Eu tenho meu banco de dados que demora buscando os registros! Se eu fizer um teste unitário, ele vai até o banco, e demora por causa dele!”

Sim, isso acontece sim! E por isso chamamos de testes unitários. pois testamos apena uma unidade do código.

Mas e agora como “não ir ao banco de dados” se a minha aplicação “vai”?

Simples: Fake It.

Test Double - Fake

Provavelmente você já deve ter ouvido a palavra Fake alguma vez na sua vida. Fake é um Test Double (Dublê de testes). Os test doubles (Stubs, Fakes e Mocks) nos ajudam a similar propriedades, estados e respostas que nosso teste unitário espera.

O Fake é um Test Double que nós ajuda a similar o comportamento de um componente (como bancos de dados ou serviços externos).

Veja este método da classe “Tool”:

public List<Tool> getToolsFromCategory(String categoryName) {
	List<Tool> allTools = new ToolRepository().getAllTools();
	List<Tool> toolsFromCategory = new ArrayList<Tool>();
	for (Tool tool : allTools)
		if (tool.ToolCategoryName.equals(categoryName))
			toolsFromCategory.add(tool);
	return toolsFromCategory;
}        

Digamos que no cenário nós queremos o seguinte: Buscar todas as Tools (Ferramentas) que sejam da categoria passada por parâmetro (categoryName).

Observe a classe ToolRepository. Ela possui o método getAllTools(), que vai no banco e busca todas as “Tool”s. O que precisamos é que no método getAllTools() finjamos que fomos ao banco de dados e retornamos as “Tool”s que possuem nele.

E testar se o “For” e o “If” dentro dele, respondem de acordo com o comportamento que esperamos, que é: Buscar todas as Tools (Ferramentas) que sejam da categoria passada por parâmetro (categoryName).

Legal, e como fazer isso?

Bem primeiro deixe eu mostrar a classe “Tool” completa para criarmos o teste (Por favor, lembre-se que estou abordando o conceito de Teste Unitário, antes de mostrar o TDD).

Fazendo

Esta é a classe Tool completa:

public class Tool {

	public String Name;
	public String ToolCategoryName;

	public Tool(String name, String toolCategoryName){
		Name = name;
		ToolCategoryName = toolCategoryName;
	}  

	public List<Tool> getToolsFromCategory(String categoryName){
		List<Tool> allTools = new ToolRepository().getAllTools();

		List<Tool> toolsFromCategory = new ArrayList<Tool>();
		for (Tool tool : allTools)
			if (tool.ToolCategoryName.equals(categoryName))
				toolsFromCategory.add(tool);

		return toolsFromCategory;
	}

	public void insertToolInDatabase(Tool tool)
	{
		  //Implemente aqui
	}
}

Este seria o teste que eu criaria:

public class ToolTest {

	@org.junit.Test
	public void test_GetToolsFromCategory() {

		//Arrange
		String category = "Antena";
		Tool tool = new Tool("Nome", "Categoria");

		//Act
		List<Tool> tools = tool.getToolsFromCategory(category);

		//Assert
		Assert.assertTrue(allToolsAreFromCategory(category, tools));

	}

	private boolean allToolsAreFromCategory(String category, List<Tool> tools) {

		for (Tool tool : tools)
			if (tool.ToolCategoryName != category)
				return false;

		return tools.size() > 0;
	}
}

Mas ainda assim, ele vai no banco de dados né?

E o Fake então… Não tem como eu fingir uma chamada no código de produção!

Bem, teremos de dar uma refatorada para Fakear isso ai. A primeira parte da refatoração seria essa:

public class Tool {

	public String Name;
	public String ToolCategoryName;
	*private ToolRepository _toolRepository;*

	public Tool(String name, String toolCategoryName){
		Name = name;
		ToolCategoryName = toolCategoryName;
		*_toolRepository = new ToolRepository();*
	}

	*public Tool(String name, String toolCategoryName, ToolRepository toolRepository){
		Name = name;
		ToolCategoryName = toolCategoryName;
		_toolRepository = toolRepository;
	}*

	public List<Tool> getToolsFromCategory(String categoryName){
		*List<Tool> allTools = _toolRepository.getAllTools();*

		List<Tool> toolsFromCategory = new ArrayList<Tool>();
		for (Tool tool : allTools)
			if (tool.ToolCategoryName.equals(categoryName))
				toolsFromCategory.add(tool);

		return toolsFromCategory;
	}

	public void insertToolInDatabase(Tool tool)
	{
			//Implemente aqui
	}
}

O que nós fizemos aqui foi somente mover o local da criação da dependência ao ToolRepository para o construtor, e criamos um construtor novo recebendo a mesma dependência. Nós chamamos essa técnica de “Poor Man Dependency Injection”.

Mas ainda assim não somos capazes de ter um Fake do nosso banco de dados que é chamado pela ToolRepository. Bem vamos ter de mudar mais duas coisas:

1- Este é nosso ToolRepository Atualmente:

public class ToolRepository {

	public List<Tool> getAllTools()
	{
		//Return All Tools From Database
		List<Tool> tools = new ArrayList<Tool>();
		return tools;
	}
}

A refatoração que iremos fazer é criar uma Interface para ela implementar com o método dela que queremos similar! getAllTools()

public interface IToolRepository {
	List<Tool> getAllTools();
}


public class ToolRepository *implements IToolRepository* {

	public List<Tool> getAllTools()
	{
		//Return All Tools From Database
		List<Tool> tools = new ArrayList<Tool>();
		return tools;
	}
}

public class Tool {

	public String Name;
	public String ToolCategoryName;
	private *IToolRepository* _toolRepository;

	public Tool(String name, String toolCategoryName){
		Name = name;
		ToolCategoryName = toolCategoryName;
		_toolRepository = new ToolRepository();
	}

	public Tool(String name, String toolCategoryName, *IToolRepository* toolRepository){
		Name = name;
		ToolCategoryName = toolCategoryName;
		_toolRepository = toolRepository;
	}

	public List<Tool> getToolsFromCategory(String categoryName){
		List<Tool> allTools = _toolRepository.getAllTools();

		List<Tool> toolsFromCategory = new ArrayList<Tool>();
		for (Tool tool : allTools)
			if (tool.ToolCategoryName.equals(categoryName))
				toolsFromCategory.add(tool);

		return toolsFromCategory;
	}

	public void insertToolInDatabase(Tool tool)
	{
		  //Implemente aqui
	}
}

O Fake

Simples refatorações certo? Criamos a Interface e mudamos na classe Tool do tipo ToolRepository para IToolRepository. Agora podemos criar nossa classe Fake de repositório que simila uma chamada ao banco de dados.

 class FakeToolRepository implements IToolRepository
{
	@Override
	public List<Tool> getAllTools() {
		List<Tool> tools = new ArrayList<Tool>();
		tools.add(new Tool("Nome 1", "Camera"));
		tools.add(new Tool("Nome 2", "Antena"));
		tools.add(new Tool("Nome 3", "Camera"));
		tools.add(new Tool("Nome 4", "Antena"));
		tools.add(new Tool("Nome 5", "Camera"));
		tools.add(new Tool("Nome 6", "Antena"));
		return tools;
	}
}

E refatoramos nosso teste para que ele use a classe FakeRepository:

@org.junit.Test
public void test_GetToolsFromCategory() {

	//Arrange
	String category = "Antena";
	Tool tool = new Tool("Nome", "Categoria", new FakeToolRepository());

	//Act
	List<Tool> tools = tool.getToolsFromCategory(category);

	//Assert
	Assert.assertTrue(allToolsAreFromCategory(category, tools));

}

Veja agora se aplicação não continua funcionando? Além disso agora garantimos que o comportamento da aplicação não seja mais quebrado, assim, não tendo recorrência de erros! Além de, atingirmos o Fast do F.I.R.S.T.

O que fizemos foi simplesmente criar uma Classe que implementa o IToolRepository, e na implementação de seu método retornamos uma Lista de “Tool”s, que é o que o nosso método que estamos testando espera do banco de dados.

Agora estamos unitariamente testando uma parte da nossa aplicação.

Nó próximo artigo cobriremos o “Isolated”.

Se precisarem do fonte de exemplo ele vai estar no seguinte repositório do Github

comentarios com Disqus Disqus