Skip to content

Commit 5148aa3

Browse files
committed
Add test classloading blog
1 parent f204c13 commit 5148aa3

File tree

1 file changed

+60
-0
lines changed

1 file changed

+60
-0
lines changed
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
---
2+
layout: post
3+
title: 'The internals (and a few externals) of Quarkus test classloading have changed'
4+
tags: announcement testing
5+
synopsis: 'The way that Quarkus loads test classes has been updated. Most tests will not need to change, but here are some things to watch out for.'
6+
author: hcummins
7+
---
8+
9+
== What's changing?
10+
11+
The internals of Quarkus test classloading have been rewritten in 3.22.
12+
In particular, how `@QuarkusTest` test classes are loaded is changing.
13+
14+
== Why?
15+
16+
In previous versions, Quarkus tests were invoked using the default JUnit classloader, and then executed in a different, Quarkus-aware, classloader.
17+
18+
This mostly worked very well, and meant that `QuarkusTest` tests mostly behaved as if they were part of the same application as the code under test.
19+
The Quarkus test framework could start and stop Quarkus instances at the right point in the test lifecycle, inject CDI dependencies, and do other useful Quarkus bytecode manipulation.
20+
However, some use cases didn't work. Tests using advanced JUnit features like `@TestTemplate` and `@ParameterizedTest` sometimes found that the same test code might appear to run in several classloaders in a single test, or that injected dependencies weren't always available.
21+
22+
While Quarkus extensions can do all sorts of marvellous bytecode manipulation to improve the developer experience, they cannot manipulate test classes with the same freedom that they do normal application classes.
23+
24+
Over time, test-related defects were building up that couldn't be changed without a fundamental rewrite of test classloading. The Quarkus test code itself was also growing ever-more complex as it tried to work around various JUnit edge cases. Moving test instances from one classloader to another involved serializing and deserialization, which is harder to implement on newer JVM versions with tighter class security. For example, Quarkus used to use XStream as the serialization provider, but XStream stopped working in Java 17.
25+
26+
What if, instead, tests were simply run in the same classloader used to to load them?
27+
28+
== What you need to do
29+
30+
From Quarkus 3.22 onwards, this is exactly how `@QuarkusTest` classloading works.
31+
What do your tests need to change in order to work with the new architecture?
32+
Hopefully, nothing.
33+
One of the goals of this change was that the rewrite didn't touch any tests in our test suite, to make sure they'd all continue working without updates.
34+
In practice, there have been a few hiccups and we've discovered some edge cases in the broader ecosystem.
35+
36+
=== Dropped support
37+
38+
- *`@TestProfile` on `@Nested` tests.* Mixing different test profiles and test resources on `@Nested` tests is no longer supported. By definition, every `@TestProfile` must get its own Quarkus application and classloader. Having multiple classloaders execute one test isn't compatible with loading the test with the classloader used to run it.
39+
- *Version 2.x of the Maven Surefire plugin*. Versions below 3.x of the Maven Surefire plugin will no longer work with `@QuarkusTest`. Version 3 of the Surefire plugin was released in 2023, so version 2 is now rather old.
40+
41+
=== Known regressions
42+
43+
- *Dev services start in the discovery phase*. Quarkus Dev Services are currently started during the augmentation phase. With the new design, all augmentation happens at the beginning of the test run. If several test classes with different Dev Service configuration are augmented before any tests are run, multiple differently-configured Dev Services may be running at the same time. This can cause port conflicts and cross-talk on configuration values. We're hoping to have a https://github.com/quarkusio/quarkus/issues/45785[fix] for this in the next release. As a workaround, splitting conflicting tests into separate projects should fix symptoms.
44+
- *Config access from JUnit conditions*. Using a `ConfigProvider` from a custom JUnit condition will https://github.com/quarkusio/quarkus/issues/47081[trigger a `ServiceConfigurationError`]. The workaround is to set the thread context classloader to `this.getClass().getClassLoader()` before reading config, and then set it back afterwards.
45+
- Increased memory footprint running tests. For suites using multiple profiles and resources, more heap or metaspace may be needed.
46+
47+
=== Things to watch out for
48+
49+
- *Test order change*. As part of the rewrite, the execution order of some tests has swapped around. Of course, we all know tests should not depend on execution order if they don't set an order explicitly. However, it's easy to not notice that a test requires a certain order... until the order changes. We discovered some tests in our own suite that were sensitive to the execution order, and other people may make similar discoveries.
50+
- *Test timing change*. We also discovered that the rewrite exposed some timing issues in tests. Because classloading is frontloaded at the beginning of the test run, rather than between test executions, there's less time for asynchronous operations to finish between tests. For example, there may no longer be time for external state to 'reset' before the next test starts. This might expose some heisenbugs in test suites.
51+
52+
53+
== Next steps
54+
55+
The main work of the test classloading rewrite has been delivered in 3.22, and has unlocked a bunch of possible improvements.
56+
Some test defects weren't directly fixed by the main change, but the architecture is now in place to enable a fix.
57+
More excitingly, test-related extensions, like the Pact extensions, can now add new features to reduce test boilerplate.
58+
59+
As always, if you spot issues or oddities, please let us know on https://quarkusio.zulipchat.com/[zulip] or https://github.com/quarkusio/quarkus/issues[raise an issue].
60+

0 commit comments

Comments
 (0)