Como simular um database para testes (Java)?

Estou programando em Java e meus aplicativos estão fazendo muito uso do DB. Por isso, é importante para mim poder testar meu uso de database facilmente.
Quais são os testes do DB? Para mim, eles devem fornecer dois requisitos simples:

  1. Verifique a syntax SQL.
  2. Mais importante, verifique se os dados estão selecionados / atualizados / inseridos corretamente, de acordo com uma determinada situação.

Bem, então parece que tudo que eu preciso é um DB.
Mas na verdade, eu prefiro não, pois há poucas dificuldades em usar um DB para um teste:

  • “Basta obter um teste DB, quão difícil poderia ser?” – Bem, no meu local de trabalho, ter um teste pessoal DB é bem impossível. Você tem que usar um database “público”, que é acessível para todos.
  • “Esses testes com certeza não são rápidos …” – Testes de DB tendem a ser mais lentos que os testes usuais. Não é ideal ter testes lentos.
  • “Este programa deve lidar com qualquer caso!” – Torna-se um pouco chato e até mesmo impossível tentar e simular cada caso em um DB. Para cada caso, uma certa quantidade de consultas de inserção / atualização deve ser feita, o que é irritante e leva tempo.
  • “Espere um segundo, como você sabe que existem 542 linhas nessa tabela?” – Um dos principais princípios em testes é poder testar a funcionalidade de maneira diferente daquela do seu código testado. Ao usar um database, geralmente há uma maneira de fazer algo, portanto, o teste é exatamente o mesmo que o código de núcleo.

Então, você pode descobrir que eu não gosto de DBs quando se trata de testes (é claro que vou ter que chegar a isso em algum ponto, mas eu prefiro chegar lá mais tarde no meu teste, depois que eu encontrei a maioria dos bugs usando o resto dos methods de teste). Mas o que estou procurando?

Eu estou procurando uma maneira de simular um database, um database simulado, usando o sistema de arquivos ou apenas a memory virtual. Eu pensei que talvez houvesse uma ferramenta Java / pacote que permite simplesmente construir (usando interface de código) um database DB por teste, com tabelas e linhas simuladas, com verificação SQL e com uma interface de código para monitorar seu status ).

Você está familiarizado com esse tipo de ferramenta?


Edit: Obrigado pelas respostas! Embora eu estivesse pedindo uma ferramenta, você também me deu algumas dicas sobre o problema 🙂 Vou levar algum tempo para verificar suas ofertas, então não posso dizer agora se suas respostas estavam satisfatórias.

Enfim, aqui está uma visão melhor do que estou procurando – Imagine uma class chamada DBMonitor, que uma de suas características é encontrar o número de linhas em uma tabela. Aqui está um código imaginário de como eu gostaria de testar esse recurso usando o JUnit:

public class TestDBMonitor extends TestCase { @Override public void setUp() throws Exception { MockConnection connection = new MockConnection(); this.tableName = "table1"; MockTable table = new MockTable(tableName); String columnName = "column1"; ColumnType columnType = ColumnType.NUMBER; int columnSize = 50; MockColumn column = new MockColumn(columnName, columnType, columnSize); table.addColumn(column); for (int i = 0; i < 20; i++) { HashMap fields = new HashMap(); fields.put(column, i); table.addRow(fields); } this.connection = connection; } @Test public void testGatherStatistics() throws Exception { DBMonitor monitor = new DBMonitor(connection); monitor.gatherStatistics(); assertEquals(((MockConnection) connection).getNumberOfRows(tableName), monitor.getNumberOfRows(tableName)); } String tableName; Connection connection; } 

Espero que este código seja claro o suficiente para entender a minha ideia (desculpe-me por erros de syntax, eu estava digitando manualmente sem o meu querido Eclipse: P).

By the way, eu uso ORM parcialmente, e minhas consultas SQL brutas são bastante simples e não devem diferir de uma plataforma para outra.

nova resposta à velha pergunta (mas as coisas avançaram um pouco):

Como simular um database para testes (Java)?

