Pergunta

Eu trabalho com um monte de aplicações web que são movidos por bancos de dados de complexidade variável no backend. Normalmente, há uma camada separada da lógica de negócios e apresentação ORM . Isso faz com que a lógica de negócios bastante simples de testes de unidade; coisas podem ser implementadas em módulos discretos e todos os dados necessários para o teste pode ser falsificado por meio de objeto de zombaria.

Mas testando o ORM e banco de dados em si sempre foi repleta de problemas e compromissos.

Ao longo dos anos, tenho tentado algumas estratégias, nenhum dos quais me completamente satisfeito.

  • Coloque um banco de dados de teste com dados conhecidos. Executar testes contra o ORM e confirmam que os dados corretos volta. A desvantagem aqui é que seu teste DB tem de manter-se com quaisquer alterações de esquema no banco de dados do aplicativo e pode ficar fora de sincronia. Ele também se baseia em dados artificiais, e pode não expor os erros que ocorrem devido à entrada do usuário estúpido. Finalmente, se o banco de dados de teste é pequeno, não vai revelar ineficiências como um índice faltando. (OK, esse último não é realmente o teste de unidade deve ser usado para, mas não faz mal.)

  • Coloque uma cópia do banco de dados de produção e teste contra isso. O problema aqui é que você pode não ter nenhuma idéia do que está no DB produção em um determinado momento; os testes podem ter de ser reescrito se os dados muda ao longo do tempo.

Algumas pessoas têm apontado que essas duas estratégias dependem de dados específicos, e uma unidade de teste deve testar apenas a funcionalidade. Para esse fim, eu vi sugerido:

  • Usar um servidor de banco de dados de simulação e verificar apenas que o ORM está enviando as consultas corretas em resposta a uma determinada chamada de método.

Que estratégias você usou para testar aplicações de bases de dados, se houver? O que tem trabalhado o melhor para você?

Foi útil?

Solução

Eu realmente usou sua primeira abordagem com algum sucesso, mas em uma forma ligeiramente diferente que eu acho que iria resolver alguns dos seus problemas:

  1. Mantenha todo o esquema e roteiros para criá-lo no controle de origem para que qualquer pessoa pode criar o esquema de banco de dados atual, após um check-out. Além disso, manter os dados de amostra em arquivos de dados que são carregados por parte do processo de construção. Como você descobrir dados que provoca erros, acrescente aos seus dados de amostra para verificar que os erros não re-emergir.

  2. Use um servidor de integração contínua para construir o esquema de banco de dados, carregar os dados de exemplo, e executar testes. Isto é como nós manter nosso banco de dados de teste em sincronia (reconstruí-lo em cada teste). Embora isto requer que o servidor CI têm acesso e posse de sua própria instância de banco de dados dedicado, eu digo que ter o nosso esquema db construído 3 vezes por dia ajudou dramaticamente erros achar que provavelmente não teria sido encontrado até pouco antes da entrega (se não mais tarde ). Eu não posso dizer que eu reconstruir o esquema antes de cada commit. Alguem? Com essa abordagem, você não terá que (bem, talvez nós devemos, mas não é um grande negócio se alguém se esquece).

  3. Para o meu grupo, de entrada de utilizador é feita ao nível da aplicação (não db) de modo que este é testado através de testes de unidade padrão.

Banco de Dados Carregando Produção Cópia:
Esta foi a abordagem que foi usado no meu último emprego. Foi uma grande causa dor de um par de questões:

  1. A cópia sairia da data a partir da versão de produção
  2. As alterações seriam feitas para o esquema do cópia e não iria ficar propagadas para os sistemas de produção. Neste ponto, teríamos esquemas divergentes. Não é divertido.

Mocking Database Server:
Nós também fazer isso no meu trabalho atual. Depois de todos os nós cometer executar testes de unidade contra o código de aplicação que tem assessores db simulados injectado. Em seguida, três vezes por dia nós executamos a construção db completo descrito acima. Eu recomendo definitivamente ambas as abordagens.

Outras dicas

