英文:
Intermittently getting ParameterResolutionException for JUnit5 parameterized tests running in parallel
问题 {#heading}
我正在编写参数化测试,其中测试类和方法并行运行。以下是配置。
junit.jupiter.execution.parallel.enabled = true
junit.jupiter.execution.parallel.mode.default = concurrent
junit.jupiter.execution.parallel.mode.classes.default = concurrent
junit.jupiter.execution.parallel.config.dynamic.factor = 2
测试代码段如下 -
import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource;
@ExtendWith(SimpleCallbackListener.class) public class HelloWorldTest { private static final Map<String, List<TestData>> testDataMap;
static { testDataMap = TestsConfigManager.readTestConfig("test-data"); }
private static List<Object[]> data() { final List<Object[]> testDataList = prepareTestData(testDataMap); return testDataList; }
@ParameterizedTest(name = "{index}: {0}") @MethodSource("data") @Tag("regression") public void testMethodName( final String expected, final String actual ) { // test code goes here }
}
SimpleCallbackListener
如下 -
public class SimpleCallbackListener implements BeforeAllCallback, ExtensionContext.Store.CloseableResource { /** * 防止同一例程中的多个线程的门卫 */ private static final Lock LOCK = new ReentrantLock(); private static boolean started = false; private Instant startTime;
@Override public void beforeAll(final ExtensionContext context) { LOCK.lock(); try { if (!started) { startTime = Instant.now(); requireApplicationInitializer(); // 下面一行在根测试上下文关闭时注册回调钩子 context.getRoot().getStore(GLOBAL).put(this.getClass().getName(), this); started = true; } } finally { LOCK.unlock(); } }
}
问题 {#heading-1}
此设置导致以下间歇性异常。
org.junit.jupiter.api.extension.ParameterResolutionException: No ParameterResolver registered for parameter ... in method ...
at java.base@17.0.7/java.util.concurrent.RecursiveAction.exec(RecursiveAction.java:194)
at java.base@17.0.7/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:373)
at java.base@17.0.7/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1182)
at java.base@17.0.7/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1655)
at java.base@17.0.7/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1622)
at java.base@17.0.7/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:165)
查看了以下建议的答案 -
- https://stackoverflow.com/questions/65934665/why-do-i-have-parameterresolutionexception-when-i-dont-even-use-parametrized-tehttps://stackoverflow.com/questions/65934665/why-do-i-have-parameterresolutionexception-when-i-dont-even-use-parametrized-te
- https://stackoverflow.com/questions/51867650/junit-5-no-parameterresolver-registered-for-parameter
英文:
I am writing parameterized tests, where test classes and methods are running in parallel. Below is the configuration.
junit.jupiter.execution.parallel.enabled = true
junit.jupiter.execution.parallel.mode.default = concurrent
junit.jupiter.execution.parallel.mode.classes.default = concurrent
junit.jupiter.execution.parallel.config.dynamic.factor = 2
The tests snippet is below -
import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource;
@ExtendWith(SimpleCallbackListener.class) public class HelloWorldTest { private static final Map&lt;String, List&lt;TestData&gt;&gt; testDataMap;
static { testDataMap = TestsConfigManager.readTestConfig(&amp;quot;test-data&amp;quot;); }
private static List&amp;lt;Object[]&amp;gt; data() { final List&amp;lt;Object[]&amp;gt; testDataList = prepareTestData(testDataMap); return testDataList; }
@ParameterizedTest(name = &amp;quot;{index}: {0}&amp;quot;) @MethodSource(&amp;quot;data&amp;quot;) @Tag(&amp;quot;regression&amp;quot;) public void testMethodName( final String expected, final String actual ) { // test code goes here }
}
SimpleCallbackListener
is as below -
public class SimpleCallbackListener implements BeforeAllCallback, ExtensionContext.Store.CloseableResource { /** * gatekeeper to prevent multiple Threads within the same routine */ private static final Lock LOCK = new ReentrantLock(); private static boolean started = false; private Instant startTime;
@Override public void beforeAll(final ExtensionContext context) { LOCK.lock(); try { if (!started) { startTime = Instant.now(); requireApplicationInitializer(); // The following line registers a callback hook when the root test context is shut down context.getRoot().getStore(GLOBAL).put(this.getClass().getName(), this); started = true; } } finally { LOCK.unlock(); } }
}
Question {#question}
This setup is causing below intermittent exception.
org.junit.jupiter.api.extension.ParameterResolutionException: No ParameterResolver registered for parameter ... in method ...
at java.base@17.0.7/java.util.concurrent.RecursiveAction.exec(RecursiveAction.java:194)
at java.base@17.0.7/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:373)
at java.base@17.0.7/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1182)
at java.base@17.0.7/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1655)
at java.base@17.0.7/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1622)
at java.base@17.0.7/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:165)
Looked at answers suggested at -
- https://stackoverflow.com/questions/65934665/why-do-i-have-parameterresolutionexception-when-i-dont-even-use-parametrized-tehttps://stackoverflow.com/questions/65934665/why-do-i-have-parameterresolutionexception-when-i-dont-even-use-parametrized-te
- https://stackoverflow.com/questions/51867650/junit-5-no-parameterresolver-registered-for-parameter
答案1 {#1}
得分: 0
我成功找出了根本原因。
问题出在以下方法上。
public static List<Object[]> prepareTestData() { final List<Object[]> testDataList = new ArrayList<>();
testDataMap .entrySet() .parallelStream() .forEach(entry -> { final String storeAlias = entry.getKey(); final List<TestData> testDataList = entry.getValue(); final List<Object[]> list = testDataList .stream() ... .map(testData -> new Object[]{someObj1, someObj2}) .toList(); testDataList.addAll(list); });
}
在这里,我试图并发地将元素添加到 ArrayList
中。
但后来我意识到 ArrayList
不是线程安全的,因此这导致了问题。
解决方案? {#heading-2}
我使用了 CopyOnWriteArrayList
。
英文:
I was able to figure out the root cause.
The issue was with the following method.
public static List<Object[]> prepareTestData() { final List<Object[]> testDataList = new ArrayList<>();
testDataMap .entrySet() .parallelStream() .forEach(entry -&amp;gt; { final String storeAlias = entry.getKey(); final List&amp;lt;TestData&amp;gt; testDataList = entry.getValue(); final List&amp;lt;Object[]&amp;gt; list = testDataList .stream() ... .map(testData -&amp;gt; new Object[]{someObj1, someObj2}) .toList(); testDataList.addAll(list); });
}
Here, I was trying to add the elements to an ArrayList
concurrently.
But, then I realized that ArrayList is not thread-safe, and hence this was causing issues.
Solution? {#solution}
I used CopyOnWriteArrayList
.