você não simula isso. você zomba de seus repositorys e você não os testa ou usa o mesmo db em seus testes e testa seus sqls. Todos os dbs na memory não são totalmente compatíveis, por isso não oferecem total cobertura e confiabilidade. e nunca tente simular / simular os objects db profundos como conexão, conjunto de resultados, etc. isso não lhe dá nenhum valor e é um pesadelo para desenvolver e manter

ter um database de teste pessoal é bem impossível. Você tem que usar um database “público”, que é acessível para todos

infelizmente, muitas empresas ainda usam esse modelo, mas agora temos o docker e há imagens para quase todos os bancos de dados. produtos comerciais têm algumas limitações (como até alguns GB de dados) que não são importantes para os testes. você também precisa que seu esquema e estrutura sejam criados neste database local

“Esses testes com certeza não são rápidos …” – Testes de DB tendem a ser mais lentos que os testes usuais. Não é ideal ter testes lentos.

sim, testes de db são mais lentos, mas não são tão lentos. Eu fiz algumas medições simples e um teste típico levou 5-50ms. o que leva tempo é a boot do aplicativo. Há muitas maneiras de acelerar isso:

  • As primeiras estruturas de DI (como a mola) oferecem uma maneira de executar apenas parte de sua aplicação. se você escrever sua aplicação com uma boa separação de lógica relacionada a db e não-db, então em seu teste você pode iniciar somente a parte db
  • cada db tem muitas opções de ajuste que o tornam menos durável e muito mais rápido. isso é perfeito para testes. exemplo de postgres
  • você também pode colocar o database inteiro em tmpfs

  • Outra estratégia útil é ter grupos de testes e manter os testes de database desativados por padrão (se eles realmente diminuírem sua compilation). Desta forma, se alguém está realmente trabalhando em db, ele precisa passar sinalizador adicional na linha cmd ou usar IDE (grupos de teste e seletores de teste personalizados são perfeitos para isso)

Para cada caso, uma certa quantidade de consultas de inserção / atualização deve ser feita, o que é irritante e leva tempo

‘leva tempo’ parte foi discutida acima. Isso é chato? Eu vi duas maneiras:

  • prepare um dataset para todos os seus casos de teste. então você tem que mantê-lo e raciocinar sobre isso. geralmente é separado do código. tem kilobytes ou megabytes. É grande para ver em uma canvas, para compreender e raciocinar sobre. introduz o acoplamento entre os testes. porque quando você precisa de mais linhas para o teste A, sua count(*) no teste B falha. só cresce porque mesmo quando você apaga alguns testes, você não sabe quais linhas foram usadas apenas por este teste
  • cada teste prepara seus dados. Desta forma, cada teste é completamente independente, legível e fácil de raciocinar. Isso é chato? imo, de jeito nenhum! permite escrever novos testes muito rapidamente e poupa muito trabalho no futuro

Como você sabe que existem 542 linhas nessa tabela? “- Um dos principais princípios em testes, é poder testar a funcionalidade de maneira diferente daquela do seu código testado

uhm … não realmente. O princípio principal é verificar se o seu software gera a saída desejada em resposta a uma input específica. Portanto, se você chamar o dao.insert 542 vezes e, em seguida, o dao.count retornar 542, isso significa que o software funciona conforme especificado. se você quiser, pode chamar o cache de confirmação / eliminação entre. Claro, às vezes você quer testar sua implementação ao invés do contrato e então verificar se o seu dao mudou o estado do database. mas você sempre testa o sql A usando o sql B (insert vs select, sequence next_val versus valor retornado etc). sim, você sempre terá o problema ‘quem vai testar meus testes’, e a resposta é: ninguém, então mantenha-os simples!