Estou sempre correndo testes contra um in-memory DB (HSQLDB ou Derby) por estas razões:

  • Isso faz você pensar que os dados a ter em seu teste DB e por quê. Apenas tirante sua produção DB em um sistema de teste se traduz em "Eu não tenho idéia do que estou fazendo ou por que e se algo der errado, não foi me !!" ;)
  • Faz-se que o banco de dados pode ser recriada com pouco esforço em um lugar novo (por exemplo, quando precisamos replicar um bug de produção)
  • Ele ajuda muito com a qualidade dos arquivos DDL.

O in-memory DB é carregado com novos dados uma vez que os testes de começar e depois a maioria dos testes, invoco ROLLBACK para mantê-lo estável. Sempre manter os dados no estábulo teste DB! Se os dados muda o tempo todo, você não pode teste.

Os dados são carregados a partir de SQL, um modelo DB ou um despejo / backup. Eu prefiro lixeiras se eles estão em um formato legível, porque eu posso colocá-los em 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 sei. Você nunca tem que carregar enormes quantidades de dados :) Não para testes de unidade. Os testes de desempenho são uma outra questão e regras diferentes.

Eu tenho feito essa pergunta por um longo tempo, mas acho que não há nenhuma bala de prata para isso.

O que faz atualmente está zombando os objetos DAO e manter um em representação de memória de uma boa coleção de objetos que representam casos interessantes de dados que poderia viver 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 DAO, mas nunca testar o DAO em si, e na minha experiência, vejo que um monte de erros acontecem nessa camada como bem. Eu também manter alguns testes de unidade que são executados no banco de dados (por causa do uso de TDD ou teste rápido no local), mas esses testes nunca são executados no meu servidor de integração contínua, uma vez que não mantêm um banco de dados para esse fim e eu acho que os testes que são executados no servidor CI deve ser auto-suficiente.

Outra abordagem que eu acho muito interessante, mas nem sempre vale a pena desde que é um pouco demorado, é criar o mesmo esquema que você usa para a produção em um banco de dados integrado que apenas é executado no teste de unidade.

Mesmo que não há dúvida esta abordagem melhora a sua cobertura, existem algumas desvantagens, uma vez que você tem que ser o mais próximo possível para ANSI SQL para que ele funcione tanto com os seus SGBD atuais e a substituição incorporado.

Não importa o que você acha que é mais relevante para o seu código, existem alguns projetos lá fora, que pode tornar mais fácil, como DbUnit .

