Como criar testes de integração gratuitos escalonáveis ​​e com efeitos colaterais?


14

No meu projeto atual, estou tendo dificuldades para encontrar uma boa solução para criar testes de integração escaláveis ​​que não tenham efeitos colaterais. Um pequeno esclarecimento sobre a propriedade livre de efeitos colaterais: é principalmente sobre o banco de dados; não deve haver nenhuma alteração no banco de dados após a conclusão dos testes (o estado deve ser preservado). Talvez a escalabilidade e a preservação do estado não se combinem, mas eu realmente quero buscar uma solução melhor.

Aqui está um teste de integração típico (esses testes tocam a camada do banco de dados):

public class OrderTests {

    List<Order> ordersToDelete = new ArrayList<Order>(); 

    public testOrderCreation() {
        Order order = new Order();
        assertTrue(order.save());
        orderToDelete.add(order);
    }

    public testOrderComparison() {
        Order order = new Order();
        Order order2 = new Order();
        assertFalse(order.isEqual(order2);
        orderToDelete.add(order);
        orderToDelete.add(order2);
    }
    // More tests

    public teardown() {
         for(Order order : ordersToDelete)
             order.delete();
    }
}

Como se pode imaginar, essa abordagem produz testes extremamente lentos. E, quando aplicado a todos os testes de integração, leva cerca de 5 segundos para testar apenas uma pequena parte do sistema. Eu posso imaginar esse número aumentando quando a cobertura é aumentada.

Qual seria outra abordagem para escrever esses testes? Uma alternativa em que posso pensar é ter variáveis ​​globais (dentro de uma classe) e todos os métodos de teste compartilham essa variável. Como resultado, apenas alguns pedidos são criados e excluídos; resultando em testes mais rápidos. No entanto, acho que isso apresenta um problema maior; os testes não são mais isolados e fica cada vez mais difícil entendê-los e analisá-los.

Pode ser que os testes de integração não sejam executados com a mesma frequência que os testes de unidade; portanto, baixo desempenho pode ser aceitável para eles. De qualquer forma, seria ótimo saber se alguém encontrou alternativas para melhorar a escalabilidade.

Respostas:


6

Procure usar o Hypersonic ou outro banco de dados na memória para testar a unidade. Os testes não apenas serão executados mais rapidamente, mas os efeitos colaterais não são relevantes. A reversão das transações após cada teste também possibilita a execução de vários testes na mesma instância.

Isso também forçará você a criar maquetes de dados, o que é uma coisa boa para a IMO, pois significa que algo que ocorre no banco de dados de produção não pode inexplicavelmente começar a falhar nos testes e fornece um ponto de partida para o que uma "instalação limpa de banco de dados" ", o que ajuda se você precisar implantar repentinamente uma nova instância do aplicativo sem conexão com o site de produção existente.

Sim, eu mesmo uso esse método, e sim, foi uma PITA configurar a PRIMEIRA vez, mas é mais do que pago durante a vida do projeto que estou prestes a concluir. Também existem várias ferramentas que ajudam nos modelos de dados.


Parece uma otima ideia. Eu gosto especialmente do aspecto completo de isolamento; começando com uma nova instalação do banco de dados. Parece que será necessário algum esforço, mas uma vez configurado, será muito benéfico. Obrigado.
Guven

3

Esse é o eterno problema que todos enfrentam enquanto escrevem testes de integração.

A solução ideal, principalmente se você estiver testando a produção, está abrindo uma transação na configuração e revertendo-a na desmontagem. Eu acho que isso deve atender às suas necessidades.

Onde isso não for possível, por exemplo, quando você estiver testando o aplicativo a partir da camada do cliente, outra solução é usar um banco de dados em uma máquina virtual e tirar um instantâneo na configuração e voltar a ele em desmontagem (não demore o tempo que você espera).


Não acredito que não pensei na reversão da transação. Usarei essa ideia como uma solução rápida para a solução, mas no final o banco de dados na memória parece muito promissor.
Guven

3

Os testes de integração sempre devem ser executados na configuração da produção. No seu caso, significa que você deve ter o mesmo banco de dados e servidor de aplicativos. Obviamente, por uma questão de desempenho, você pode optar por usar um banco de dados na memória.

O que você nunca deve fazer é estender o escopo da transação. Algumas pessoas sugeriram assumir o controle da transação e revertê-la após os testes. Ao fazer isso, todas as entidades (supondo que você esteja usando o JPA) permanecerão anexadas ao contexto de persistência durante a execução do teste. Isso pode resultar em alguns erros muito desagradáveis ​​que são muito difíceis de encontrar .

Em vez disso, você deve limpar o banco de dados manualmente após cada teste através de uma biblioteca como JPAUnit ou semelhante a essa abordagem (limpe todas as tabelas usando JDBC).

Como seus problemas de desempenho, você não deve executar os testes de integração em todas as versões. Deixe seu servidor de integração contínua fazer isso. Se você usa o Maven, pode aproveitar o plug-in à prova de falhas , que permite separar seus testes em testes de unidade e integração.

Além disso, você não deve zombar de nada. Lembre-se de que você está testando a integração, ou seja, testando o comportamento no ambiente de execução em tempo de execução.


Boa resposta. Um ponto: pode ser aceitável zombar de algumas dependências externas (por exemplo, enviando email). Mas você deve simular a criação de um serviço / servidor de simulação completo, e não o código Java.
sleske

Sleske, eu concordo com você. Especialmente quando você estiver usando o JPA, EJB, JMS ou implementando com outra especificação. Você pode trocar o servidor de aplicativos, o provedor de persistência ou o banco de dados. Por exemplo, você pode querer usar o Glassfish e o HSQLDB incorporados para simplesmente configurar e melhorar a velocidade (é claro que você pode escolher outra implementação certificada).
BenR 22/10/11

2

Sobre escalabilidade

Eu já tive esse problema algumas vezes antes, que os testes de integração estavam demorando muito para serem executados e não eram práticos para um único desenvolvedor executar continuamente em um ciclo de feedback apertado de alterações. Algumas estratégias para lidar com isso são:

