Desenvolvedores precisam trabalhar com dois ou mais sistemas durante o dia de trabalho. A qualidade desses sistemas depende da integração e dos testes unitários, exigindo uma pipeline e uma grande infraestrutura. A pipeline executa testes em um servidor semelhante ao servidor de produção, chamado staging. Segundo o Server Mania e a Unimake , o custo para manter um servidor varia de 2 mil a 15 mil na produção, sendo um custo elevado dependendo da empresa. Esse custo duplica quando é necessário um ambiente para produção e outro para staging. No entanto, uma pipeline que executa testes em um ambiente de staging pode resultar em muitos erros falsos positivos quando um sistema dependente está inativo ou uma equipe está implantando uma nova versão. Além disso, uma equipe de desenvolvedores pode enfrentar outros problemas, como a quantidade elevada de plataformas para instalar, diferenças nas versões das plataformas por desenvolvedor, dificuldade em atualizar a versão da plataforma e diferenças nas versões das plataformas entre os ambientes de produção e desenvolvimento.
Um sistema de API REST precisa de pelo menos um banco de dados e um sistema de mensagem. No entanto, um sistema de mercado real precisa de um serviço de cache, um sistema de armazenamento, e a lista de plataformas pode aumentar de acordo com os requisitos de mercado. Portanto, para executar localmente o sistema, um desenvolvedor precisa instalar todas essas plataformas. O número pode aumentar exponencialmente quando há mais sistemas. O desenvolvedor precisa instalar diferentes versões de cada plataforma para cada sistema. Outro problema é que um desenvolvedor pode instalar uma plataforma com a versão x e outro desenvolvedor instalar a versão z. O primeiro desenvolvedor pode criar um teste que funciona na versão x, mas não funciona na versão z. Assim, o segundo desenvolvedor não consegue executar os testes e precisa de ajuda para descobrir o erro devido à diferença nas versões. Um problema semelhante ocorre quando um bug ocorre na produção e não ocorre localmente devido à diferença nas versões. Além disso, quando um novo desenvolvedor tenta configurar o ambiente de desenvolvimento, o código clonado não possui a versão das plataformas. Portanto, ele precisa instalar a última versão disponível para executar os testes. O resultado é que muitos testes falharão, e, no pior caso, ele não conseguirá executar o sistema localmente sem a ajuda de um desenvolvedor sênior.
Este artigo visa ajudar os desenvolvedores a economizarem dinheiro e a continuarem aumentando a qualidade na entrega do sistema. Mostraremos como usar duas ferramentas, Docker e TestContainers, para diminuir o custo de testar sistemas com Java. Utilize o Docker para fornecer a infraestrutura para suas dependências, e o TestContainers é uma biblioteca que permite criar diferentes cenários para seus testes. Portanto, o leitor aprenderá a usar essas ferramentas para criar seu ambiente de teste facilmente, com o menor custo e sem dificuldades ao trabalhar com uma equipe de desenvolvedores.
Criando Infraestrutura com Docker
Todo software precisa de alguma infraestrutura, como um banco de dados ou um sistema de mensagens. O processo básico para um desenvolvedor testar o software na fase de desenvolvimento é criar manualmente o ambiente em sua estação de trabalho. O Docker é uma ferramenta que pode ajudar o desenvolvedor a executar cada dependência em contêineres. Saiba mais sobre o Docker na [Docker Official].
Provisionamento com a Biblioteca TestContainers
A biblioteca TestContainers facilita a criação de todos os ambientes para seus testes no início da plataforma de teste. Ela permite criar qualquer tipo de contêiner programaticamente, definir as portas, as redes, a versão dos contêineres e configurar corretamente cada teste. Encontre mais informações no [TestContainers Quick Start].
Usando TestContainers
Antes de tudo, é necessário adicionar a biblioteca ao gerenciamento de dependências. No Gradle, você pode fazer isso da seguinte forma:
Existem dois modos para configurar contêineres em seus testes: contêiner compartilhado e contêiner local. O primeiro tipo cria e interrompe o contêiner apenas quando a JVM é encerrada, e o segundo cria um novo contêiner para cada método de teste. Em ambos os modos, você cria um contêiner para seu teste, começando pela criação de uma classe para o contêiner ou configurando uma biblioteca que cria um contêiner para você. Veja abaixo um exemplo de como criar uma classe de contêiner para o PostgreSQL:
Depois disso, você pode criar um contêiner para seu teste usando esta classe. Uma observação aqui é a necessidade de uma nova dependência que possui a classe `PostgreSQLContainer`:
Agora, veja um exemplo de teste usando essa classe:
Este cenário é para ter controle sobre o contêiner. Existe uma anotação que informará ao TestContainers qual contêiner precisa ser iniciado e interrompido para cada método de teste. Veja um exemplo abaixo:
Usando uma Plataforma Web
Mas este é um teste sem nenhuma plataforma web, como Quarkus ou Spring. A configuração do TestContainers para o Quarkus, por exemplo, precisa de algumas novas classes. Antes disso, precisamos entender nossos requisitos. Dependendo do seu cenário, será necessário iniciar e interromper um contêiner para cada classe de teste ou reutilizar um contêiner para todos os testes. Para o primeiro caso, crie uma classe que implemente `QuarkusTestResourceLifecycleManager`. Por exemplo, a classe abaixo:
Com esta classe, é possível controlar a criação e destruição de um contêiner. Quando o contexto do Quarkus inicia, ele criará o contêiner. Depois, ele chamará o método `start` para obter todas as propriedades a serem substituídas. Por exemplo, a porta para conectar ao banco de dados do PostgreSQL. Quando o contexto do Quarkus é encerrado, o método `stop` é chamado, permitindo parar o contêiner. Se não for necessário um novo contêiner para cada classe de teste, é possível remover o contêiner parado e criar um campo estático assim:
Com essa configuração, é possível criar um contêiner para o contexto, e no final da execução, a biblioteca TestContainer interromperá automaticamente o contêiner. Recomendamos esse recurso para cenários em que você tem testes independentes e não precisa de nova infraestrutura, como um banco de dados limpo. Usando esse modo, é possível gastar menos tempo executando os testes do que usando um contêiner local.
Mais detalhes podem ser encontrados no [ TestContainers Quick Start ] na documentação oficial.
Ambiente
Usamos as seguintes versões:
- TestContainers: 1.17.2
- Gradle: 6
- Docker: 20
- Quarkus: 1.13.6.Final
- Java: 11
O código de exemplo está neste [ repositório ]
Conclusão
Neste artigo, mostramos como criar testes de integração com contêineres para simular qualquer tipo de ambiente. Também mostramos os dois modos de configurar os contêineres em seus testes e as diferenças entre eles. Além disso, demos exemplos de como usar a biblioteca TestContainers com a plataforma Quarkus.
Entretanto, neste artigo, não abordamos como usar o TestContainers com outras plataformas web como o Spring. Além disso, mais bibliotecas integram-se ao Docker em outras linguagens. A comunidade criou muitos repositórios na conta do TestContainers no [GitHub] para compartilhar integrações com outras linguagens.