Qual seria a melhor abordagem para testar a unidade de um modelo que se integra a um aplicativo fortemente acoplado ao banco de dados?
O cenário específico aqui é um carrinho de compras - eu gostaria de poder testar a adição, remoção e recuperação de itens do carrinho, além da lógica de preços etc. Isso, na minha opinião, exige acesso ao banco de dados, embora eu tenha lido várias vezes que acesso ao banco de dados deve ser evitado.
unit-testing
user1189880
fonte
fonte
Respostas:
A injeção de dependência é uma maneira de lidar com isso. Você pode configurar um banco de dados de teste para imitar o carrinho de compras ou até escrever algum código que "confirme" a transação do cliente. Então, em tempo de execução, seu software escolherá qual componente se conectar.
Apenas não conecte-se ao banco de dados de produção para nada durante o teste!
fonte
No teste de unidade, você precisa definir o limite do que está testando. O teste de unidade é diferente do teste de integração. Se a lógica de preços for independente do conteúdo do carrinho, teste-o separadamente. Se não for esse o caso, e todos os módulos estiverem fortemente acoplados, crie um ambiente de teste que imite a produção o máximo possível e trabalhe com isso. Não acredito que atalhos e simulações ajudem a longo prazo.
fonte
O modelo não deve depender de um banco de dados (concreto). Se ele conhece apenas um banco de dados abstrato (leia "interface") que é entregue ao modelo, você pode substituir o banco de dados por um objeto simulado .
fonte
Eu tive um problema semelhante - não tinha possibilidade de garantir que meu banco de dados de teste mantivesse os valores. Então, no futuro, recebo, por exemplo, outros preços.
Extraí os dados necessários para um pequeno sqlite -DB e usei esse banco de dados para meus testes. O banco de dados de teste agora faz parte da configuração do meu teste de unidade.
fonte
"Melhor" é subjetivo, mas você pode simplesmente usar uma conexão db de teste.
Use acessórios para carregar alguns dados de teste (exemplo de produtos a serem comprados) e depois escreva o caso de teste para a classe / função que você deseja testar.
fonte
Eu construí um plugin para o Symfony 1.4 (PHP) para resolver esse problema (entre outros). Ele é modelado de acordo com a maneira como a estrutura de teste do Django (Python) opera : a estrutura cria e preenche um banco de dados de teste separado antes de cada teste iniciar e destrói o banco de dados de teste após a conclusão de cada teste.
Eu tinha algumas preocupações com essa estratégia, tanto em termos de desempenho (se o esquema não muda, por que não simplesmente limpar os dados em vez de reconstruir toda a estrutura?) E conveniência (às vezes eu quero inspecionar o banco de dados depois de um falha no teste, por isso não a destrua indiscriminadamente!), por isso adotei uma abordagem ligeiramente diferente.
Antes da execução do primeiro teste, o banco de dados é destruído e reconstruído, caso haja alterações no modelo desde o último teste. Antes de cada teste subsequente, os dados no banco de dados são limpos, mas a estrutura não é reconstruída (embora uma reconstrução manual possa ser acionada a partir de um teste, se necessário).
Ao carregar seletivamente acessórios de dados em cada teste, é possível criar o ambiente adequado para esse teste sem interferir nos testes subsequentes. Os arquivos de fixação também podem ser reutilizados, o que torna essa tarefa muito menos onerosa (embora ainda seja minha parte menos favorita dos testes de escrita!).
Nas duas estruturas de teste, o adaptador de banco de dados está configurado para usar a conexão de teste em vez da conexão "produção" para impedir que a execução do teste corrompa os dados existentes.
fonte
Eu diria, basta ir em frente e usar equipamentos para pré-carregar os dados. É como as estruturas de teste de unidade parecem funcionar em geral, ao testar a manipulação de dados.
Mas se você realmente deseja evitar ter que se conectar a um banco de dados de qualquer tipo e seguir a definição excessivamente rigorosa de que os testes de unidade não tocam em nada fora do código, dê uma olhada na zombaria de objetos - isso pode lhe dar idéias.
Por exemplo, em vez de soltar o SQL diretamente no código em que você precisa, tenha uma maneira de chamar um método que faça apenas o que esse SQL faz. Use
Person.getPhoneNumber()
, por exemplo, em vez deSELECT phone_number FROM person WHERE id = <foo>
. Não apenas é mais fácil e fácil entender rapidamente, mas durante o teste, você pode zombar do objeto Person para quegetPhoneNumber()
ele sempre retorne555-555-5555
ou algo assim, em vez de tocar no banco de dados.fonte
Isso é bastante fácil com junit, se um pouco longo.
A "configuração" deve definir e preencher um conjunto de tabelas temporárias.
Você pode executar os testes de unidade para todas as funcionalidades de atualização, inserção e exclusão.
Para cada teste, você chama seu método de atualização e, em seguida, executa um SQL para verificar o resultado esperado.
Na fase de "desmontagem", você solta todas as tabelas.
Dessa maneira, você sempre executa os mesmos testes nos mesmos dados iniciais. Se você mantiver as tabelas entre os testes, elas acabam sendo "poluídas" por testes com falha, também, um teste consistente de "inserção" é quase impossível, pois você precisa continuar inventando novas chaves em todos os testes.
fonte