Simplificando o desenvolvimento de aplicativos Java com o MongoDB: um guia abrangente para usar testcontainers
Aasawari Sahasrabuddhe6 min read • Published Jul 22, 2024 • Updated Jul 22, 2024
APLICATIVO COMPLETO
Avalie esse Artigo
O mundo do desenvolvimento de software é um ciclo contínuo. Construímos, testamos, implementamos e iteramos – nos esforçando para fornecer aplicativos de alta qualidade. Dentro deste ciclo de vida de desenvolvimento de software (SDLC), o teste é crucial para garantir que nosso código funcione conforme o esperado. Existem dois tipos principais de testes que encontramos:
- Teste de unidade: nesta parte, a intenção é verificar se a lógica de negócios do código está funcionando bem e se nenhum resultado inesperado foi formado.
- Teste de integração: Nesta parte do teste, o desenvolvedor avalia como diferentes partes do sistema interagem entre si. Eles simulam cenários do mundo real integrando-se a dependências externas. Ao fornecer uma visão mais holística do comportamento do aplicativo, os testes de integração tradicionais podem ser complicados de configurar e manter devido à necessidade de recursos externos, como bancos de dados.
O Testcontainers é uma estrutura de código aberto para Java que aproveita o poder dos contêineres do Docker para agilizar os testes de integração, criando instâncias leves e descartáveis de dependências reais nos contêineres do Docker durante o teste.
Este tutorial explorará o uso doTestcontainers com Java e Spring Boot, aproveitando o MongoDB como nosso banco de dados. Mostraremos como configurar o Testcontainers, integrá-lo ao seu projeto e usá-lo efetivamente para simplificar seus testes de integração.
Vamos começar.
- Java versão 22 — Baixe e instale o JDK mais recente a partir do site oficial da Oracle
- Versão do Maven 3.9.6 – Baixe e instale a versão mais recente do site oficial da Maven
- Docker versão 26.0.0 – Baixe e instale o Docker Desktop para seu sistema operacional a partir do site oficial
Como desenvolvedor de software, você navega por todas as fases do ciclo de vida de desenvolvimento de software durante o desenvolvimento de aplicativos. Quando a fase de desenvolvimento estiver concluída, a próxima etapa crítica envolve escrever casos de teste para verificar a lógica de negócios do aplicativo.
Independentemente de seu aplicativo ser desenvolvido usando Java vanilla ou Spring Boot, você normalmente escreverá testes JUnit para garantir que a lógica de negócios funcione corretamente e atenda aos requisitos especificados. Esses testes são essenciais para validar a funcionalidade do aplicativo e manter altos padrões de qualidade.
Para testar a funcionalidade completa do seu aplicativo, você também é obrigado a realizar testes de integração. O teste de integração é particularmente importante, pois se concentra nas interações entre diferentes módulos do software. Ele detecta problemas de interface e valida as relações funcionais e o fluxo de dados entre as unidades combinadas, garantindo que elas funcionem juntas perfeitamente. Isso é crucial para sistemas com vários componentes interconectados, pois melhora a confiabilidade e a estabilidade gerais da aplicação, garantindo que todas as peças se integrem corretamente e funcionem como um todo unificado.
Agora, suponhamos que seu aplicativo Java use MongoDB como banco de dados. Ao escrever seus testes JUnits, o que você prefere?
- Testando seus serviços com bancos de dados in-memory que também podem ser utilizados por outro módulo do aplicativo?
- Testando o aplicativo com o banco de dados criado localmente apenas para a lógica de negócios a ser testada?
Como parte da otimização do banco de dados, você escolheria a segunda opção acima.
Agora, é aqui que Testcontainers ajudaria a alcançar a segunda opção.
Vamos entender o conceito de Testcontainers na próxima seção.
Testcontainers for Java é uma biblioteca Java que suporta testes JUnit, fornecendo instâncias leves e descartáveis de bancos de dados comuns, navegadores Selenium ou qualquer outra coisa que possa ser executada em um container Docker.
Esses containers são gerenciados durante a execução do teste, garantindo um ambiente limpo e isolado para cada teste ou conjunto de testes. Ao aproveitar o Docker, o Testcontainers permite que os desenvolvedores automatizem a configuração e a desmontagem das dependências de teste, simplificando significativamente o processo de teste de integração.
Como já discutimos acima, o uso do Testcontainers oferece a vantagem de usar o banco de dados localmente, reduzindo a carga no banco de dados real usado pelos serviços.
Junto com isso, o Testcontainers também ajuda a fornecer:
- Consistência: garante que os testes sejam executados em um ambiente consistente, independentemente da infraestrutura subjacente.
- Isolamento: cada teste pode ser executado em um ambiente isolado com seu próprio conjunto de dependências.
- Ambientes atualizados: isso permite que você alterne facilmente para diferentes versões de dependências usando diferentes imagens do Docker, o que é particularmente útil para testar a compatibilidade com várias versões de um banco dedados ou serviço.
Até agora, você deve ter o conhecimento teórico sobre o que é Testcontainers e por que devemos usá-lo.
Nesta seção do artigo, discutiremos como os Testcontainers podem ser usados em Java e Spring Boot.
Isso será dividido em duas seções:
- Testcontainers com Java
- Testcontainers com Spring Boot
O conceito de Testcontainers permanece o mesmo, independentemente da estrutura em uso, mas essas duas seções ajudarão você a entender o aplicativo em ambos os cenários.
Para começar a usar o Testcontainers, você precisa carregar a dependência abaixo em seu arquivo pom.xml.
1 <dependency> 2 <groupId>org.mongodb</groupId> 3 <artifactId>mongodb-driver-sync</artifactId> 4 <version>5.1.0</version> 5 </dependency> 6 <dependency> 7 <groupId>org.junit.jupiter</groupId> 8 <artifactId>junit-jupiter-api</artifactId> 9 <version>5.10.2</version> 10 <scope>test</scope> 11 </dependency> 12 <dependency> 13 <groupId>org.junit.jupiter</groupId> 14 <artifactId>junit-jupiter-engine</artifactId> 15 <version>5.10.2</version> 16 <scope>test</scope> 17 </dependency> 18 <dependency> 19 <groupId>org.mockito</groupId> 20 <artifactId>mockito-core</artifactId> 21 <version>5.12.0</version> 22 <scope>test</scope> 23 </dependency> 24 <dependency> 25 <groupId>org.testcontainers</groupId> 26 <artifactId>testcontainers</artifactId> 27 <version>1.19.8</version> 28 <scope>test</scope> 29 </dependency> 30 <dependency> 31 <groupId>org.testcontainers</groupId> 32 <artifactId>junit-jupiter</artifactId> 33 <version>1.19.8</version> 34 <scope>test</scope> 35 </dependency> 36 <dependency> 37 <groupId>org.testcontainers</groupId> 38 <artifactId>mongodb</artifactId> 39 <version>1.19.8</version> 40 <scope>test</scope> 41 </dependency>
Os casos de teste escritos em testes JUnits são executados em três etapas com Testcontainers.
- Antes dos testes: Nesta etapa, o contêiner é criado, o banco de dados será carregado e as coleções serão criadas.
- Durante os testes: esse é o estágio em que a lógica de negócios real do aplicativo é testada.
- Após os testes: Nesta fase, os contêineres criados na primeira etapa são destruídos e os recursos são liberados.
Neste exemplo de código, o caso de teste JUnits é escrito para testar uma consulta simples para localizar cinco nomes de filmes que tenham classificações IMDb maiores que sete.
Os testes JUnits são escritos na classeTestcontainerClass.java e a descrição abaixo irá ajudá-lo a entender o código:
Se você observar o código, ele está dividido em quatro partes:
- Criando contêiner: isso puxará o mongo:7.0.0 imagem docker do Docker Hub e crie o container. A anotação @Container iniciará e interromperá o container quando os testes terminarem.
1 @Container 2 private static final MongoDBContainer mongoDBContainer = new MongoDBContainer("mongo:7.0.0");
- Configuração: O container é iniciado e os documentos de exemplo são inseridos dentro
testCollection
.
1 @BeforeAll 2 public void setup() { 3 String uri = mongoDBContainer.getConnectionString(); 4 mongoClient = MongoClients.create(uri); 5 database = mongoClient.getDatabase("testdb"); 6 collection = database.getCollection("testCollection"); 7 8 // Insert sample data 9 Document doc1 = new Document("title", "Inception").append("imdb", new Document("rating", 8.8)); 10 Document doc2 = new Document("title", "The Room").append("imdb", new Document("rating", 3.7)); 11 Document doc3 = new Document("title", "The Dark Knight").append("imdb", new Document("rating", 9.0)); 12 13 collection.insertMany(List.of(doc1, doc2, doc3)); 14 }
- Testes reais: O caso de teste real para a lógica de negócios é implementado.
1 @Test 2 void testMoviesWithHighRating() { 3 List<Document> resultDocuments = new ArrayList<>(); 4 try (MongoCursor<Document> cursor = collection.find(Filters.gt("imdb.rating", 7)) 5 .projection(new Document("title", 1).append("_id", 0)) 6 .limit(5) 7 .iterator()) { 8 while (cursor.hasNext()) { 9 Document doc = cursor.next(); 10 System.out.println(doc.toJson()); 11 resultDocuments.add(doc); 12 } 13 } 14 15 assertEquals(2, resultDocuments.size()); 16 for (Document doc : resultDocuments) { 17 assertTrue(doc.containsKey("title")); 18 assertFalse(doc.containsKey("_id")); 19 } 20 }
No exemplo de código acima, quando os dados de exemplo forem carregados, o resultDocuments conterá uma lista vazia.
Em seguida, na seção try-catch do código, o cursor itera pelos resultados, adicionando cada documento à lista resultDocuments depois de imprimi-lo no formato JSON no console. Após a iteração, o teste afirma que exatamente dois documentos foram recuperados verificando o tamanho da listaresultDocuments.
Além disso, para cada documento na lista, o teste afirma que o campo title está presente e o campo _id está ausente. Essas afirmações garantem que a query se comporte conforme o esperado, recuperando o número correto de documentos com os campos apropriados.
Muitas vezes é confuso que os testes sejam criados no banco de dados de produção. Mas a vantagem de usar Testcontainers é que, quando você executa a classe de teste, a imagem do Docker é carregada, o documento de amostra é criado e os casos de teste são executados neste container. Após a conclusão dos testes, o container e a conexão são fechados.
Agora, vamos tentar entender como essa lógica pode ser implementada no framework Spring Boot.
Para escrever testes JUnits em um aplicativo Spring Boot, utilizaremos um repositório antigo do GitHub que explica agregações avançadas usando o Spring Boot.
Neste repositório, você verá diferentes APIs REST criadas que funcionam na collection sample_supplies.sales .
Neste exemplo, vamos utilizá-lo apenas para escrever casos de teste para a API findAll().
Os testes JUnits são escritos de forma semelhante em MongoDBSalesRepositoryTest.java.
- Configurar o contêiner:
1 @Container 2 public static MongoDBContainer mongoDBContainer = new MongoDBContainer(DockerImageName.parse("mongo:7.0.0"));
- Insira dados no contêiner:
1 @DynamicPropertySource 2 static void setProperties(DynamicPropertyRegistry registry) { 3 registry.add("spring.data.mongodb.uri", mongoDBContainer::getReplicaSetUrl); 4 registry.add("spring.data.mongodb.database", () -> "testdb"); 5 } 6 7 @BeforeAll 8 public static void setUpAll() { 9 String mongoUri = mongoDBContainer.getConnectionString(); 10 mongoClient = MongoClients.create(mongoUri); 11 } 12 13 @BeforeEach 14 public void setUp() { 15 salesRepository.save(createMockSales()); 16 } 17 18 private Sales createMockSales() { 19 Item item1 = new Item("Item1", Arrays.asList("Tag1", "Tag2"), new BigDecimal("19.99"), 2); 20 Item item2 = new Item("Item2", Arrays.asList("Tag3", "Tag4"), new BigDecimal("29.99"), 1); 21 List<Item> items = Arrays.asList(item1, item2); 22 23 Customer customer = new Customer("Male", 30, "customer@example.com", 5); 24 25 return new Sales(new ObjectId(), new Date(), items, "Store1", customer, true, "Online"); 26 }
- Testes reais:
1 @Test 2 public void testFindAll() { 3 List<Sales> salesList = salesRepository.findAll(); 4 assertThat(salesList).isNotEmpty(); 5 assertThat(salesList.size()).isEqualTo(1); 6 Sales sales = salesList.get(0); 7 assertThat(sales.getStoreLocation()).isEqualTo("Store1"); 8 assertThat(sales.getCustomer().getEmail()).isEqualTo("customer@example.com"); 9 assertThat(sales.getItems()).hasSize(2); 10 }
O métodotestFindAll valida a funçãofindAll do SalesRepository. Ele verifica se pelo menos um registro de vendas é retornado se houver exatamente um registro e se seus atributos correspondem aos valores esperados, como localização da loja, e-mail do cliente e o número de itens comprados. Este teste garante a correção da função de recuperação do repositório.
Ao longo deste tutorial, demonstramos como configurar e usar Testcontainers para testes de integração com o MongoDB em um aplicativo Java básico e um aplicativo Spring Boot. Começamos adicionando as dependências necessárias ao nosso projeto, configurando o container do MongoDB e escrevendo casos de teste para validar nossa lógica de negócios. Seguindo essas etapas, é possível obter uma configuração de teste mais robusta e de fácil manutenção.
Se você leu este artigo e deseja aprender mais sobre esse conteúdo, recomendamos que visite o Centro do Desenvolvedor do MongoDB. Você também pode visitar os fóruns de nossa comunidade para discussões e documentação significativas para aprender mais.
Principais comentários nos fóruns
Ainda não há comentários sobre este artigo.