outras ferramentas que podem ajudá-lo:

  1. testcontainers irá ajudá-lo a fornecer db real.

  2. dbunit – ajudará você a limpar os dados entre os testes

    contras:

    • muito trabalho é necessário para criar e manter esquemas e dados. especialmente quando seu projeto está em um estágio de desenvolvimento intensivo.
    • é outra camada de abstração, então se de repente você quiser usar algum recurso de database que não é suportado por essa ferramenta, pode ser difícil testá-la
  3. testegration – intenção de fornecer um ciclo de vida completo, pronto para usar e extensível (divulgação: eu sou um criador).

    contras:

    • livre apenas para pequenos projetos
    • projeto muito jovem
  4. flyway ou liquibase – ferramentas de migration db. Eles ajudam você a criar facilmente o esquema e todas as estruturas no seu database local para testes.

Java vem com o Java DB .

Dito isto, aconselho não usar outro tipo de database do que o que você usa na produção, a menos que você passe por uma camada ORM. Caso contrário, seu SQL pode não ser tão compatível com as plataformas quanto você pensa.

Confira também DbUnit

Eu usei o Hypersonic para esse propósito. Basicamente, é um arquivo JAR (um puro database em memory Java) que você pode executar em sua própria JVM ou em sua própria JVM e enquanto está em execução, você tem um database. Então você pára e seu database desaparece. Eu usei – até agora – como um database puramente na memory. É muito simples iniciar e parar via Ant ao executar testes de unidade.

Há muitos pontos de vista sobre como testar pontos de integração, como a conexão do database via SQL. Meu conjunto pessoal de regras que funcionou bem para mim é o seguinte:

1) Separe a lógica de access ao database e as funções da lógica geral de negócios e oculte-a atrás de uma interface. Razão: A fim de testar a grande maioria da lógica no sistema, é melhor usar um dummy / stub no lugar do database real como seu mais simples. Razão 2: é dramaticamente mais rápido

2) Tratar testes para o database como testes de integração que são separados do corpo principal de testes de unidade e precisam ser executados em um database de configuração Motivo: Velocidade e qualidade dos testes

3) Todo desenvolvedor precisará de seu próprio database distinto. Eles precisarão de uma maneira automatizada para atualizar sua estrutura com base nas mudanças de seus colegas de equipe e introduzir dados. Ver pontos 4 e 5.

4) Use uma ferramenta como http://www.liquibase.org para gerenciar upgrades na sua estrutura de database. Razão: Proporciona agilidade na capacidade de alterar a estrutura existente e avançar nas versões

5) Use uma ferramenta como http://dbunit.sourceforge.net/ para gerenciar os dados. Configure arquivos de cenário (xml ou XLS) para casos de teste e dados de base específicos e limpe apenas o que é necessário para qualquer caso de teste. Razão: Muito melhor do que inserir e excluir dados manualmente Motivo 2: Mais fácil para os testadores entenderem como ajustar os cenários Razão 3: É mais rápido executar

6) Você precisa de testes funcionais que também tenham DBUnit como dados de cenário, mas são conjuntos de dados muito maiores e executam todo o sistema. Isso completa a etapa de combinar o conhecimento que a) Os testes de unidade são executados e, portanto, a lógica é sólida b) Que os testes de integração para o database são executados e SQL está correto resultando em “e o sistema como um todo funciona como um pilha de fundo ”

Essa combinação me serviu muito bem até agora para alcançar uma alta qualidade de testes e produtos, além de manter a velocidade do desenvolvimento de testes unitários e a agilidade para mudar.

“Basta obter um teste DB, quão difícil poderia ser?” – Bem, no meu local de trabalho, ter um teste pessoal DB é bem impossível. Você tem que usar um database “público”, que é acessível para todos.

Parece que você tem problemas culturais no trabalho que estão fornecendo uma barreira para que você possa fazer o seu trabalho ao máximo de suas habilidades e o benefício do seu produto. Você pode querer fazer algo sobre isso.

Por outro lado, se o esquema do database estiver sob version control, você sempre poderá ter uma compilation de teste que crie um database a partir do esquema, preencha-o com dados de teste, execute seus testes, reúna os resultados e elimine o database. Ele só existiria enquanto durar os testes. Pode ser um novo database em uma instalação existente se o hardware for um problema. Isso é semelhante ao que fazemos onde eu trabalho.

