Key Takeaways
- Modern Java enterprise testing requirements have become increasingly diverse, and the underlying technologies are evolving rapidly. Developers and quality engineers should embrace data-driven testing, rather than applying traditional "rules of thumb".
- The required changes in testing practices can be demonstrated using two of the latest Java specifications currently in progress: Jakarta Data and Jakarta NoSQL.
- Developers should establish clear and comprehensive testing procedures through documentation, guiding project contributors towards a unified approach to testing.
- Developers and quality engineers should embrace advanced libraries, such as JUnit Jupiter and AssertJ. These libraries can streamline testing processes, enhance code readability, and improve overall test effectiveness.
- Engineers should use modern container-based frameworks such as Testcontainers to ensure consistent and reproducible conditions for testing Jakarta EE applications.
In the dynamic world of software development, the significance of open-source projects cannot be overstated. These collaborative endeavors drive innovation and establish benchmarks for quality and endurance in the technology landscape. Long-term projects, such as Java, which has been released as open source for over twenty years, and the Jakarta EE specifications and their implementations, exemplify the impact of open-source initiatives.
These projects offer invaluable insights and methodologies that can be readily applied to various enterprise contexts, emphasizing the importance of a robust documentation and testing culture.
This article aims to delve deeper into the testing procedures and tools employed in the latest specifications, focusing on the data-driven testing approach. By examining these methodologies, we seek to uncover the secrets behind ensuring quality, reliability, and longevity in open-source projects. We'll explore the intricacies of testing practices in the ever-evolving landscape of software development and discover how these principles can be leveraged to elevate the quality of enterprise projects.
We will exclusively focus on two of the latest specifications currently in progress: Jakarta Data and Jakarta NoSQL. These emerging specifications represent innovation within the Jakarta EE ecosystem and are poised to redefine how Java enterprise applications interact with data.
Jakarta Data, one of the latest additions to the Jakarta EE family, defines core APIs designed for developers to build Jakarta EE applications with easy access to diverse data technologies.
This specification enables developers to harness the benefits of relational and non-relational databases, cloud-based data services, and other data technologies. The goal is to foster a more agile and adaptable approach to data management within Jakarta EE applications.
The @Repository
annotation is an essential component of Jakarta Data as it enables the repository interface. This interface provides a simple connection between your domain and database, allowing you to access multiple databases in a domain-centric manner.
Figure 1: The Jakarta Data representation.
Similarly, Jakarta NoSQL represents another step forward in Jakarta EE's evolution. This specification and Jakarta Data offer developers a standardized approach to integrating Java applications with NoSQL databases.
Our examination will revolve around these two specifications and their implementations, mainly through the lens of Eclipse JNoSQL. We aim to uncover how they pave the way for a data-centric approach to testing within Jakarta EE projects. By exploring data-driven testing principles, we'll dissect the intricacies of ensuring quality, reliability, and longevity in Jakarta EE projects. By focusing on the latest advancements in Jakarta EE and their potential impact on testing methodologies, we strive to equip developers and enterprises with the knowledge and tools needed to thrive in the ever-evolving software development landscape.
Figure 2: Eclipse JNoSQL illustration that demonstrates that with these two specifications, we can switch among NoSQL databases.
Now that we've set the stage for a comprehensive exploration of Jakarta Data and Jakarta NoSQL specifications, we will delve into how these specifications can improve testing methodologies within Jakarta EE projects. We will shine the spotlight on Eclipse JNoSQL as our implementation platform. In the next section, we'll embark on a journey through the modernization of testing practices, unraveling the specific steps applied to Jakarta Data and Jakarta NoSQL projects.
Testing Methodologies
In this section, we’ll explore six essential steps to enhance testing methodologies within Jakarta EE projects to ensure robustness and reliability throughout development. These steps were designed to align Jakarta Data and Jakarta NoSQL projects with the demands of contemporary software engineering:
- Testing Guidelines: Establish clear and comprehensive testing procedures through documentation, guiding project contributors towards a unified approach to testing.
- Modern Testing Libraries: Embrace advanced libraries such as JUnit Jupiter and AssertJ to streamline testing processes, enhance code readability, and improve overall test effectiveness.
- Data-Driven Approach: Adopt a data-driven approach to testing, enabling the exploration of diverse datasets to uncover potential issues and edge cases within Jakarta EE projects.
- Extensive Assertions: Utilize assertion libraries to comprehensively validate code behavior, ensuring that Jakarta EE applications meet stringent quality standards.
- Coverage Extension: Expand test coverage using tools like PITest, JaCoCo or Cobertura. This will allow Jakarta EE project developers to assess the effectiveness of their tests and identify areas for improvement.
- Containers for Testing: Leverage containers to create and manage isolated testing environments, ensuring consistent and reproducible conditions for testing Jakarta EE applications.
By embracing these six steps, developers can elevate their testing practices with Jakarta EE projects, ensuring that their applications are robust, reliable, and resilient in the face of evolving software requirements.
Let’s delve deeper into each of these steps and uncover how they contribute to the success of Jakarta EE projects.
Figure 3: Steps to enhance testing methodologies within Jakarta EE projects.
In our first step in the test, the initial point is documentation: A Testing Guideline serves as a compass, steering all project contributors towards a shared vision of quality and reliability. It encapsulates a set of best practices and approaches that govern testing procedures to ensure consistency and effectiveness across the project.
In Jakarta JNoSQL and Jakarta Data projects, these testing guidelines shape the project’s testing culture and foster a commitment to quality. They encompass:
- Tools to Use: Defined sets of testing and related tools to streamline testing processes and ensure uniformity across contributions.
- Naming Conventions: Consistent naming conventions for test classes, methods, descriptions, and structures enhance test readability and maintainability.
- Specific Test Creation Approach: Guidelines on test creation approaches, such as soft assertions and extensive assertions, ensure validation of code behavior.
- Coverage Extension: Emphasis on extending test coverage, including techniques like mutation testing, uncovering hidden bugs and improving overall code quality.
These guidelines serve as a roadmap for testing and underscore the importance of documentation. They provide a clear starting point for project contributors, facilitating seamless integration into the testing framework. For example, documentation in Jakarta Data, as seen in the provided sample in Asciidoc format, outlines the frameworks to be utilized, naming conventions, test structure, and more, to set the standard for testing practices within the project.
To explore further, refer to the Jakarta Data project’s Testing Guideline on GitHub.
To complement the guide, we provide these tips:
- Tools for Use: The recommended tools include JUnit 5 combined with AssertJ, providing a testing framework with enhanced assertion capabilities.
- Naming Conventions: Test classes should be named after the test subject, followed by the "Test" suffix, promoting clarity and organization within the test suite.
- Test Method Descriptions: Test methods should describe the test intent using a structured approach:
- Prefix "should"
- Action name
- Optional expected result for happy paths and mandatory for negative scenarios (corner cases)
For example, this code excerpt demonstrates how to create a test using the guide:
class CalculatorTest {
@Test
@DisplayName("Should sum up correctly two numbers")
void sumUpCorrectly() {
// AAA pattern (arrange, act, assert)
}
}
Prefer Soft Assertions: Soft assertions are recommended over hard assertions. They allow multiple statements to be made within a single test method without aborting the test execution on the first failure. This promotes a more comprehensive validation of test scenarios.
assertSoftly(softly->{
softly.assertThat(pageable.size()).isEqualTo(20);
softly.assertThat(pageable.page()).isEqualTo(1L);
softly.assertThat(pageable.mode()).isEqualTo(Pageable.Mode.CURSOR_NEXT);
softly.assertThat(pageable.cursor().size()).isEqualTo(3);
softly.assertThat(pageable.cursor().getKeysetElement(0)).isEqualTo("First");
softly.assertThat(pageable.cursor().getKeysetElement(1)).isEqualTo(2L);
softly.assertThat(pageable.cursor().getKeysetElement(2)).isEqualTo(3);
});
The second step in modernizing testing methodologies within Jakarta EE projects involves the careful selection and adoption of modern testing tools and libraries. While Jakarta EE traces its roots back to the Java EE platform, whichoriginated in 1999, contemporary tools are essential for staying agile and competitive in today's software landscape.
One of the youngest specifications within Jakarta EE, Jakarta NoSQL and Jakarta Data, seamlessly integrate modern testing libraries to enhance code maintainability, readability, and test coverage. Among these libraries, the adoption of JUnit 5 stands out, offering advanced features that significantly improve testing practices.
Migrating to JUnit 5, specifically JUnit Jupiter, has proven to be worth every effort, bringing a host of valuable features to Jakarta EE projects. These features include Test Description for clearer test intent, Environment Conditiona for handling environment-specific testing scenarios, and various sources for Data-Driven Testing, such as Value Source, Method Source, and Argument Source.
By leveraging the above capabilities of JUnit 5, developers can improve their testing practices with Jakarta EE projects to ensure robustness, reliability, and adaptability in the face of evolving software requirements. Let's delve deeper into how these features are utilized to enhance testing within Jakarta EE projects.
The third step in modernizing testing methodologies within Jakarta EE projects revolves around embracing Data-Driven Testing. In essence, this approach involves iterating the same test code with different data sets, providing a mechanism for rapidly enhancing code coverage and maintainability.
JUnit Jupiter is a powerful ally within Jakarta EE projects, mainly at Jakarta Data and Jakarta NoSQL, offering a @ParameterizedTest
annotation to streamline data-driven testing. This annotation and various source options simplify injecting dynamic data into test cases.
In the first sample code snippet from the ValueWriterDecoratorTest
, the @ValueSource
annotation is utilized to inject values from parameters into the test method. It allows for effortlessly testing compatibility across different classes.
@ParameterizedTest(name = "must be compatible to {0}")
@DisplayName("Should be able to verify the test compatibility")
@ValueSource(classes = {Enum.class, Optional.class, Temporal.class})
@SuppressWarnings("unchecked")
void shouldVerifyCompatibility(Class<?> supportedClass) {
assertThat(valueWriter.test(supportedClass)).isTrue();
}
As Figure 4 shows, we can see a single test being executed multiple times based at @ValueSource
and different values.
Figure 4: Test application executing the shouldVerifyCompatibility()
method defined in the ValueWriterDecoratorTest
class.
Additionally, JUnit Jupiter provides more advanced options for dynamically injecting parameters, as demonstrated in the TemporalWriterTest
. Here, the @MethodSource
annotation requests values from a method, enabling the testing of temporal conversions with various inputs.
@ParameterizedTest(name = "must convert {0}")
@DisplayName("Should be able to convert temporal")
@MethodSource("temporalDataForConversion")
void shouldConvert(Temporal temporal) {
String result = valueWriter.write(temporal);
assertThat(result).isEqualTo(temporal.toString());
}
static Stream<Arguments> temporalDataForConversion() {
return Stream.of(
arguments(LocalDateTime.now()),
arguments(LocalDate.now()),
arguments(LocalTime.now()),
arguments(Year.now()),
arguments(YearMonth.now()),
arguments(ZonedDateTime.now())
);
}
The beauty of JUnit Jupiter lies in its flexibility, allowing developers to decouple tests from specific data values. It enables seamless integration with external data sources such as Data Faker, databases, or web services without impacting the integrity of the test suite. By leveraging the Data-Driven Testing approach offered by JUnit Jupiter, Jakarta EE projects can achieve more test coverage and maintainability while ensuring robustness and reliability in their software solutions.
In the quest to modernize testing methodologies within Jakarta EE projects, the fourth step revolves around leveraging extensive assertions offered by libraries like AssertJ. These libraries enrich the testing toolkit with many assertions and features, including the soft assertion approach to simplify and enhance the validation process.
AssertJ is known for its inclusion of a fluent API library, which expands the range of available assertions and significantly improves code readability. This enhanced readability is exemplified in tests like the KeyValueQueryParserTest
, where AssertJ's fluent API streamlines the validation process.
@Test
@DisplayName("Should execute prepared statement using 'put' setting a value")
void shouldExecutePrepareStatement2() {
// code ignored
assertSoftly(softly -> {
softly.assertThat(entity.key()).as("key is equal").isEqualTo("Diana");
softly.assertThat(entity.value()).as("entity is equal").isEqualTo("Hunt");
softly.assertThat(duration).as("duration is equal").isEqualTo(ofSeconds(10L));
});
}
In this example, the assertSoftly()
method from AssertJ’s soft assertion approach allows multiple assertions to be made within a single test method without aborting test execution upon encountering the first failure. It promotes a more comprehensive validation of test scenarios while maintaining readability and clarity in the test code.
By embracing extensive assertions provided by libraries like AssertJ, Jakarta EE projects can enhance their test suites’ robustness, reliability, and maintainability to ultimately ensure the quality and integrity of their software solutions.
As the fifth step in modernizing testing methodologies within Jakarta EE projects, we turn to mutation testing. This advanced technique allows us to uncover hidden bugs and assess the strength of our tests.
PITest, a state-of-the-art mutation testing system designed for Java and the JVM, plays a pivotal role in this process. PiTest enables us to evaluate how well our tests respond to these changes by automatically introducing faults or mutations into our codebase.
Here’s how mutation testing works: faults are injected into the code, and then our tests are executed. If a test fails, indicating that it detected the mutation, the mutation is considered killed. Conversely, if the test passes, meaning that it failed to detect the mutation, the mutation survives.
At the Eclipse JNoSQL project, we can seamlessly integrate PiTest into our testing workflow with a simple command:
mvn clean test-compile -P pitest
This command triggers the analysis process, generating comprehensive reports that provide insights into test coverage, mutation scores, and test strength. These reports can be accessed via the index.html
file located at target/pit-reports
.
Figure 5: The PITest execution against the Eclipse JNoSQL communication core project.
Figure 5 above shows PiTest in action, showcasing the coverage, mutation, and test strength metrics. This visual representation allows us to assess our tests’ effectiveness and identify improvement areas.
By leveraging PITest and mutation testing, Jakarta EE projects can extend their test coverage, detect latent bugs, and enhance their software solutions’ overall quality and reliability. With PITest, we can ensure that our tests are robust and comprehensive, providing confidence in the stability of our applications.
For Jakarta Data and Jakarta NoSQL projects, the final step in elevating testing methodologies revolves around leveraging containers for testing. This step is crucial given the core database integration, where ensuring seamless interaction and compatibility is paramount.
In the sixth and final step of modernizing testing methodologies within Jakarta EE projects, Testcontainers emerges as a powerful testing library. Offering easy and lightweight APIs, Testcontainers simplifies the bootstrapping of integration tests with precise services encapsulated within Docker containers. Its functionality manages the lifecycle of databases during testing to guarantee initialization at the start of tests, persistence during execution, and proper teardown afterwards.
Within the JNoSQL-Database project, several integrations necessitate direct integration with databases, making tests crucial for confirming this contract. Here, we witness Testcontainers in action, seamlessly orchestrating the setup and teardown of database containers to facilitate comprehensive testing.
To optimize resource usage, the project employs a Singleton pattern instance whenever possible, minimizing the overhead of initializing database instances. Two primary styles are utilized: a Singleton pattern that manages the container and the Base Test pattern. The project is transitioning towards a standardized structure derived from Testcontainers documentation, ensuring consistency and clarity in test management.
abstract class AbstractContainerBaseTest {
static final MySQLContainer MY_SQL_CONTAINER;
static {
MY_SQL_CONTAINER = new MySQLContainer();
MY_SQL_CONTAINER.start();
}
}
class FirstTest extends AbstractContainerBaseTest {
@Test
void someTestMethod() {
String url = MY_SQL_CONTAINER.getJdbcUrl();
// Create a connection and run test as normal
}
}
To enable flexibility in test execution, developers utilize properties that allow them to selectively enable or disable integration tests based on system properties. Heavy integration tests can be toggled as needed.
@EnabledIfSystemProperty(named = "jnosql.test.integration", matches = "true")
class MongoDBDocumentManagerTest {
// Executes test with a container
}
This approach allows developers to efficiently manage test execution, ensuring that resource-intensive tests are run selectively, optimizing testing processes within Jakarta EE projects. By harnessing containers for testing, the Jakarta Data and Jakarta NoSQL projects can validate database integrations, enhancing the reliability and robustness of their software solutions.
Leveraging containers for testing within Jakarta EE projects, particularly in Jakarta Data and Jakarta NoSQL endeavors, is instrumental in ensuring the robustness and reliability of software solutions. Testcontainers, with its lightweight APIs and seamless Docker container integration, facilitates comprehensive testing of database interactions. By employing Testcontainers alongside established testing patterns and practices, such as Singleton instances and flexible test execution toggles, Jakarta EE developers can streamline testing processes and validate database integrations. This meticulous approach not only enhances the quality and reliability of software solutions but also allows developers to confidently navigate the complexities of database integration within Jakarta EE ecosystems.
Conclusion
As we conclude our exploration into modernizing testing methodologies within Jakarta EE projects, it becomes evident that enhancing code quality and reliability is an ongoing endeavor. Adopting innovative tools and practices, such as data-driven testing, extensive assertions, and container-based testing, marks significant strides toward ensuring the robustness of software solutions.
It's important to recognize that the Jakarta Data and Jakarta NoSQL projects, like any open-source initiative, remain dynamic and continuously seek improvements. The active engagement of contributors, testers, and users is vital in driving these projects forward, refining testing methodologies, and improving the reliability of Jakarta EE applications.
For those seeking further insights and guidance in software testing, there are plenty of references and resources. One notable recommendation is Effective Software Testing: A Developer's Guide by Maurizio Aniche, which is a comprehensive book that has transformed testing practices for many.
Additionally, the guidance and leadership of individuals like Elias Nogueira, a renowned Quality Engineer, and Java Champion, play a pivotal role in promoting good testing practices within Jakarta EE projects. Nogueira’s blog site and active presence on social media platforms, such as Twitter, serve as valuable resources for developers seeking to elevate their understanding of testing and contribute to advancing Jakarta EE projects.
As we navigate the ever-evolving software development landscape, let us remain committed to fostering a culture of quality, collaboration, and continuous improvement within Jakarta EE projects. Together, we can empower developers, strengthen testing practices, and deliver software solutions of unparalleled reliability and excellence.