测试体系、Fixture 与 CI 门禁¶
IntelliJ Platform 插件测试不是普通纯单元测试。官方建议以模型级功能测试为主:在 headless 环境中使用真实平台组件,加载测试文件,执行功能,再比较输出。这样测试更慢,但稳定性和真实度远高于大量 mock。
测试分层¶
| 层级 | 适合测试 |
|---|---|
| 纯单元测试 | 与平台无关的解析、算法、DTO、协议 |
| Light tests | PSI、补全、检查、Quick Fix、Action 上下文 |
| Heavy tests | 多模块项目、真实项目结构、模块/库/Facet 变更 |
| Integration/UI tests | 真实 UI 流程、Split Mode UI、端到端插件行为 |
| Plugin Verifier | 二进制兼容、API 可用性、目标 IDE 兼容 |
默认优先写 light tests。只有确实需要多模块、真实项目重建或更完整环境时才写 heavy tests。
测试依赖¶
Gradle 2.x 需要显式声明测试框架:
dependencies {
intellijPlatform {
testFramework(org.jetbrains.intellij.platform.gradle.TestFrameworkType.Platform)
}
}
测试 Java PSI 或 Java 相关功能时,还需要 Java 插件依赖和对应测试框架能力:
dependencies {
intellijPlatform {
bundledPlugin("com.intellij.java")
testFramework(org.jetbrains.intellij.platform.gradle.TestFrameworkType.Platform)
}
}
外部系统集成测试可使用:
dependencies {
testImplementation("com.jetbrains.intellij.platform:external-system-test-framework")
}
Base Class 与 Fixture¶
两种写法:
- 继承标准 base class,例如
BasePlatformTestCase。 - 手动用
IdeaTestFixtureFactory创建 fixture,不绑定某个测试框架。
常用基类:
| 基类 | 场景 |
|---|---|
BasePlatformTestCase |
不依赖 Java 的 light test |
LightPlatformTestCase |
平台级 light test |
LightJavaCodeInsightFixtureTestCase |
JUnit 3 Java PSI / code insight |
LightJavaCodeInsightFixtureTestCase4 |
JUnit 4 Java PSI / code insight |
LightJavaCodeInsightFixtureTestCase5 |
JUnit 5 Java PSI / code insight |
HeavyPlatformTestCase |
heavy test |
Light tests 会尽量复用项目实例。Heavy tests 每个测试创建新项目,成本更高。
LightProjectDescriptor¶
Light test 需要 SDK、库、Facet、模块类型时,用 LightProjectDescriptor:
override fun getProjectDescriptor(): LightProjectDescriptor {
return JAVA_17
}
如果相邻测试返回同一个 descriptor,项目可以复用;descriptor 不同则重建项目。descriptor 的 equals() 会影响复用行为。
testdata 目录¶
推荐结构:
src/test/kotlin/com/example/
src/test/testData/
inspections/
completion/
formatter/
rename/
第三方插件必须覆写 getTestDataPath():
override fun getTestDataPath(): String {
return "src/test/testData"
}
常用 fixture 方法:
myFixture.configureByFile("completion/basic.example")
myFixture.configureByFiles("rename/before.example", "rename/other.example")
myFixture.copyFileToProject("project/config.example", "config.example")
myFixture.copyDirectoryToProject("project", ".")
testdata 文件不应放在 source root 下,因为它们往往不是可编译源码。
特殊标记¶
编辑器测试支持特殊标记:
| 标记 | 含义 |
|---|---|
<caret> |
光标位置 |
<selection> / </selection> |
选择区 |
<block> / </block> |
列选择区 |
示例:
foo.<caret>bar
Highlighting 测试¶
检查、Annotator、parser error highlighting 都可以用:
myFixture.enableInspections(ExampleInspection())
myFixture.configureByFile("highlighting/warning.example")
myFixture.checkHighlighting(true, true, true)
expected markup:
<warning descr="Unknown property">missing.key</warning>
支持 severity:
<error><warning><weak_warning><info><inject>- custom severity name
SyntaxHighlighter 的 lexer 级高亮可用 EditorTestUtil.testFileSyntaxHighlighting()。推荐先让测试生成 answer file,再人工审查,避免手写大量 token attribute 输出。
Completion、Quick Fix、Formatter 测试¶
Completion:
myFixture.configureByFile("completion/basic.example")
myFixture.completeBasic()
assertContainsElements(myFixture.lookupElementStrings!!, "expectedName")
Quick Fix:
myFixture.enableInspections(ExampleInspection())
myFixture.configureByFile("quickfix/before.example")
val action = myFixture.findSingleIntention("Create property")
myFixture.launchAction(action)
myFixture.checkResultByFile("quickfix/after.example")
Formatter:
myFixture.configureByFile("formatter/before.example")
CodeStyleManager.getInstance(project).reformat(myFixture.file)
myFixture.checkResultByFile("formatter/after.example")
Mock 策略¶
官方不推荐大规模 mock 平台组件。插件代码通常依赖真实 PSI、VFS、Project Model、Service、Extension Point 和索引;mock 这些对象很容易测到一个与真实 IDE 不一致的系统。
适合 mock 的对象:
- 外部 HTTP 客户端。
- 自己定义的纯业务接口。
- 调试协议或 LSP/MCP 后端。
- 时间、随机数、文件下载等边界。
不建议 mock:
ProjectPsiElementVirtualFileDocumentDumbServiceExtensionPointName
CI 门禁¶
PR 门禁:
./gradlew test
./gradlew buildPlugin
./gradlew verifyPlugin
Nightly 或 release candidate:
./gradlew runPluginVerifier
Release:
./gradlew signPlugin
./gradlew publishPlugin
多产品插件建议构建 verifier matrix:
| 维度 | 示例 |
|---|---|
| IDE 产品 | IC、IU、PY、WS、GO、DB |
| 版本 | since-build、最新稳定、EAP |
| 可选依赖 | 有/无 JavaScript、Database、Kotlin |
| 运行模式 | 单体 IDE、Split Mode |
失败排查¶
| 现象 | 优先检查 |
|---|---|
| 测试偶发找不到文件 | in-memory FS 缓存不一致,清理 sandbox system |
checkHighlighting() 多出高亮 |
是否开启了额外 inspection;ignoreExtraHighlighting 是否应为 true |
| Completion 为空 | 光标标记、文件类型、Dumb Mode、依赖插件 |
| Java PSI 测试失败 | Mock JDK / Java plugin / project descriptor |
| Heavy test 很慢 | 是否能降级为 light test |
| CI 通过本地失败 | Gradle JVM、IDE sandbox、环境变量差异 |