解決 JUnit 5 中的 ParameterResolutionException
1. 概述
JUnit 5 引入了一些強大的功能,包括對參數化測試的支援。編寫參數化測試可以節省大量時間,並且在許多情況下,可以透過簡單的註釋組合來啟用它們。
然而,不正確的配置可能會導致難以調試的異常,因為 JUnit 在幕後管理測試執行的許多方面。
ParameterResolutionException
就是這樣的異常之一:
org.junit.jupiter.api.extension.ParameterResolutionException: No ParameterResolver registered for parameter ...
在本教程中,我們將探討此異常的原因以及解決方法。
2. JUnit 5的ParameterResolver
要了解此異常的原因,我們首先需要了解訊息告訴我們缺少什麼: ParameterResolver
。
在 JUnit 5 中,引入了ParameterResolver
接口,以允許開發人員擴展 JUnit 的基本功能並編寫採用任何類型參數的測試。讓我們來看一個簡單的ParameterResolver
實作:
public class FooParameterResolver implements ParameterResolver {
@Override
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
throws ParameterResolutionException {
// Parameter support logic
}
@Override
public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
// Parameter resolution logic
}
}
我們可以看到該類別有兩個主要方法:
-
supportsParameter():
決定是否支援參數類型 -
resolveParameter()
:傳回測試執行的參數
因為ParameterResolutionException
是在沒有ParameterResolver
實作的情況下拋出的,所以我們暫時還不會太在意實作細節。讓我們先討論一下異常的一些潛在原因。
3. ParameterResolutionException
ParameterResolutionException
可能很難調試,尤其是對於那些不太熟悉參數化測試的人來說。
首先,我們定義一個簡單的Book
類,我們將為其編寫單元測試:
public class Book {
private String title;
private String author;
// Standard getters and setters
}
對於我們的範例,我們將為Book
編寫一些單元測試來驗證不同的標題值。讓我們從兩個非常簡單的測試開始:
@Test
void givenWutheringHeights_whenCheckingTitleLength_thenTitleIsPopulated() {
Book wuthering = new Book("Wuthering Heights", "Charlotte Bronte");
assertThat(wuthering.getTitle().length()).isGreaterThan(0);
}
@Test
void givenJaneEyre_whenCheckingTitleLength_thenTitleIsPopulated() {
Book jane = new Book("Jane Eyre", "Charlotte Bronte");
assertThat(wuthering.getTitle().length()).isGreaterThan(0);
}
很容易看出這兩個測試基本上在做相同的事情:設定Book
並檢查長度。我們可以透過將它們組合成單一參數化測試來簡化測試。讓我們討論一下這種重構可能出錯的一些方式。
3.1.將參數傳遞給@Test
方法
採用非常快速的方法,我們可能認為將參數傳遞給@Test
註解的方法就足夠了:
@Test
void givenTitleAndAuthor_whenCreatingBook_thenFieldsArePopulated(String title, String author) {
Book book = new Book(title, author);
assertThat(book.getTitle().length()).isGreaterThan(0);
assertThat(book.getAuthor().length()).isGreaterThan(0);
}
程式碼編譯並運行,但進一步思考一下,我們應該質疑這些參數來自哪裡。運行這個例子,我們看到一個異常:
org.junit.jupiter.api.extension.ParameterResolutionException: No ParameterResolver registered for parameter [java.lang.String arg0] in method ...
JUnit 無法知道要將哪些參數傳遞給測試方法。
讓我們繼續重構我們的單元測試並研究ParameterResolutionException.
3.2.競爭註釋
正如我們之前提到的,我們可以使用ParameterResolver
提供缺少的參數,但讓我們從值來源開始更簡單。由於有兩個值( title
和author
),我們可以使用CsvSource
為我們的測試提供這些值。
此外,我們缺少一個關鍵註解: @ParameterizedTest
。此註釋通知 JUnit 我們的測試已參數化並且已將測試值注入其中。
讓我們快速嘗試重構:
@ParameterizedTest
@CsvSource({"Wuthering Heights, Charlotte Bronte", "Jane Eyre, Charlotte Bronte"})
@Test
void givenTitleAndAuthor_whenCreatingBook_thenFieldsArePopulated(String title, String author) {
Book book = new Book(title, author);
assertThat(book.getTitle().length()).isGreaterThan(0);
assertThat(book.getAuthor().length()).isGreaterThan(0);
}
這似乎很合理。然而,當我們執行單元測試時,我們看到一些有趣的事情:兩次測試運行通過,第三次測試運行失敗。仔細觀察,我們也看到一個警告:
WARNING: Possible configuration error: method [...] resulted in multiple TestDescriptors [org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor, org.junit.jupiter.engine.descriptor.TestTemplateTestDescriptor].
This is typically the result of annotating a method with multiple competing annotations such as @Test, @RepeatedTest, @ParameterizedTest, @TestFactory, etc.
透過新增競爭測試註釋**,我們無意中創建了多個TestDescriptor
** 。這意味著 JUnit 仍在運行我們測試的原始@Test
版本以及新的參數化測試。
只需刪除@Test
註釋即可解決此問題。
3.3.使用ParameterResolver
之前,我們討論了ParameterResolver
實作的一個簡單範例。現在我們已經有了一個有效的測試,讓我們引入一個BookParameterResolver
:
public class BookParameterResolver implements ParameterResolver {
@Override
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
throws ParameterResolutionException {
return parameterContext.getParameter().getType() == Book.class;
}
@Override
public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
return parameterContext.getParameter().getType() == Book.class
? new Book("Wuthering Heights", "Charlotte Bronte")
: null;
}
}
這是一個簡單的範例,僅傳回一個Book
實例進行測試。現在我們有一個ParameterResolver
來為我們提供測試值,我們應該能夠從第一個範例返回測試。同樣,我們可以嘗試:
@Test
void givenTitleAndAuthor_whenCreatingBook_thenFieldsArePopulated(String title, String author) {
Book book = new Book(title, author);
assertThat(book.getTitle().length()).isGreaterThan(0);
assertThat(book.getAuthor().length()).isGreaterThan(0);
}
但正如我們在運行此測試時看到的那樣,同樣的異常仍然存在。但原因略有不同——現在我們有了ParameterResolver
,我們仍然需要告訴 JUnit 如何使用它。
幸運的是,這就像將@ExtendWith
註解添加到包含我們的測試方法的外部類別一樣簡單:
@ExtendWith(BookParameterResolver.class)
public class BookUnitTest {
@Test
void givenTitleAndAuthor_whenCreatingBook_thenFieldsArePopulated(String title, String author) {
// Test contents...
}
// Other unit tests
}
再次運行它,我們看到測試執行成功。
4。結論
在本文中,我們討論了 JUnit 5 的ParameterResolutionException
以及缺失或競爭配置如何導致此異常。與往常一樣,本文的所有程式碼都可以在 GitHub 上找到。