На цій сторінці пояснюється, як написати код для автоматичного тестування частин вашого мода. Є два способи автоматично перевірити ваш мод: модульні тести за допомогою 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: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
Щоб отримати пояснення того, що насправді робить цей код, перегляньте кодеки.
Налаштування реєстрів
Чудово, перший тест спрацював! Але зачекайте, другий тест провалився? У журналах ми отримуємо одну з таких помилок.
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, який входить до прикладу мода та шаблону моду.
Ваші тести тепер виконуватимуться на кожній збірці, включно з тестами постачальників CI, таких як 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"],
"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
Для отримання додаткової інформації перегляньте відповідні Javadocs у Fabric API.
Запуск ігрового текстування
Серверні ігрові тестування запускатимуться автоматично із завданням build Gradle. Ви можете запускати клієнтські ігрові тестування за допомогою завдання Gradle runClientGameTest.
Запуск ігрових тестувань з GitHub Actions
Наявні робочі процеси GitHub Action, які використовують build, запускатимуть серверні ігрові тестування автоматично. Щоб запустити тестування клієнтські ігрові тестування за допомогою дій GitHub, додайте наступний сніппет до свого 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