Se você estiver usando o Oracle no trabalho, poderá usar o recurso Restaurar Ponto no Banco de Dados do Flashback para fazer com que o database retorne a um tempo anterior aos seus testes. Isso eliminará todas as alterações feitas pessoalmente no database.

Vejo:

https://docs.oracle.com/cd/E11882_01/backup.112/e10642/flashdb.htm#BRADV71000

Se você precisar de um database de teste para uso com produção / trabalho do Oracle, procure o database de edição expressa do XE, da Oracle. Isto é gratuito para uso pessoal, com um limite de database menor que 2gb de tamanho.

Recentemente, mudamos para o JavaDB ou o Derby para implementar isso. O Derby 10.5.1.1 agora implementa uma representação na memory para que ele rode muito rápido, não precisa ir para o disco: Derby In Memory Primer

Nós projetamos nosso aplicativo para rodar em Oracle, PostgreSQL e Derby, para que não cheguemos muito longe em nenhuma plataforma antes de descobrir que um database suporta um recurso que outros não suportam.

Eu concordo com o banjollity. A configuração de ambientes de desenvolvimento e teste isolados deve ser uma alta prioridade. Todo sistema de database que usei é de código aberto ou possui uma edição de desenvolvedor gratuita que você pode instalar em sua estação de trabalho local. Isso permite que você desenvolva o mesmo dialeto de database que a produção, oferecendo access total de administrador aos bancos de dados de desenvolvimento e é mais rápido do que usar um servidor remoto.

Tente usar o derby . É fácil e portátil. Com o Hibernate seu aplicativo se torna flexível. Teste no derby, produção em qualquer coisa que você gosta e confia.

Eu acho que o framework Acolyte pode ser usado para tal simulação do DB: https://github.com/cchantep/acolyte .

Ele permite executar o Java existente (para teste) com conexões que você gerencia manipulação de consulta / atualização: retornando conjuntos de resultados apropriados, contagem de atualizações ou aviso de acordo com os casos de execução.

Estamos criando um ambiente de teste de database no trabalho agora. Nós sentimos que devemos usar um sistema de gerenciamento de database real com dados simulados . Um problema com um DBMS simulado é que o SQL nunca realmente se consolidou como um padrão, portanto, um ambiente de teste artificial teria que suportar fielmente o dialeto do nosso database de produção. Outro problema é que fazemos uso extensivo de restrições de valores de colunas, restrições de foreign keys e restrições únicas, e como uma ferramenta artificial provavelmente não implementaria esses, nossos testes de unidade poderiam passar, mas nossos testes de sistema falhariam quando atingissem o real. restrições. Se os testes demorarem demais, isso indica um erro de implementação e ajustamos nossas consultas (normalmente, os conjuntos de dados de teste são minúsculos em comparação à produção).

Instalamos um DBMS real em cada máquina de desenvolvedor e em nosso servidor de integração e teste contínuo (usamos o Hudson). Eu não sei quais são as restrições da sua política de trabalho, mas é muito fácil instalar e usar o PostgreSQL, o MySQL e o Oracle XE. Estes são todos gratuitos para uso em desenvolvimento (mesmo o Oracle XE), portanto não há razão racional para proibir seu uso.

A questão chave é como você garante que seus testes sempre começam com o database em um estado consistente? Se os testes fossem todos somente leitura, não há problema. Se você pudesse projetar testes mutantes para sempre rodar em transactions que nunca se comprometem, não há problema. Mas normalmente você precisa se preocupar em reverter as atualizações. Para fazer isso, você pode exportar o estado inicial para um arquivo e, em seguida, importá-lo de volta após o teste (os comandos exp e shell do Oracle fazem isso). Ou você pode usar um ponto de verificação / reversão. Mas uma maneira mais elegante é usar uma ferramenta como o dbunit , que funciona bem para nós.

A principal vantagem disso é que pegamos muito mais bugs na frente, onde eles são muito mais fáceis de consertar e nosso teste de sistema real não é bloqueado enquanto os desenvolvedores tentam debugar os problemas febrilmente. Isso significa que produzimos um código melhor mais rápido e com menos esforço.

