IntelliJ Plugin の開発で JUnit 5 を使う
IntelliJ Plugin の開発で JUnit 5 を使う
IntelliJ IDEA のプラグインは IntelliJ IDEA のウィーザードや IntelliJ Platform Plugin Template を使えば簡単に始められます。
しかし、単体テストが JUnit 3 とか 4 とかなんですね。とくに古いからって困ることはないんですけど、せっかくなので JUnit 5 にしてみます。
JUnit 5 化
build.gradle.kts の修正
標準で JUnit 5 が有効になっていませんので、build.gradle.kts
ファイルを修正します。
diff --git a/build.gradle.kts b/build.gradle.kts
index ab1ec21..f319ca5 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -20,7 +20,16 @@ intellij {
plugins.set(listOf(/* Plugin Dependencies */))
}
+dependencies {
+ testImplementation("org.junit.jupiter:junit-jupiter-api:5.9.2")
+ testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.9.2")
+}
+
tasks {
+ test {
+ useJUnitPlatform()
+ }
+
// Set the JVM compatibility versions
withType<JavaCompile> {
sourceCompatibility = "17"
テストケースを書く
JUnit 5 ではアノテーションやアサーションが変更されています。
package com.example.demo
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
class Test {
@Test
fun test() {
assertTrue(false)
}
}
IntelliJ の機能テストを書く
IntelliJ の機能テストはドキュメントが少なくて手探り状態でした。
まず、IntelliJ の機能テストのためには様々なサービスが登録されていなければなりません。テスト用にそれらを初期化するために TestApplicationManager.getInstance()
メソッドを呼び出す必要があります。
package com.example.demo
import com.intellij.testFramework.TestApplicationManager
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
class Test {
@Test
fun test() {
assertTrue(false)
}
companion object {
init {
TestApplicationManager.getInstance()
}
}
}
テスト用の IntelliJ プロジェクトを読み込みます。
@Test
fun test() {
val projectRoot = System.getProperty("user.dir")
val project = PlatformTestUtil.loadAndOpenProject(Path(projectRoot)) {}
// 第二引数 (`{}`) はプロジェクトを閉じたときに実行されるクロージャー (多分)
}
プロジェクトからファイルを読み込むには VirtualFile や PsiFile への理解が欠かせません。(私はよくわかってません)
@Test
fun test() {
val projectRoot = System.getProperty("user.dir")
val project = PlatformTestUtil.loadAndOpenProject(Path(projectRoot)) {}
val virtualFile = VfsTestUtil.findFileByCaseSensitivePath(
Paths.get(projectRoot, "src/test/kotlin/com/example/demo/Test.kt").toString()
)
val psiFile = PsiManager.getInstance(project).findFile(virtualFile)!!
println(psiFile.fileType.name)
println(psiFile.text)
}
このテストを実行すると、ERROR: Read access is allowed from inside read-action (or EDT) only (see com.intellij.openapi.application.Application.runReadAction())
というエラーになります。
自分もあまりよくわかってないのですが、UI スレッドでないスレッドから呼び出せないメソッドが含まれているようです。
次のように runReadAction()
メソッドのクロージャー内で処理すればいいのですが、テストのたびに書いていたら結構面倒です。
ApplicationManager.getApplication().runReadAction {
// ここに処理を書く
}
代わりに、テストクラスを拡張します。
@ExtendWith(EdtInterceptor::class)
class Test {
// ...
これでエラーがなくなりました。
ファイルタイプとファイルの内容がコンソールに出力されます。(ファイルタイプが PLAIN_TEXT
になってるのが残念ですが、どうすればいいのかはいまいちわかりません)
ファイルの内容を書き換えるには、Document クラスを利用します。
@Test
fun test() {
val projectRoot = System.getProperty("user.dir")
val project = PlatformTestUtil.loadAndOpenProject(Path(projectRoot)) {}
val virtualFile = VfsTestUtil.findFileByCaseSensitivePath(
Paths.get(projectRoot, "src/test/kotlin/com/example/demo/Test.kt").toString()
)
val psiFile = PsiManager.getInstance(project).findFile(virtualFile)!!
println(psiFile.fileType.name)
println(psiFile.text)
val document = PsiDocumentManager.getInstance(project).getDocument(psiFile)!!
// 0 文字目から 10 文字目までを削除して "abc" という文字列を挿入します
document.replaceString(0, 10, "abc")
println(document.text)
}
これを実行すると ERROR: Assertion failed: Write access is allowed inside write-action only (see com.intellij.openapi.application.Application.runWriteAction())
エラーになります。
WriteCommandAction
クラスを利用して、書き込みを許可したうえで書き換えるようにします。
@Test
fun test() {
// ...
val document = PsiDocumentManager.getInstance(project).getDocument(psiFile)!!
WriteCommandAction.writeCommandAction(project).run<Exception> {
document.replaceString(0, 10, "abc")
}
println(psiFile.text)
}
無事、書き換えられました。
最後に
textlint IntelliJ Plugin の開発で機能テストも行っていますが、ドキュメントが少なくて手探り状態ですすめています。
これがいいやり方かどうかは分かりませんが、参考になればと思い公開しました。間違いや別の方法があれば教えていただけるとうれしいです。
Discussion