  • Escreva menos testes de integração - se você tiver uma boa cobertura com testes de unidade no sistema, os testes de integração devem se concentrar mais em questões de integração (ahem), em como os diferentes componentes trabalham juntos. Eles são mais parecidos com os testes de fumaça, que apenas tentam ver se o sistema ainda funciona como um todo. Portanto, idealmente, seus testes de unidade cobrem a maioria das partes funcionais do aplicativo e os testes de integração apenas verificam como essas partes interagem umas com as outras. Você não precisa de uma cobertura abrangente de caminhos lógicos aqui, apenas alguns caminhos críticos pelo sistema.
  • Execute apenas um subconjunto dos testes de cada vez - como um desenvolvedor único trabalhando em alguma funcionalidade, normalmente você tem noção de quais testes são necessários para cobrir essa funcionalidade. Execute apenas os testes que fazem sentido para cobrir suas alterações. Estruturas como JUnit permitem agrupar equipamentos em uma categoria . Crie os agrupamentos que permitem a melhor cobertura para cada recurso que você possui. Obviamente, você ainda deseja executar todos os testes de integração em algum momento, talvez antes de se comprometer com o sistema de controle de origem e, certamente, no servidor de integração contínua.
  • Otimize o sistema - Os testes de integração, juntamente com alguns testes de desempenho, podem fornecer a entrada necessária para ajustar partes lentas do sistema, para que os testes sejam executados mais rapidamente mais tarde. Isso pode ajudar a descobrir os índices necessários no banco de dados, interfaces de bate-papo entre subsistemas ou outros gargalos de desempenho que precisam ser endereçados.
  • Execute seus testes em paralelo - Se você tiver um bom agrupamento de testes ortogonais, tente executá-los em paralelo . Esteja atento ao requisito ortogonal que mencionei, porém, você não quer que seus testes pisem nos pés um do outro.

Tente combinar essas técnicas para obter um efeito maior.


1
Diretrizes realmente boas; escrever menos testes de integração é com certeza o melhor conselho. Além disso, executá-los em paralelo seria uma alternativa muito boa; um "teste" perfeito para verificar se os meus testes foram corretos em termos de isolamento.
Guven

2

Para fins de teste, usamos uma implantação baseada em arquivo de um banco de dados SQLite (basta copiar um recurso). Isso foi feito para que também pudéssemos testar as migrações de esquema. Tanto quanto sei, as alterações do esquema não são transacionais, portanto, não serão revertidas depois que uma transação for abotada. Além disso, não ter que depender do suporte a transações para a configuração de teste permite testar o comportamento das transações do seu aplicativo corretamente.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.