在 Kotlin 中 @Rule 必须是公开的:解决 ActivityTestRule 问题

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

引言:那个让人抓狂的错误

\n\n

您是否曾经为 Android 编写过 Kotlin 测试,添加了 ActivityTestRule,运行测试——然后突然看到这个令人心寒的消息:"The @Rule must be public"?如果是这样——您并不孤单。这个问题在 Stack Overflow 上获得了数千次浏览,每天都有数十名开发者踩到同一个坑。

\n\n

问题看似简单,但在 Kotlin 中却变成了一场真正的冒险。在 Java 中一切都很顺利:@Rule,公共字段——搞定。而在 Kotlin 中……嗯,您懂的。今天我们将弄清楚为什么会发生这种情况,更重要的是,如何一劳永逸地解决它。

\n\n

顺便说一句,如果您更喜欢 Python,PythonLib 上也有很多关于测试的有用内容。但今天——我们只讲纯 Kotlin 和 Android。

\n\n

为什么会出现 "The @Rule must be public" 错误?

\n\n

让我们回顾一下 ActivityTestRule 在 JUnit 中是如何工作的。这是一个规则(Rule),它在每个测试之前启动 Activity 并在测试之后正确关闭它。在 Java 中,您这样写:

\n\n
@Rule\npublic ActivityTestRule<MainActivity> activityTestRule =\n    new ActivityTestRule<>(MainActivity.class);\n
\n\n

很简单:字段是公共的,规则是 public。而在 Kotlin 中,默认情况下所有内容都是 public,但这里有一个陷阱。当您这样写时:

\n\n
@Rule\nval activityTestRule = ActivityTestRule(MainActivity::class.java)\n
\n\n

……Kotlin 会生成一个私有字段和一个公共 getter。而 JUnit 要求的是 公共字段,而不是 getter。这就是全部的秘密。

\n\n

错误 "The @Rule must be public" 出现的原因正是 JUnit 看不到您的字段——它是私有的。

\n\n

解决方案 #1:使用 @get:Rule(最流行的方法)

\n\n

最简单且最常用的方法是将注解应用于 getter 而不是字段。为此,Kotlin 提供了 @get:Rule 语法:

\n\n
class MainActivityTest {\n\n    @get:Rule\n    val activityTestRule = ActivityTestRule(MainActivity::class.java)\n\n    @Test\n    fun testActivityIsNotNull() {\n        assertNotNull(activityTestRule.activity)\n    }\n}\n
\n\n

这里发生了什么?我们是在说:"注解 getter,而不是字段"。JUnit 看到公共 getter,获得对规则的访问权限,一切正常。这已经经过数百名开发者的验证。

\n\n

这个解决方案是您的"方案 A"。它简洁、易懂,并且不需要额外的库。

\n\n

解决方案 #2:通过 @JvmField 使字段公开

\n\n

如果您不喜欢 @get:Rule 语法(虽然不应该),可以使用 @JvmField 注解。它强制 Kotlin 生成一个没有 getter 的公共字段:

\n\n
class MainActivityTest {\n\n    @JvmField\n    @Rule\n    val activityTestRule = ActivityTestRule(MainActivity::class.java)\n\n    @Test\n    fun testActivityIsNotNull() {\n        assertNotNull(activityTestRule.activity)\n    }\n}\n
\n\n

它的工作方式与 Java 代码完全相同。缺点——代码稍多一些。优点——明确指定字段应该是公共的。

\n\n

我个人更喜欢 @get:Rule——它更短,对于 Kotlin 开发者来说读起来更自然。但选择权在您手中。

\n\n

解决方案 #3:使用 ActivityScenarioRule(现代方法)

\n\n

现在让我们谈谈 Google 推荐用来替代 ActivityTestRule 的方法。在 androidx.test.ext:junit 库中,有 ActivityScenarioRule。它更灵活,并且与现代 Android 测试集成得更好。

\n\n

使用它时,同样需要使用 @get:Rule 或 @JvmField:

\n\n
class MainActivityTest {\n\n    @get:Rule\n    val activityScenarioRule = ActivityScenarioRule(MainActivity::class.java)\n\n    @Test\n    fun testActivityIsNotNull() {\n        activityScenarioRule.scenario.onActivity { activity ->\n                

博客

书籍推荐