Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions PRODUCT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Ideas

## Structure

Content

Health Check

Optimization Potential

Target Run Time

## Visualize

Display the top three slowest context
Identify which context are full and which are sliced ones (feedback from Wim)
build up current test time
Better reuse stats due to context refresh issues
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
# Profile Your Tests. Speed Up Your Tests. Ship Faster 🚤
# Profile Your Tests. Speed Up Your Build. Ship Faster 🚤

<p align="center">
<img src="docs/resources/spring-test-profiler-logo-three-256x256.png" alt="Spring Test Profiler Logo" />
</p>

A Spring Test utility that provides visualization and insights for Spring Test execution, with a focus on Spring context caching statistics.
The Spring Test Profiler is a Spring Test utility that provides visualization and insights for Spring Test execution, with a focus on Spring context caching.

It helps you identify optimization opportunities in your Spring Test suite to speed up your builds and ship to production faster and with more confidence.

Find [more information](https://pragmatech.digital/products/spring-test-profiler/) about the profiler on our website.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.scheduling.annotation.EnableScheduling;

@EnableScheduling
@SpringBootApplication
public class DemoApplication implements CommandLineRunner {

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package digital.pragmatech.demo.service;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Component
public class ScheduledLogger {

private static final Logger LOG = LoggerFactory.getLogger(ScheduledLogger.class);

private final ApplicationContext applicationContext;

public ScheduledLogger(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}

// @Scheduled(fixedDelay = 100L)
public void log() {
LOG.info("Scheduled task executed within context '{}'.", applicationContext.hashCode());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package digital.pragmatech.demo.service;

import org.springframework.boot.CommandLineRunner;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.stereotype.Component;

@Component
public class SlowDown implements CommandLineRunner {

@Override
public void run(String... args) throws Exception {
try {
Thread.sleep(5000);
System.out.println("ApplicationContext initialized after delay.");
}
catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package digital.pragmatech.demo;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.properties.PropertyMapping;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.test.context.TestPropertySources;

import static org.assertj.core.api.Assertions.assertThat;

// Spring Boot < 4.0
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, properties = "management.server.port=")
public class MigrationIT {

@Autowired
private TestRestTemplate restTemplate;

@Test
void shouldReturnSuccessfulHealthCheckRestTemplate() {
var response = restTemplate
.getForEntity("/actuator/health", String.class);

assertThat(response.getStatusCode().value()).isEqualTo(200);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package digital.pragmatech.demo.pausing;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;

import static org.assertj.core.api.Assertions.assertThat;

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, properties = "management.server.port=")
public class BasicIT {

@Autowired
private TestRestTemplate restTemplate;

@Test
void shouldReturnSuccessfulHealthCheckRestTemplate() throws Exception {
var response = restTemplate
.getForEntity("/actuator/health", String.class);

assertThat(response.getStatusCode().value()).isEqualTo(200);

Thread.sleep(5_000); // Pausing to allow scheduled tasks to run
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package digital.pragmatech.demo.pausing;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;

import static org.assertj.core.api.Assertions.assertThat;

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, properties = "management.server.port=")
public class OtherIT {

@Autowired
private TestRestTemplate restTemplate;

@Test
void shouldReturnSuccessfulHealthCheckRestTemplate() throws Exception {
var response = restTemplate
.getForEntity("/actuator/health", String.class);

assertThat(response.getStatusCode().value()).isEqualTo(200);

Thread.sleep(5_000); // Pausing to allow scheduled tasks to run
}
}
22 changes: 20 additions & 2 deletions demo/spring-boot-4.0-maven/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>4.0.0-M2</version>
<version>4.0.0-RC2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>

Expand Down Expand Up @@ -61,13 +61,31 @@
<scope>runtime</scope>
</dependency>

<!-- Test dependencies -->
<!-- Test dependencies, now more modular in Spring Boot 4.0 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webmvc-test</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webclient-test</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-restclient-test</artifactId>
<scope>test</scope>
</dependency>

<!-- Spring Test Insight Extension -->
<dependency>
<groupId>digital.pragmatech.testing</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.scheduling.annotation.EnableScheduling;

@EnableScheduling
@SpringBootApplication
public class DemoApplication implements CommandLineRunner {

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package digital.pragmatech.demo.service;

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component
@Scope("prototype")
public class OtherService {

public String doWork() {
return "Other Service is working!";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package digital.pragmatech.demo.service;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.event.ContextPausedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Component
public class ScheduledLogger {

private static final Logger LOG = LoggerFactory.getLogger(ScheduledLogger.class);

private final ApplicationContext applicationContext;

public ScheduledLogger(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}

@Scheduled(fixedDelay = 100L)
public void log() {
LOG.info("Scheduled task executed within context '{}'.", applicationContext.hashCode());
}

// New event in Spring Framework 7.0
@EventListener(ContextPausedEvent.class)
public void contextPaused(ContextPausedEvent event) {
LOG.info("Application context '{}' has been paused.", event.getApplicationContext().hashCode());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@
import digital.pragmatech.demo.entity.Book;
import digital.pragmatech.demo.entity.BookCategory;
import digital.pragmatech.demo.repository.BookRepository;
import digital.pragmatech.demo.service.OtherService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.bean.override.mockito.MockitoBean;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
Expand All @@ -30,6 +32,9 @@
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) // BAD: Forces context reload
class BadOneIT {

@MockitoBean
private OtherService otherService;

private final BookRepository bookRepository;

public BadOneIT(@Autowired BookRepository bookRepository) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@
import digital.pragmatech.demo.entity.BookCategory;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureWebMvc;
import org.springframework.boot.resttestclient.autoconfigure.AutoConfigureTestRestTemplate;
import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureWebMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.web.server.test.client.TestRestTemplate;
import org.springframework.boot.resttestclient.TestRestTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.ActiveProfiles;
Expand All @@ -24,8 +25,8 @@
* Also uses webEnvironment.RANDOM_PORT but different from other tests.
*/
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureWebMvc // BAD: Unnecessary annotation that changes context
@ActiveProfiles("test") // Same as first test, but other config differs
@AutoConfigureTestRestTemplate
@TestPropertySource(properties = {
"spring.datasource.url=jdbc:h2:mem:badtest3;DB_CLOSE_DELAY=-1", // Different DB name again
"spring.jpa.hibernate.ddl-auto=create-drop",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package digital.pragmatech.demo;

import org.junit.jupiter.api.Test; // Junit 6 - yay
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.resttestclient.TestRestTemplate;
import org.springframework.boot.resttestclient.autoconfigure.AutoConfigureRestTestClient;
import org.springframework.boot.resttestclient.autoconfigure.AutoConfigureTestRestTemplate;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.client.RestTestClient;

import static org.assertj.core.api.AssertionsForClassTypes.assertThat;

@AutoConfigureTestRestTemplate
@AutoConfigureRestTestClient // consider as an alternative to the TestRestTemplate
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class MigrationIT {

@Autowired
private TestRestTemplate restTemplate;

@Autowired
private RestTestClient restClient;

@Test
void shouldReturnSuccessfulHealthCheckRestTemplate() {
var response = restTemplate
.getForEntity("/actuator/health", String.class);

assertThat(response.getStatusCode().value()).isEqualTo(200);
}

@Test
void shouldReturnSuccessfulHealthCheckRestClient() {
restClient
.get()
.uri("/actuator/health")
.exchange()
.expectStatus()
.is2xxSuccessful();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package digital.pragmatech.demo.pausing;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.resttestclient.autoconfigure.AutoConfigureRestTestClient;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.client.RestTestClient;

@AutoConfigureRestTestClient
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class HealthCheckIT {

@Autowired
private RestTestClient restClient;

@Test
void shouldReturnSuccessfulHealthCheckRestClient() throws Exception {
restClient
.get()
.uri("/actuator/health")
.exchange()
.expectStatus()
.is2xxSuccessful();

Thread.sleep(5_000); // Pausing to allow scheduled tasks to run
}
}
Loading
Loading