此页面解释了如何在你的模组中编写自动化测试。 有两种方法自动化测试你的模组:使用 Fabric Loader JUnit 进行单元测试,或使用 Minecraft 游戏测试框架进行游戏内测试。
单元测试用于测试你代码中的组件,比如方法和工具类;游戏内测试则启动 Minecraft 客户端与服务端来运行你的测试,适用于测试功能和游玩过程。
单元测试
由于 Minecraft 模组运行依赖于运行时字节码修改工具比如 mixin,仅仅添加并使用 JUnit 一般不会生效。 这就是为什么 Fabric 提供了 Fabric Loader JUnit,一个针对 Minecraft 模组进行单元测试的 JUnit 插件。
配置 Fabric Loader JUnit
首先,我们需要将 Fabric Loader JUnit 添加到开发环境。 将以下依赖添加到你的 build.gradle:
gradle
testImplementation "net.fabricmc:fabric-loader-junit:${project.loader_version}"1
然后,我们需要告诉 Gradle 使用 Fabric Loader JUnit 来测试。 你可以通过将以下代码添加到 build.gradle 来做到这件事:
gradle
test {
useJUnitPlatform()
}1
2
3
2
3
编写测试
重新加载 Gradle 后,现在就可以编写测试了。
这些测试的编写方式与常规 JUnit 测试相同,如果你想访问任何依赖于注册表的类(例如ItemStack),则需要进行一些额外的设置。 如果对 JUnit 比较熟悉,那么可以跳至设置注册表。
设置你的第一个测试类
测试编写于 src/test/java 目录下。
一种命名约定是镜像你正在测试的类的包结构。 例如,要测试 src/main/java/com/example/docs/codec/BeanType.java,应在 src/test/java/com/example/docs/codec/BeanTypeTest.java 创建一个类。 注意我们是如何将 Test 加入到类名称最后的。 这还允许你轻松访问包范围内私有(package-private)的方法和字段。
另一个命名约定是有一个 test 包,例如 src/test/java/com/example/docs/test/codec/BeanTypeTest.java。 如果你使用 Java 模块,这可以避免在使用相同包时可能出现的一些问题。
创建测试类后,使用 ⌘/CTRL+N 调出生成菜单。 选择测试并开始输入方法名称,通常以 test 开头。 完成时请按下 ENTER 键。 有关使用 IDE 的更多提示和技巧,请参阅 IDE 提示和技巧。

当然,你可以手写方法签名,任何没有参数且返回类型为 void 的实例方法都将被标识为测试方法。 你应该以这样结尾:

