Boas Práticas: Inserindo Dados Conhecidos no Banco
Ao concluirmos esta aula, nós:
- Seremos capazes de identificar as características de um bom dataset de banco de dados (seja em DbUnit XML ou em scripts
@Sql). - Saberemos como evitar os três erros mais comuns encontrados em datasets de testes de integração.
- Saberemos como criar datasets modulares que são fáceis de ler e de manter ao longo da evolução do sistema.
Nossos Objetivos
Quando criamos datasets para inicializar o nosso banco de dados antes dos testes de integração, precisamos atingir dois objetivos fundamentais:
- Nossos datasets devem ser fáceis de ler: Se um teste de integração falhar, é muito provável que precisemos ler os datasets (ou scripts SQL) que inicializam o banco de dados para entender o cenário. Se os nossos datasets forem confusos ou gigantescos, será muito mais difícil descobrir por que o teste falhou.
- Nossos datasets devem ser fáceis de manter: Sempre que evoluímos o esquema do banco de dados da nossa aplicação (adicionando uma coluna obrigatória ou uma nova tabela), precisamos atualizar os nossos testes de integração e datasets. Se a manutenção dos datasets for dolorosa e demorada, a equipe perderá velocidade e acabará abandonando a suíte de testes de integração.
Infelizmente, atingir esses dois objetivos exige disciplina. Vamos examinar agora os três erros mais comuns que impedem equipes de terem datasets limpos.
Os Três Erros Mais Comuns em Datasets de Teste
Existem três antipadrões muito frequentes que tornam datasets de banco de dados difíceis de ler e manter:
- Criar datasets exportando um dump do banco de dados real (produção ou homologação): O problema dessa abordagem é que os dumps exportados são tão imensos e cheios de dados irrelevantes que ninguém se atreve a limpá-los. Como resultado, os arquivos tornam-se praticamente impossíveis de ler e qualquer alteração de schema exige um esforço hercúleo.
- Criar apenas um único dataset global (“God Dataset”): Se adicionarmos todos os dados necessários para todos os testes da aplicação dentro de um único arquivo (
dataset.xmloutest-data.sql), o tamanho desse arquivo crescerá continuamente. Com o passar dos meses, terminaremos com um dataset monolítico gigantesco que sofre dos mesmos problemas de um dump exportado do banco real. - Criar um dataset específico para cada método de teste: É natural pensar que podemos resolver o problema do dataset gigante criando um arquivo separado para cada caso de teste (
test1-data.sql,test2-data.sql, etc.) contendo todas as tabelas necessárias para aquele teste. Embora esses arquivos individuais sejam fáceis de ler, eles geram uma duplicação massiva: se adicionarmos uma nova colunaNOT NULLna tabelapersonsoutasks, teremos que editar dezenas ou centenas de arquivos espalhados pelo projeto!
É evidente que devemos evitar essas três armadilhas. Vamos descobrir agora como criar datasets limpos e fáceis de manter!
Criando Datasets que Não Dão Dor de Cabeça
Podemos criar datasets limpos e sustentáveis seguindo uma estratégia em dois passos:
- Dividir o nosso banco de dados em seções lógicas independentes.
- Criar um dataset por seção lógica, de forma que cada dataset inicialize apenas as tabelas que pertencem àquela mesma seção lógica.
A pergunta imediata é: como identificamos as “seções lógicas” do nosso banco de dados?
Uma excelente heurística da engenharia de software que nos ajuda a encontrar essas fronteiras é o padrão Aggregate (Agregado) do Domain-Driven Design (DDD), definido por Martin Fowler como:
“Um Aggregate no Domain-Driven Design é um agrupamento (cluster) de objetos de domínio que podem ser tratados como uma única unidade coesa.”
Vamos ver na prática como aplicar esse conceito para dividir o banco de dados do nosso gerenciador de tarefas (Task Tracker)!
Exemplo Prático: Dividindo o Banco de Dados do Nosso Task Tracker
Suponha que inicialmente todos os nossos testes de integração utilizem um único dataset que inicializa todas as tabelas de uma só vez:
|
|
Quando analisamos esse dataset com atenção, percebemos claramente que ele mistura duas seções lógicas (dois agregados) distintas:
- As informações inseridas na tabela
persons: Essas duas linhas representam os usuários/pessoas cadastrados no sistema. - As informações inseridas nas tabelas
tasks,tagsetasks_tags: Essas linhas representam as tarefas e suas respectivas etiquetas.
Portanto, devemos dividir esse dataset em dois arquivos menores e focados:
Primeiro, criamos um dataset (persons.xml ou insert-persons.sql) que inicializa exclusivamente a tabela persons:
|
|
(Ou seu equivalente em SQL para @Sql("/sql/insert-persons.sql")):
|
|
Segundo, criamos um dataset (tasks.xml ou insert-tasks.sql) que inicializa as tabelas da seção lógica de tarefas (tasks, tags e tasks_tags):
|
|
(Ou seu equivalente em SQL para @Sql("/sql/insert-tasks.sql")):
|
|
Dica de Legibilidade: Mesmo dividindo o banco em seções lógicas, em uma aplicação corporativa real um dataset pode conter várias linhas com diferentes estados. Sempre adicione comentários (
<!-- ... -->no XML ou-- ...no SQL) explicando o papel de cada registro (ex:-- Tarefa aberta atribuída a Anna com 1 tag).
Regras para Utilizar Nossos Novos Datasets
Depois de dividirmos o banco de dados em seções lógicas e criarmos os datasets correspondentes, devemos seguir três regras de ouro no dia a dia:
- Compor os datasets necessários em cada teste: Se um teste de integração precisa de dados em tabelas que pertencem a múltiplas seções lógicas (por exemplo,
personsetasks), passamos ambos os arquivos na anotação do teste (@DatabaseSetup("persons.xml", "tasks.xml")ou@Sql("/sql/insert-persons.sql", "/sql/insert-tasks.sql")). - Não criar novos arquivos de dataset a menos que uma nova seção lógica surja no banco de dados: Se precisarmos de um novo registro em uma seção lógica existente, devemos tentar primeiro aproveitar os registros já existentes ou adicionar uma nova linha ao dataset daquela seção lógica, evitando a proliferação de dezenas de arquivos quase idênticos.
- Só adicionar novas linhas aos datasets existentes quando for estritamente necessário: Devemos manter cada dataset o menor possível. Essa disciplina nos força a refletir cuidadosamente sobre os nossos casos de teste antes de sairmos duplicando dados.
Ao seguirmos essas três regras, colhemos dois grandes benefícios:
- Nossos datasets permanecem enxutos e fáceis de ler, economizando horas de depuração quando um teste de integração falha.
- Se alterarmos a estrutura de uma tabela no banco de dados (por exemplo,
persons), precisaremos atualizar apenas um único arquivo (persons.xml/insert-persons.sql) em todo o projeto!
Resumo
Esta aula nos ensinou cinco lições fundamentais:
- Um bom dataset de teste de integração deve ser fácil de ler e fácil de manter.
- Nunca devemos exportar dumps de bancos de dados reais para usar diretamente como datasets de teste.
- Devemos evitar tanto o “dataset único monolítico” quanto a criação de dezenas de “datasets específicos por método de teste”.
- Ao criar datasets, devemos dividir nosso banco de dados em seções lógicas (Agregados), onde cada arquivo inicializa as tabelas pertencentes a uma única seção lógica.
- Só devemos criar um novo arquivo de dataset quando adicionarmos uma nova seção lógica ao nosso modelo de dados (ou para cenários especiais de “banco vazio”).