Trabalho com muitos aplicativos da Web que são direcionados por bancos de dados de complexidade variada no back-end. Normalmente, há uma camada ORM separada da lógica de negócios e de apresentação. Isso torna o teste de unidade da lógica de negócios bastante simples; as coisas podem ser implementadas em módulos discretos e todos os dados necessários para o teste podem ser falsificados por meio de zombaria de objetos.
Mas testar o ORM e o próprio banco de dados sempre foi repleto de problemas e compromissos.
Ao longo dos anos, tentei algumas estratégias, nenhuma das quais me satisfez completamente.
Carregue um banco de dados de teste com dados conhecidos. Execute testes no ORM e confirme se os dados corretos retornam. A desvantagem aqui é que o banco de dados de teste precisa acompanhar as alterações de esquema no banco de dados do aplicativo e pode ficar fora de sincronia. Ele também depende de dados artificiais e não pode expor bugs que ocorrem devido a entradas estúpidas do usuário. Finalmente, se o banco de dados de teste for pequeno, não revelará ineficiências como um índice ausente. (OK, esse último não é realmente para o que o teste de unidade deve ser usado, mas não dói.)
Carregue uma cópia do banco de dados de produção e teste contra isso. O problema aqui é que você pode não ter idéia do que está no banco de dados de produção a qualquer momento; seus testes podem precisar ser reescritos se os dados mudarem com o tempo.
Algumas pessoas apontaram que essas duas estratégias dependem de dados específicos, e um teste de unidade deve testar apenas a funcionalidade. Para esse fim, vi sugestões:
- Use um servidor de banco de dados simulado e verifique apenas se o ORM está enviando as consultas corretas em resposta a uma determinada chamada de método.
Quais estratégias você usou para testar aplicativos orientados a banco de dados, se houver? O que funcionou melhor para você?
fonte
Respostas:
Na verdade, eu usei sua primeira abordagem com algum sucesso, mas de maneiras um pouco diferentes que resolveriam alguns dos seus problemas:
Mantenha todo o esquema e scripts para criá-lo no controle de origem, para que qualquer pessoa possa criar o esquema atual do banco de dados após um check-out. Além disso, mantenha dados de amostra em arquivos de dados que são carregados por parte do processo de construção. Ao descobrir dados que causam erros, adicione-os aos dados de amostra para verificar se os erros não voltam a aparecer.
Use um servidor de integração contínua para construir o esquema do banco de dados, carregar os dados de amostra e executar testes. É assim que mantemos nosso banco de dados de teste sincronizado (reconstruindo-o a cada execução de teste). Embora isso exija que o servidor de IC tenha acesso e propriedade de sua própria instância de banco de dados dedicada, digo que a criação do nosso esquema db 3 vezes ao dia ajudou a encontrar drasticamente erros que provavelmente não seriam encontrados antes da entrega (se não mais tarde) ) Não posso dizer que reconstruo o esquema antes de cada confirmação. Alguem? Com essa abordagem, você não precisará (bem, talvez devêssemos, mas não é grande coisa se alguém esquecer).
Para o meu grupo, a entrada do usuário é feita no nível do aplicativo (não no banco de dados), portanto, isso é testado através de testes de unidade padrão.
Carregando a cópia do banco de dados de produção:
essa foi a abordagem usada no meu último trabalho. Foi uma enorme dor causada por alguns problemas:
Zombando do servidor de banco de dados:
Também fazemos isso no meu trabalho atual. Após cada confirmação, executamos testes de unidade contra o código do aplicativo que injetou acessadores de banco de dados simulados. Em seguida, três vezes ao dia, executamos a compilação completa do banco de dados descrita acima. Definitivamente, recomendo as duas abordagens.
fonte
Estou sempre executando testes em um banco de dados na memória (HSQLDB ou Derby) por estes motivos:
O banco de dados na memória é carregado com dados atualizados após o início dos testes e após a maioria dos testes, invoco o ROLLBACK para mantê-lo estável. SEMPRE mantenha os dados no banco de dados de teste estável! Se os dados mudarem o tempo todo, você não poderá testar.
Os dados são carregados do SQL, um banco de dados modelo ou um dump / backup. Eu prefiro despejos se eles estiverem em um formato legível, porque eu posso colocá-los no VCS. Se isso não funcionar, eu uso um arquivo CSV ou XML. Se eu tiver que carregar enormes quantidades de dados ... eu não. Você nunca precisa carregar enormes quantidades de dados :) Não para testes de unidade. Os testes de desempenho são outro problema e regras diferentes se aplicam.
fonte
Estou fazendo essa pergunta há muito tempo, mas acho que não há uma bala de prata para isso.
O que atualmente faço é zombar dos objetos DAO e manter uma representação na memória de uma boa coleção de objetos que representam casos interessantes de dados que podem estar no banco de dados.
O principal problema que vejo com essa abordagem é que você está cobrindo apenas o código que interage com sua camada do DAO, mas nunca testando o próprio DAO e, na minha experiência, vejo que muitos erros também ocorrem nessa camada. Também mantenho alguns testes de unidade que são executados no banco de dados (por uma questão de usar TDD ou teste rápido localmente), mas esses testes nunca são executados no meu servidor de integração contínua, pois não mantemos um banco de dados para esse fim e eu acho que os testes executados no servidor de IC devem ser independentes.
Outra abordagem que acho muito interessante, mas nem sempre vale a pena, pois consome um pouco de tempo, é criar o mesmo esquema usado para produção em um banco de dados incorporado que é executado apenas no teste de unidade.
Embora não haja dúvida de que essa abordagem melhora sua cobertura, existem algumas desvantagens, pois você deve estar o mais próximo possível do ANSI SQL para que ele funcione tanto com o DBMS atual quanto com a substituição incorporada.
Não importa o que você ache mais relevante para o seu código, existem alguns projetos por aí que podem facilitar, como o DbUnit .
fonte
Mesmo se existem ferramentas que permitem que você para zombar seu banco de dados, de uma forma ou de outra (por exemplo jOOQ 's
MockConnection
, o que pode ser visto em esta resposta - aviso legal, eu trabalho para o fornecedor de jOOQ), eu aconselharia não para zombar bancos de dados maiores com complexo consultas.Mesmo se você quiser apenas testar a integração do seu ORM, lembre-se de que um ORM emite uma série muito complexa de consultas ao seu banco de dados, que pode variar em
Zombar de tudo isso para produzir dados fictícios sensíveis é bastante difícil, a menos que você esteja construindo um pequeno banco de dados dentro do seu mock, que interpreta as instruções SQL transmitidas. Dito isso, use um banco de dados de teste de integração conhecido que possa ser redefinido facilmente com dados conhecidos, nos quais você poderá executar seus testes de integração.
fonte
Eu uso o primeiro (executando o código em um banco de dados de teste). O único problema substantivo que vejo com essa abordagem é a possibilidade de os esquemas ficarem fora de sincronia, com os quais mantenho mantendo um número de versão no meu banco de dados e fazendo todas as alterações de esquema por meio de um script que aplica as alterações para cada incremento de versão.
Também faço todas as alterações (incluindo o esquema do banco de dados) no meu ambiente de teste, para que seja o contrário: depois que todos os testes forem aprovados, aplique as atualizações do esquema ao host de produção. Também mantenho um par separado de bancos de dados de teste versus aplicativo no meu sistema de desenvolvimento para poder verificar se a atualização do db funciona corretamente antes de tocar nas caixas de produção reais.
fonte
Estou usando a primeira abordagem, mas um pouco diferente que permite resolver os problemas que você mencionou.
Tudo o que é necessário para executar testes para DAOs está no controle de origem. Inclui esquema e scripts para criar o banco de dados (a janela de encaixe é muito boa para isso). Se o banco de dados incorporado puder ser usado - eu o uso para acelerar.
A diferença importante com as outras abordagens descritas é que os dados necessários para o teste não são carregados de scripts SQL ou arquivos XML. Tudo (exceto alguns dados do dicionário que são efetivamente constantes) é criado pelo aplicativo usando funções / classes de utilitário.
O principal objetivo é tornar os dados usados pelo teste
Basicamente, significa que esses utilitários permitem especificar declarativamente apenas coisas essenciais para o teste no próprio teste e omitir coisas irrelevantes.
Para dar uma idéia do que isso significa na prática, considere o teste para algum DAO que funciona com
Comment
s paraPost
s escritos porAuthors
. Para testar operações CRUD para esse DAO, alguns dados devem ser criados no banco de dados. O teste seria semelhante a:Isso tem várias vantagens sobre scripts SQL ou arquivos XML com dados de teste:
Rollback vs Commit
Acho mais conveniente que os testes sejam confirmados quando executados. Em primeiro lugar, alguns efeitos (por exemplo
DEFERRED CONSTRAINTS
) não podem ser verificados se a confirmação nunca acontecer. Em segundo lugar, quando um teste falha, os dados podem ser examinados no banco de dados, pois não são revertidos pela reversão.Por uma razão, isso tem uma desvantagem: o teste pode produzir dados quebrados e isso levará a falhas em outros testes. Para lidar com isso, tento isolar os testes. No exemplo acima, todo teste pode criar um novo
Author
e todas as outras entidades são criadas relacionadas a ele, portanto colisões são raras. Para lidar com os invariantes restantes que podem ser potencialmente interrompidos, mas não podem ser expressos como uma restrição de nível de banco de dados, uso algumas verificações programáticas para condições incorretas que podem ser executadas após cada teste (e são executadas no IC, mas geralmente desativadas localmente para desempenho) razões).fonte
PostBuilder.post()
. Ele gera alguns valores para todos os atributos obrigatórios da postagem. Isso não é necessário no código de produção.Para projetos baseados em JDBC (direta ou indiretamente, por exemplo, JPA, EJB, ...), você pode simular não todo o banco de dados (nesse caso, seria melhor usar um banco de dados de teste em um RDBMS real), mas apenas simular no nível JDBC .
A vantagem é a abstração que vem dessa maneira, já que os dados JDBC (conjunto de resultados, contagem de atualizações, aviso, ...) são os mesmos, independentemente do back-end: seu prod db, um db de teste ou apenas alguns dados de maquete fornecidos para cada teste caso.
Com a conexão JDBC copiada para cada caso, não há necessidade de gerenciar o banco de dados de teste (limpeza, apenas um teste por vez, recarregar equipamentos, ...). Toda conexão de maquete é isolada e não há necessidade de limpeza. Somente dispositivos mínimos necessários são fornecidos em cada caso de teste para simular a troca JDBC, o que ajuda a evitar a complexidade de gerenciar um banco de dados inteiro de teste.
Acolyte é minha estrutura, que inclui um driver e utilitário JDBC para esse tipo de maquete: http://acolyte.eu.org .
fonte