注意侧边栏中的绿色箭头指示——可以简单地点击来运行测试。 或者,你的测试将在每次构建时自动运行,包括 GitHub Actions 等 CI 构建。 如果你正在使用 GitHub Actions,请不要忘记阅读设置 GitHub Actions。
现在,该编写您的实测代码了。 你可以使用 org.junit.jupiter.api.Assertions 断言条件。 检查以下测试:
java
public class BeanTypeTest {
private static final Gson GSON = new GsonBuilder().create();
@BeforeAll
static void beforeAll() {
BeanTypes.register();
}
@Test
void testBeanCodec() {
StringyBean expectedBean = new StringyBean("This bean is stringy!");
Bean actualBean = Bean.BEAN_CODEC.parse(JsonOps.INSTANCE, GSON.fromJson("{\"type\":\"example:stringy_bean\",\"stringy_string\":\"This bean is stringy!\"}", JsonObject.class)).getOrThrow();
Assertions.assertInstanceOf(StringyBean.class, actualBean);
Assertions.assertEquals(expectedBean.getType(), actualBean.getType());
Assertions.assertEquals(expectedBean.getStringyString(), ((StringyBean) actualBean).getStringyString());
}
@Test
void testDiamondItemStack() {
// I know this isn't related to beans, but I need an example :)
ItemStack diamondStack = new ItemStack(Items.DIAMOND, 65);
Assertions.assertTrue(diamondStack.is(Items.DIAMOND));
Assertions.assertEquals(65, diamondStack.getCount());
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
有关此代码实际作用的解释,请参阅 Codec。
设置注册表
很好,第一个测试运行了! 但是稍等,第二个测试失败了? 在日志文件中,我们得到了以下错误。
log
java.lang.ExceptionInInitializerError
at net.minecraft.item.ItemStack.<clinit>(ItemStack.java:94)
at com.example.docs.codec.BeanTypeTest.testBeanCodec(BeanTypeTest.java:20)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
Caused by: java.lang.IllegalArgumentException: Not bootstrapped (called from registry ResourceKey[minecraft:root / minecraft:game_event])
at net.minecraft.Bootstrap.createNotBootstrappedException(Bootstrap.java:118)
at net.minecraft.Bootstrap.ensureBootstrapped(Bootstrap.java:111)
at net.minecraft.registry.Registries.create(Registries.java:238)
at net.minecraft.registry.Registries.create(Registries.java:229)
at net.minecraft.registry.Registries.<clinit>(Registries.java:139)
... 5 more
Not bootstrapped (called from registry ResourceKey[minecraft:root / minecraft:game_event])
java.lang.IllegalArgumentException: Not bootstrapped (called from registry ResourceKey[minecraft:root / minecraft:game_event])
at net.minecraft.Bootstrap.createNotBootstrappedException(Bootstrap.java:118)
at net.minecraft.Bootstrap.ensureBootstrapped(Bootstrap.java:111)
at net.minecraft.registry.Registries.create(Registries.java:238)
at net.minecraft.registry.Registries.create(Registries.java:229)
at net.minecraft.registry.Registries.<clinit>(Registries.java:139)
at net.minecraft.item.ItemStack.<clinit>(ItemStack.java:94)
at com.example.docs.codec.BeanTypeTest.testBeanCodec(BeanTypeTest.java:20)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
这是因为我们正在尝试访问注册表或依赖于注册表的类(或者,在极少数情况下,依赖于其他 Minecraft 类,如 SharedConstants),但 Minecraft 尚未初始化。 我们只需要初始化一下就能使注册表生效。 在你的 beforeAll 函数前简单地加入以下代码。
java
SharedConstants.tryDetectVersion();
Bootstrap.bootStrap();1
2
2
配置 GitHub Actions
INFO
本节假设您正在使用包含在示例模组和模组模板中的标准 GitHub Action 工作流。
你的测试现在将在每个构建上运行,包括由 GitHub Actions 等 CI 提供商构建的构建。 但是如果构建失败呢? 需要将日志作为工件上传,这样就可以查看测试报告了。
将下列文本添加进 .github/workflows/build.yaml 文件中 ./gradlew build 步骤的下方。
yaml
- name: Store reports
if: failure()
uses: actions/upload-artifact@v4
with:
name: reports
path: |
**/build/reports/
**/build/test-results/1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
游戏测试
Minecraft 提供了用于测试服务器端功能的游戏测试框架。 Fabric 还提供客户端游戏测试来测试客户端功能,类似于端到端测试。
使用 Fabric Loom 设置游戏测试
服务器和客户端游戏测试都可以手动设置或使用 Fabric Loom 设置。 本指南将使用 Loom。
要将游戏测试添加到你的模组,请将以下内容添加到你的 build.gradle:
gradle
fabricApi {
configureTests {
createSourceSet = true
modId = "example-mod-test-${project.name}"
enableGameTests = true // Default is true
enableClientGameTests = true // Default is true
eula = true // By setting this to true, you agree to the Minecraft EULA.
}
}1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
要查看所有可用选项,请参阅 Loom 测试文档。
设置游戏测试目录
INFO
如果你启用了 createSourceSet,则仅需要此部分,推荐这样做。 当然,你也可以自己编写 Gradle 代码,但你需要自行承担所有风险。
如果你像上面的示例一样启用了 createSourceSet,那么你的游戏测试将位于单独的源集中,并带有单独的 fabric.mod.json。 模块名称默认为 gametest。 在 src/gametest/resources/ 中创建一个新的 fabric.mod.json,如下所示:
json
{
"schemaVersion": 1,
"id": "example-mod-test",
"version": "1.0.0",
"name": "Example mod",
"icon": "assets/example-mod/icon.png",
"environment": "*",
"entrypoints": {
"fabric-gametest": ["com.example.docs.ExampleModGameTest"],
"fabric-client-gametest": ["com.example.docs.ExampleModClientGameTest"]
}
}1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
请注意,此 fabric.mod.json 需要在 src/gametest/java/com/example/docs/ExampleModGameTest 进行服务器游戏测试,并在 src/gametest/java/com/example/docs/ExampleModClientGameTest 进行客户端游戏测试。
编写游戏测试
你现在可以在 src/gametest/java 目录中创建服务器和客户端游戏测试。 以下是每个示例的基本示例:
java
package com.example.docs;
import java.lang.reflect.Method;
import net.minecraft.gametest.framework.GameTestHelper;
import net.minecraft.world.level.block.Blocks;
import net.fabricmc.fabric.api.gametest.v1.CustomTestMethodInvoker;
import net.fabricmc.fabric.api.gametest.v1.GameTest;
public class ExampleModGameTest implements CustomTestMethodInvoker {
@GameTest
public void test(GameTestHelper context) {
context.assertBlockPresent(Blocks.AIR, 0, 0, 0);
context.succeed();
}
@Override
public void invokeTestMethod(GameTestHelper context, Method method) throws ReflectiveOperationException {
context.setBlock(0, 0, 0, Blocks.AIR);
method.invoke(this, context);
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
java
package com.example.docs;
import net.fabricmc.fabric.api.client.gametest.v1.FabricClientGameTest;
import net.fabricmc.fabric.api.client.gametest.v1.context.ClientGameTestContext;
import net.fabricmc.fabric.api.client.gametest.v1.context.TestSingleplayerContext;
@SuppressWarnings("UnstableApiUsage")
public class ExampleModClientGameTest implements FabricClientGameTest {
@Override
public void runTest(ClientGameTestContext context) {
try (TestSingleplayerContext singleplayer = context.worldBuilder().create()) {
singleplayer.getClientWorld().waitForChunksRender();
context.takeScreenshot("example-mod-singleplayer-test");
}
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
有关详细信息,请参阅 Fabric API 中的相应 Javadoc。
运行游戏测试
服务器游戏测试将通过 Gradle 任务 build 自动运行。 你可以使用 Gradle 任务 runClientGameTest 运行客户端游戏测试。
在 GitHub Actions 上运行游戏测试
使用 build 的现有 GitHub Action 工作流将自动运行服务器游戏测试。 要使用 GitHub Actions 运行客户端游戏测试,请将以下代码片段添加到 build.gradle 文件,并将以下作业添加到你的工作流。 Gradle 代码片段将使用 Loom 的生产运行任务运行客户端游戏测试,该作业将在 CI 中执行生产运行任务。
WARNING
目前,由于网络同步器出现错误,游戏测试可能会在 GitHub Actions 上失败。 如果遇到此错误,你可以在生产运行任务声明中将 -Dfabric.client.gametest.disableNetworkSynchronizer=true 添加到 JVM 参数中。
gradle
dependencies {
productionRuntimeMods "net.fabricmc.fabric-api:fabric-api:${fabricApiVersion}"
}
tasks.register("runProductionClientGameTest", net.fabricmc.loom.task.prod.ClientProductionRunTask) {
jvmArgs.add("-Dfabric.client.gametest")
// Whether to use XVFB to run the game, using a virtual framebuffer. This is useful for headless CI environments.
// Defaults to true only on Linux and when the "CI" environment variable is set.
// XVFB must be installed, on Debian-based systems you can install it with: `apt install xvfb`
useXVFB = true
}1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
yaml
client_game_test:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 21
- run: ./gradlew runProductionClientGameTest
- if: always()
uses: actions/upload-artifact@v4
with:
path: build/run/clientGameTest/screenshots1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13

