Pessoal
Ao longo da minha carreira, já passei por 3 projetos em que me deparei com o cenário-problema que eu vou descrever abaixo.
O objetivo do meu post é ouvir opiniões de vocês a respeito dos problemas que eu encontrei nestes cenários para a implementação de testes unitários.
Em todos os casos eram sistemas que acessavam bancos de dados legados com as seguintes características:
- O banco de dados legado é um Oracle 8i
- Existe um total de 3.000 tabelas neste banco de dados
- O volume dos dados em produção beira os 8 TERA
- 90% destas tabelas se relacionam entre si de forma declarada (milhares de constraints de FK)
- Os relacionamentos declarados são bastante complexos pois existem muitas chaves primárias compostas por N chaves estrangeiras em cascatas de até 5 níveis
- Meu sistema precisa manipular dados de 1% destas tabelas (cerca de 30 delas)
- Meu sistema também tem algumas tabelas adicionadas a este banco de dados que são exclusivas dele (cerca de 15)
- Toda a lógica de negócio depende demais do estado destas tabelas do sistema legado
- A maior parte da lógica de negócio se resume a adicionar, alterar ou excluir dados de algumas tabelas verificando dezenas de pré-condições em outras
- Todas as queries precisam de ajustes muito finos para serem performáticas e também porque DBAs fazem auditorias regulares no BD e pressionam os desenvolvedores caso encontrem queries com execution plans ?menos do que perfeitos?
Por causa desses requisitos o design do sistema, e por consequência dos testes unitários, tem as seguintes restrições:
- Não é possível usar um framework de persistência de objetos como o Hibernate pois:
- A complexidade dos relacionamentos entre as tabelas dificulta o mapeamento objeto-relacional.
- A grande quantidade de relacionamentos de vários tipos torna difícil o mapeamento de apenas um grupo de tabelas e mapear todo o schema não vale a pena.
- Os desenvolvedores precisam ter controle total sobre o SQL das queries e os frameworks de persistência tendem a gerar SQL automaticamente com poucas opções de customização.
- Várias queries precisam ser feitas por stored procedures já existentes do BD.
- A inviabilidade de utilizar um framework de persistência deixa como única alternativa o uso de JDBC direto, dentro de DAOs.
- O código dos DAOs contém a maior parte de toda a lógica de negócio, pois eles são responsáveis por inserir e alterar dados e fazer muitas e muitas verificações em várias tabelas.
- Assim, os DAOs são os componentes mais suscetíveis a erro e os que mais deveriam ser testados, especialmente devido ao grande número de exceções que podem acontecer no JDBC.
- Além disso existem muitas possibilidades de inconsistências no conjunto de tabelas que o sistema manipula e cada possibilidade dessas merece um cenário de teste que garanta o comportamento do sistema.
O grande problema aqui é realizar testes unitários nos DAOs que dependem de muitos estados diferentes do banco de dados.
Muitos diriam que o ideal seria utilizar uma ferramenta como DBUnit para inicializar o banco de dados com dados de teste em cada cenário, só que este tipo de operação tem um custo inviável em um banco de dados de 3.000 tabelas.
Inicializar apenas as tabelas alvo com dados de teste também fica difícil porque existem muitas constraints entre elas e outras tabelas fora do escopo do sistema. A única opção neste caso me parece ser desabilitar as constraints em uma versão de teste do BD.
Entretanto, depender de um Oracle para fazer testes unitários dos DAOs é algo que me desagrada profundamente, pois são bancos de dados difíceis de instalar e nem sempre você consegue colocar um na sua máquina de desenvolvimento.
Eu acho que eu deveria ser capaz de rodar testes unitários puramente em Java, na segurança e conforto da minha máquina, a qualquer momento, e ter um segundo conjunto de ?testes de integração? em que eu testaria meus DAOs contra um banco de dados real. Estes últimos eu rodaria com menos frequência.
Para conseguir isso, pensei seriamente em fazer mocks de java.sql.Connection, java.sql.PreparedStatement, java.sql.ResultSet e assim por diante, e assim simular as N condições-problema que podem aparecer no BD. Só que esta abordagem é também extremamente trabalhosa e produz um volume muito grande de código de teste que tem alto custo para ser mantido, mesmo utilizando JMock.
No final das contas eu não consigo chegar numa solução em que a implementação de testes unitários não seja extremamente trabalhosa e não consuma um período absurdo de tempo.