Автоматизированное тестирование 26.1.2
Руководство по написанию автоматических тестов с помощью Fabric Loader JUnit.
На этой странице объясняется, как написать код для автоматического тестирования частей вашего мода. Существует два способа автоматического тестирования вашего мода: модульные тесты с помощью Fabric Loader JUnit или игровые тесты с помощью фреймворка Gametest из Minecraft.
Модульные тесты следует использовать для тестирования компонентов вашего кода, таких как методы и вспомогательные классы, в то время как игровые тесты запускают реальный клиент и сервер Minecraft для запуска ваших тестов, что делает их подходящими для тестирования функций и игрового процесса.
Модульное тестирование
Поскольку моддинг Minecraft основан на инструментах модификации байт-кода во время выполнения, таких как Mixin, простое добавление и использование JUnit обычно не работает. Вот почему Fabric предоставляет Fabric Loader JUnit — плагин JUnit, который позволяет проводить модульное тестирование в Minecraft.
Настройка 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 в конец имени класса. Это также позволяет легко получить доступ к методам и полям, закрытым для пакета.
Другое соглашение об именовании — наличие пакета test, например src/test/java/com/example/docs/test/codec/BeanTypeTest.java. Это предотвращает некоторые проблемы, которые могут возникнуть при использовании того же пакета, если вы используете модули Java.
После создания тестового класса используйте ⌘/CTRL+N, чтобы вызвать меню «Создать». Выберите Test и начните вводить имя метода, обычно начинающееся с test. Нажмите ENTER, когда закончите. Дополнительные советы и рекомендации по использованию IDE см. в разделе Советы и рекомендации по IDE.

Конечно, вы можете написать сигнатуру метода вручную, и любой метод экземпляра без параметров и с типом возвращаемого значения void будет идентифицирован как тестовый метод. В итоге у вас должно получиться следующее:

Обратите внимание на зеленые стрелки индикаторы в желобе: вы можете легко запустить тест, щелкнув по ним. В качестве альтернативы ваши тесты будут запускаться автоматически для каждой сборки, включая сборки CI такие, как GitHub Actions. Если вы используете 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-mod: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 :)
ItemStackTemplate diamondStack = new ItemStackTemplate(Items.DIAMOND, 65);
Assertions.assertTrue(diamondStack.is(Items.DIAMOND));
Assertions.assertEquals(65, diamondStack.count());
}
}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
Объяснение того, что на самом деле делает этот код, см. в разделе Кодеки.
Настройка реестров
Отлично, первый тест сработал! Но подождите, второй тест не прошёл? В журналах мы получаем одну из следующих ошибок.
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
INFO
В этом разделе предполагается, что вы используете стандартный рабочий процесс GitHub Action, включенный в пример мода и в шаблон мода.
Теперь ваши тесты будут запускаться для каждой сборки, включая сборки поставщиков непрерывной интеграции, таких как GitHub Actions. Но что делать, если сборка не удалась? Нам необходимо загрузить журналы как артефакт, чтобы мы могли просматривать отчеты об испытаниях.
Добавьте это в файл .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. Создайте новый файл fabric.mod.json в папке src/gametest/resources/, как показано на рисунке:
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",
"com.example.docs.entity.EntityAttributesGameTest"
],
"fabric-client-gametest": ["com.example.docs.ExampleModClientGameTest"]
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Обратите внимание, что файл 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.getClientLevel().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
Более подробную информацию можно найти в соответствующих Javadocs в Fabric API.
Запуск тестов игры
Тесты серверной игры будут запускаться автоматически с помощью задачи Gradle build. Вы можете запустить тесты клиентской игры с помощью задачи Gradle runClientGameTest.
Запуск тестов игры в GitHub Actions
Существующие рабочие процессы GitHub Action, использующие build, будут автоматически запускать тесты серверных игр. Чтобы запустить тесты клиентской игры с помощью 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: 25
- 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