Mesmo que existem ferramentas que permitem que você para zombar seu banco de dados, de uma forma ou de outra (por exemplo jOOQ ' s MockConnection , que pode ser visto em esta resposta - aviso legal, eu trabalho para o fornecedor de jOOQ), eu aconselharia não para simulada bancos de dados maiores, com consultas complexas.

Mesmo se você só quer integração-teste seu ORM, cuidado que um questões ORM uma série muito complexa de consultas ao banco de dados, que podem variar em

  • sintaxe
  • complexidade
  • ordem (!)

Mocking tudo o que produzir dados fictício sensata é muito difícil, a menos que você está realmente construindo um pouco de banco de dados dentro de sua simulada, que interpreta as instruções SQL transmissíveis. Dito isso, use um banco de dados integração-teste bem conhecido que você pode facilmente redefinir com os dados conhecidos, contra o qual você pode executar seus testes de integração.

Eu uso o primeiro (executando o código contra um banco de dados de teste). A única questão substantiva Eu vejo você levantando com esta abordagem é a possibilidade de esquemas ficando fora de sincronia, o que eu lidar com mantendo um número de versão no meu banco de dados e fazer todas as alterações de esquema através de um script que aplica as alterações para cada incremento da versão.

Eu também fazer todas as alterações (incluindo o esquema de banco de dados) contra o meu ambiente de teste em primeiro lugar, por isso acaba sendo o contrário: Após todos os testes passam, aplicar as atualizações de esquema para o host de produção. Eu também manter um par separado de testar bancos de dados de aplicativos vs. no meu sistema de desenvolvimento para que eu possa verificar lá que a atualização db funciona corretamente antes de tocar a caixa real de produção (es).

Eu estou usando a primeira abordagem, mas um pouco diferente que permite abordar os problemas que você mencionou.

Tudo o que é necessário para executar testes para DAOs é no controle de origem. Inclui esquema e scripts para criar o DB (janela de encaixe é muito bom para isso). Se o DB incorporado pode ser usado - eu usá-lo para a velocidade

.

A diferença importante com as outras abordagens descritas é que os dados que são necessários para o teste não é carregado a partir de scripts SQL ou arquivos XML. Tudo (exceto alguns dados de dicionário que é efetivamente constante) é criado por aplicativo usando funções de utilidade / classes.

O objetivo principal é fazer com que os dados usados ??pelo teste

  1. muito perto do teste
  2. explícitas (usando arquivos SQL para dados tornam muito problemático para ver o pedaço de dados é usado por aquilo teste)
  3. testes isolar do alterações não relacionadas.

Basicamente, significa que estes utilitários permitem especificar declarativamente únicas coisas essenciais para o teste em si mesmo teste e omitir coisas irrelevantes.

Para dar uma ideia do que significa, na prática, considere o teste para alguns DAO que funciona com Comments para Posts escritos por Authors. A fim de operações de teste de CRUD para tal DAO alguns dados deve ser criado no DB. O teste seria parecido com:

@Test
public void savedCommentCanBeRead() {
    // Builder is needed to declaratively specify the entity with all attributes relevant
    // for this specific test
    // Missing attributes are generated with reasonable values
    // factory's responsibility is to create entity (and all entities required by it
    //  in our example Author) in the DB
    Post post = factory.create(PostBuilder.post());

    Comment comment = CommentBuilder.comment().forPost(post).build();

    sut.save(comment);

    Comment savedComment = sut.get(comment.getId());

    // this checks fields that are directly stored
    assertThat(saveComment, fieldwiseEqualTo(comment));
    // if there are some fields that are generated during save check them separately
    assertThat(saveComment.getGeneratedField(), equalTo(expectedValue));        
}

Isto tem várias vantagens sobre os scripts SQL ou arquivos XML com dados de teste:

  1. Manter o código é muito mais fácil (adicionando uma coluna obrigatória por exemplo, em alguma entidade que é referenciado em muitos testes, como autor, não requer a muitas mudanças de arquivos / registros, mas apenas uma mudança no construtor e / ou de fábrica)
  2. Os dados requeridos pelo teste específico é descrito no próprio teste e não em algum outro arquivo. Esta proximidade é muito importante para compreensão de teste.

reversão vs Commit

Acho que é mais conveniente que os testes cometem quando eles são executados. Em primeiro lugar, alguns efeitos (por exemplo DEFERRED CONSTRAINTS) não pode ser verificado se comprometer nunca acontece. Em segundo lugar, quando um teste falhar, os dados podem ser examinados no PO, uma vez que não é revertido pela reversão.

De causa este tem uma desvantagem que o teste pode produzir um conjunto de dados quebrados e isso vai levar a falhas em outros testes. Para lidar com isso eu tentar isolar os testes. No exemplo acima, cada teste pode criar novos Author e todas as outras entidades são criadas relacionada a ele para colisões são raras. Para lidar com as invariantes restantes que podem ser potencialmente quebrados, mas não podem ser expressas como um nível de restrição DB eu uso algumas verificações programáticas para condições de erro que podem ser executados depois de cada teste individual (e eles são executados em CI, mas geralmente desligada localmente para o desempenho razões).

Por projecto baseado JDBC (direta ou indiretamente, por exemplo, JPA, EJB, ...) você não pode maquete de todo o banco de dados (em tal caso, seria melhor usar um db de teste em um RDBMS real), mas apenas mockup a nível JDBC.

A vantagem é abstração que vem com essa maneira, como dados JDBC (conjunto de resultados, contagem de atualização, aviso, ...) são os mesmos qualquer que seja o backend: o seu db prod, um db de teste, ou apenas alguns dados fornecidos maquete para cada caso de teste.

Com conexão JDBC zombou-se para cada caso, não há necessidade de gerenciar db de teste (limpeza, apenas um teste no momento, luminárias de recarga, ...). Cada conexão maquete é isolado e não há necessidade de limpar. Somente mínimas equipamentos necessários são fornecidos em cada caso de teste para mock up troca JDBC, que ajuda a complexidade evitar de gerenciar um db teste inteiro.

Acólito é o meu quadro que inclui um driver JDBC e utilidade para este tipo de maquete: http://acolyte.eu. org .

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top