Banco de dados não transacional e testes de integração

8

Um problema que acho que vou encontrar nos meus testes de integração é ter vários testes acessando o mesmo banco de dados. Embora isso não seja um problema agora, eu sei que temos vários aplicativos aqui que acessam o mesmo banco de dados e só estou tentando pensar em uma maneira de evitar esse problema antes que ele aconteça.

Uma ideia que tenho visto muito é usar transações. Na inicialização, você inicia uma transação e, na desmontagem, reverte a transação. Isso significa que vários testes acessam as mesmas tabelas do banco de dados e não afetam um ao outro, o que é ótimo. O problema que tenho é que, no meu caso, 85-95% das tabelas com as quais estou trabalhando no MySQL são MyISAM, que não suportam transações.

Existem maneiras de contornar os mecanismos de armazenamento que não suportam transações, mas ainda permitem que vários testes acessem as mesmas tabelas sem que afetem um ao outro? Pelo que ouvi dizer, a estrutura de teste do ruby ​​on rails usa transações dessa maneira, como eles resolvem esse problema (ou eles)?

ryanzec
fonte
Você executa testes simultaneamente?
Jeffo
Eu poderia. Eu não executaria testes simultâneos do mesmo projeto, mas temos vários projetos para acessar o mesmo banco de dados / tabelas. Eu pude ver dois projetos executando seus testes ao mesmo tempo.
Ryanzec
1
+1 É ainda mais difícil se você precisar executar testes simultaneamente.
KLE

Respostas:

4

Na minha empresa, houve esse debate: persistência é um grande problema para testes.

Existem alguns hacks que o levarão a meio caminho. Decidimos não perder nosso tempo com eles:

  • usando transações que você reverte . Isso falha como:
    • Às vezes, você abre uma transação isolada (dentro de uma transação mais geral), portanto ela foi fechada antes que seu código de teste pudesse controlá-la
    • Você não pode testar sua persistência real. (O que acontece quando um teste fecha uma transação e executa uma consulta complexa afetada por esses dados em uma transação diferente; ou salva os dados e carrega-os novamente mais tarde.)
    • Você não pode testar seu código completamente (apenas as camadas abaixo do nível da transação; não é bom o suficiente se considerar as desvantagens do teste de integração).
  • usando um banco de dados limpo e atualizado para cada teste . Isso também falha como:
    • em breve, nosso conjunto de testes chegará a alguns milhares de testes (temos grandes equipes). A configuração de um novo banco de dados requer, na melhor das hipóteses, minutos (já que temos muitos catálogos e parâmetros que fazem parte de cada versão). Em breve, nossa suíte de testes levará meses para terminar e podemos esperar apenas alguns dias.
    • precisaríamos de uma instância de banco de dados específica para cada desenvolvedor ou máquina e ter uma política para iniciar apenas um teste por vez. Precisamos usar o Oracle 11 para verificar se nossas consultas estão corretas, o que seria muito caro (licenças Oracle).
  • limpeza cuidadosa após cada teste (as estruturas de teste fornecem ganchos para a execução de código após um teste). Isso falha como:
    • é muito caro manter uma combinação perfeita entre o código que doese o teste que undoes. Assim que a correspondência não for perfeita, os sintomas não ficarem claros, um teste poderá falhar enquanto estiver algumas centenas de linhas depois; ou um teste que deve falhar pode passar erroneamente.
    • sempre há casos extremos em que a limpeza não está correta (falha na máquina ou no software?). Isso deixa um estado desconhecido para execuções posteriores.
    • quando um teste mostra um erro, o estado do banco de dados seria uma informação essencial, mas seria perdida (porque fazemos a limpeza antes do próximo teste, para concluir o conjunto de testes e mostrar ao desenvolvedor uma informação completa).

Então, mudamos para uma política que sabíamos que era válida:

  • Temos testes de unidade automáticos para nossos requisitos :
    • se necessário, acessos simulados ao banco de dados (mas geralmente não precisamos do JMock, eu poderia explicar nosso design, se solicitado).
    • os testes são executados com uma taxa superior a 100 por segundo
    • os testes são rápidos para escrever, curtos e claros para leitura.
    • os testes não dependem de nenhum estado persistente; portanto, são totalmente isolados um do outro naturalmente (não é necessário nenhum mecanismo complexo)
  • teste de integração com o banco de dados separadamente (ou seja, solicitação por solicitação, não integrada à lógica do aplicativo).
    • consideramos automatizar essa tarefa, mas parecia muito cara (veja a parte anterior da minha postagem)
    • portanto, mantivemos o manual desses testes: espera-se que cada desenvolvedor que modifique uma consulta a teste novamente no banco de dados (que geralmente faz parte de seus testes de ponta a ponta por meio da interface do usuário).
KLE
fonte
2

Mesmo que você não tenha "transações", no sentido T-SQL, você ainda deve se esforçar para tornar suas transações (no sentido geral do termo) atômicas. Os testes não devem depender um do outro e devem ser reversíveis. Se você não possui nenhum escopo oficial de reversão ou transação, convém criar seu próprio. Por exemplo, você pode fazer com que seus testes de unidade executem uma limpeza, onde eles excluem todos os registros que foram criados no teste.

Morgan Herlocker
fonte
Isso já acontece sem transação (no final, as tabelas são esvaziadas, pois eu sempre inicio meus testes com tabelas vazias), mas isso não resolve o problema de vários testes em execução ao mesmo tempo. Se estou executando testes de integração para o aplicativo A e o aplicativo B ao mesmo tempo e ambos inserem 10 registros no banco de dados.table_a, se eu tiver um teste para garantir que há 10 registros na tabela, mas obtive um resultado de 10, o teste falharia mesmo que esse teste estivesse funcionando. É o caso que estou tentando evitar.
Ryanzec
@ryanzec - Eu não sigo exatamente. Se você tiver um teste para garantir que 10 registros estejam na tabela e obter um resultado de 10, parece que o teste será aprovado.
Morgan Herlocker
@ryanzec - Acho que posso entender o que você quer dizer. Se o aplicativo A exigir a aprovação de 10 registros na tabela, mas o aplicativo B inseriu alguns extras ao mesmo tempo, o teste de A falhará. Nesse caso, você precisará escrever seus testes de maneira diferente. Eu forneceria algum tipo de número de transação, para não procurar 10 registros em uma tabela para um teste, mas 10 registros com um número de transação associado.
Morgan Herlocker
0

Apenas peça que cada usuário ou cada execução substitua o banco de dados usado. É o que fazemos no trabalho. Você nunca terá problemas com dois testes simultâneos que interferem entre si.

Cada execução de teste cria o banco de dados com migrações, preenche o banco de dados com acessórios e, em seguida, desativa-os novamente no final.

Se o DBMS suportar transações, usamos isso como uma otimização para a configuração inicial e desmontagem. Eles são opcionais, embora seu teste possa demorar um pouco sem ele. Como sempre YYMV.

Sem confusão, sem confusão.

dietbuddha
fonte