Test automation has evolved significantly over the years, moving from simple record-and-playback approaches to sophisticated frameworks that leverage advanced programming concepts. Java, being one of the most widely used languages for test automation, offers a rich set of features that can dramatically improve the quality, maintainability, and efficiency of your test code.
In this module, we'll explore how advanced Java features can transform your test automation approach, making your tests more robust, readable, and maintainable.
Traditional test automation often suffers from several common problems:
Consider a typical test scenario where we need to verify that a list of users contains a specific user with certain properties:
Traditional Approach (Java 7 and earlier):
@Test
public void testUserListContainsAdmin() {
List<User> users = userService.getAllUsers();
boolean foundAdmin = false;
for (User user : users) {
if ("admin".equals(user.getRole()) && user.isActive()) {
foundAdmin = true;
break;
}
}
assertTrue("No active admin user found", foundAdmin);
}
Modern Approach (Java 8+):
@Test
public void testUserListContainsAdmin() {
List<User> users = userService.getAllUsers();
boolean foundAdmin = users.stream()
.anyMatch(user -> "admin".equals(user.getRole()) && user.isActive());
assertTrue("No active admin user found", foundAdmin);
}
The modern approach is not only more concise but also more expressive, making the intent of the test clearer.
Java 8 introduced several features that revolutionized Java programming, particularly for test automation:
Java 11 built upon Java 8's foundation with several improvements:
Java 17 (LTS) introduced several features that further enhance test automation:
Java 7:
@Test
public void testUserRegistration() {
User user = new User();
user.setUsername("testuser");
user.setEmail("test@example.com");
user.setRole("customer");
user.setActive(true);
userService.register(user);
User savedUser = userService.findByUsername("testuser");
assertNotNull(savedUser);
assertEquals("test@example.com", savedUser.getEmail());
}
Java 11 (with builder pattern):
@Test
public void testUserRegistration() {
var user = User.builder()
.username("testuser")
.email("test@example.com")
.role("customer")
.active(true)
.build();
userService.register(user);
var savedUser = userService.findByUsername("testuser");
assertNotNull(savedUser);
assertEquals("test@example.com", savedUser.getEmail());
}
Java 17 (with records):
record UserData(String username, String email, String role, boolean active) {}
@Test
public void testUserRegistration() {
var userData = new UserData("testuser", "test@example.com", "customer", true);
var user = User.fromUserData(userData);
userService.register(user);
var savedUser = userService.findByUsername("testuser");
assertNotNull(savedUser);
assertEquals("test@example.com", savedUser.getEmail());
}
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>modern-java-testing</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<junit.version>5.8.2</junit.version>
<assertj.version>3.22.0</assertj.version>
<webdriverio.version>7.16.13</webdriverio.version>
</properties>
<dependencies>
<!-- JUnit 5 -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<!-- AssertJ for fluent assertions -->
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>${assertj.version}</version>
<scope>test</scope>
</dependency>
<!-- WebDriverIO Java bindings -->
<dependency>
<groupId>com.webdriverio</groupId>
<artifactId>webdriverio-java</artifactId>
<version>${webdriverio.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M5</version>
</plugin>
</plugins>
</build>
</project>
plugins {
id 'java'
}
group = 'com.example'
version = '1.0-SNAPSHOT'
sourceCompatibility = '11'
repositories {
mavenCentral()
}
dependencies {
// JUnit 5
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.2'
// AssertJ for fluent assertions
testImplementation 'org.assertj:assertj-core:3.22.0'
// WebDriverIO Java bindings
implementation 'com.webdriverio:webdriverio-java:7.16.13'
}
test {
useJUnitPlatform()
}
In the next module, we'll dive deeper into lambda expressions and functional interfaces, exploring how they can make your test code more concise and expressive.
Continue to Module 2: Lambda Expressions and Functional Interfaces