Você poderia HSQLDB para no teste de memory db. Iniciar a base de dados na memory e executar testes nela é bastante simples.
http://hsqldb.org/

Bem, para começar, você está usando alguma Camada ORM para access ao database?
Se não, então o que você está pensando seria inútil. O que é o uso de testes quando você não tem certeza de que o SQL que está sendo triggersdo funcionará com seu database em produção, pois nos casos de teste você está usando outra coisa.
Se sim: Então você pode ter várias opções apontadas.

O jOOQ é uma ferramenta que, além de oferecer abstração SQL, também possui pequenas ferramentas, como uma SPI, que permite zombar de todo o JDBC. Isso pode funcionar de duas maneiras, conforme documentado nesta postagem do blog :

Implementando o SPI MockDataProvider :

 // context contains the SQL string and bind variables, etc. MockDataProvider provider = context -> { // This defines the update counts, result sets, etc. // depending on the context above. return new MockResult[] { ... } }; 

Na implementação acima, você pode programaticamente interceptar cada instrução SQL e retornar um resultado para ela, mesmo dinamicamente, “analisando” a sequência SQL para extrair alguns predicados / informações da tabela, etc.

Usando o mais simples (mas menos poderoso) MockFileDatabase

… que tem um formato como o seguinte (um conjunto de pares de instruções / resultados):

 select first_name, last_name from actor; > first_name last_name > ---------- --------- > GINA DEGENERES > WALTER TORN > MARY KEITEL @ rows: 3 

O arquivo acima pode ser lido e consumido da seguinte maneira:

 import static java.lang.System.out; import java.sql.*; import org.jooq.tools.jdbc.*; public class Mocking { public static void main(String[] args) throws Exception { MockDataProvider db = new MockFileDatabase( Mocking.class.getResourceAsStream("/mocking.txt"); try (Connection c = new MockConnection(db)); Statement s = c.createStatement()) { out.println("Actors:"); out.println("-------"); try (ResultSet rs = s.executeQuery( "select first_name, last_name from actor")) { while (rs.next()) out.println(rs.getString(1) + " " + rs.getString(2)); } } } } 

Observe como estamos usando a API JDBC diretamente, sem nos conectarmos a nenhum database.

Observe, eu trabalho para o fornecedor de jOOQ, então essa resposta é tendenciosa.

Cuidado, em algum momento, você está implementando um database inteiro

O acima funciona para casos simples. Mas cuidado, você acabará implementando um database inteiro. Você quer:

  1. Verifique a syntax SQL.

OK, ao ridicularizar o database como mostrado acima, você pode “verificar” a syntax, porque cada syntax que você não tenha previsto na versão exata listada acima será rejeitada por qualquer abordagem de simulação.

Você poderia implementar um analisador que analise SQL ( ou, novamente, use o jOOQ ), e depois transformar a instrução SQL em algo que você possa reconhecer e produzir um resultado mais facilmente. Mas, no final das contas, isso significa apenas implementar um database inteiro.

  1. Mais importante, verifique se os dados estão selecionados / atualizados / inseridos corretamente, de acordo com uma determinada situação.

Isso torna as coisas ainda mais difíceis. Se você executar uma inserção e, em seguida, atualizar, o resultado é obviamente diferente da atualização primeiro, em seguida, inserir, como a atualização pode ou não afetar a linha inserida.

Como você garante que isso aconteça quando “zombar” de um database? Você precisa de uma máquina de estado que se lembre do estado de cada tabela “ridicularizada”. Em outras palavras, você implementará um database.

Mocking só vai levar você até aqui

Como piotrek também mencionou, o deboche só vai levar você até aqui. É útil em casos simples quando você precisa interceptar apenas algumas consultas bem conhecidas. É impossível, se você quiser zombar do database para um sistema inteiro. Nesse caso, use um database real, idealmente o mesmo produto que você está usando na produção.