게임 내 테스트
Poly 모드 로더 모드는 Mojang의 게임 테스트 서버에서 실제 게임으로 테스트합니다. 창이 뜨지 않으므로 CI에서도 실행할 수 있으며, 결과는 게임 테스트 서버가 판정합니다.
테스트 등록
테스트는 common 엔트리포인트에서 등록합니다. ID는 <mod id>:<이름>이고, 테스트마다 빈 공간이 하나씩 주어지며, (0, 0, 0)은 그 공간의 가장 낮은 모서리입니다.
mod.gameTests().register("ruby_block_drops", test -> {
BlockPos at = BlockPos.of(0, 1, 0);
test.setBlock(at, ResourceId.of("ruby_mod", "ruby_block"));
test.assertBlock(at, ResourceId.of("ruby_mod", "ruby_block"));
int dropped = test.runCommand("loot spawn ~ ~2 ~ loot ruby_mod:blocks/ruby_block");
test.assertTrue(dropped == 1, "전리품 테이블이 아이템 1개를 떨어뜨림");
test.succeed();
});
succeed()를 꼭 호출하세요테스트는 succeed()를 호출해야 통과합니다. 단언이 실패하거나 시간 안에 끝나지 않으면 실패로 보고됩니다.
등록 형태
| 메서드 | 뜻 |
|---|---|
register(name, test) | 기본 시간, 블록 하나 공간 |
register(name, maxTicks, test) | 최대 틱 수 지정 |
register(name, maxTicks, area, test) | 최대 틱 수와 테스트 공간(TestArea) |
잘못된 테스트 이름·시간은 POLY-GAMETEST-001, 등록이 닫힌 뒤 등록은 POLY-GAMETEST-002입니다.
테스트에서 쓰는 것
GameTestContext(위 예의 test)가 제공하는 메서드입니다.
| 메서드 | 용도 |
|---|---|
world(), absolute(pos) | 테스트 월드와, 상대 좌표를 월드 좌표로 바꾸기 |
setBlock(pos, block), assertBlock(pos, block) | 블록 놓기와 확인(상대 좌표) |
assertTrue(condition, message), fail(message) | 단언과 실패 |
createMockPlayer() | 연결 없는 플레이어(아이템, 블록 사용, 메뉴) |
createMockServerPlayer() | 서버 플레이어가 필요한 기능(발전 과제, 권한, 아래 상호작용 도우미) |
createConnectedMockPlayer() | 클라이언트처럼 메모리 연결로 접속하는 생존 모드 플레이어(PlayerEvents.JOINED 발생) |
respawn(player, returnedFromEnd) | 접속한 모의 플레이어를 사망 화면이나 엔딩 크레딧 뒤 클라이언트 요청처럼 리스폰시키고, 새 플레이어를 돌려줌 |
disconnect(player) | 접속한 모의 플레이어를 내보냄(PlayerEvents.LEAVING 발생) |
useBlock(pos, player) | 플레이어가 블록을 빈손으로 사용 |
interactBlock(player, pos, face) | 서버 플레이어가 주 손으로 블록의 한 면을 우클릭. 서버가 클라이언트의 클릭을 처리하는 방식 그대로이며 InteractionResult를 돌려줌 |
interactItem(player) | 서버 플레이어가 주 손의 아이템을 허공에 사용 |
interactEntity(player, target) | 서버 플레이어가 주 손으로 엔티티를 우클릭 |
attackEntity(player, target) | 서버 플레이어가 좌클릭처럼 엔티티를 공격 |
breakBlock(player, pos) | 서버 플레이어가 캐기를 마친 것처럼 블록을 바로 부숨. 부서졌는지를 돌려줌 |
runCommand(command) | 테스트 위치에서 서버 명령 실행, 결과값 반환 |
runAfter(ticks, action) | 몇 틱 뒤에 이어서 확인 |
succeed() | 통과 |
world()는 월드 좌표를 쓰므로, 상대 좌표는 test.world().blockAt(test.absolute(pos))처럼 absolute로 바꿔 넘깁니다.
예: 아이템 지급
mod.gameTests().register("ruby_can_be_given", test -> {
PlayerView player = test.createMockPlayer();
player.give(ResourceId.of("ruby_mod", "ruby"), 2);
test.assertTrue(player.countItem(ResourceId.of("ruby_mod", "ruby")) == 2, "the player holds 2 rubies");
test.succeed();
});
예: 블록 사용
mod.gameTests().register("using_glow_block_gives_spark", test -> {
test.setBlock(BlockPos.of(0, 1, 0), GLOW_BLOCK);
PlayerView player = test.createMockPlayer();
test.useBlock(BlockPos.of(0, 1, 0), player);
test.assertTrue(player.countItem(SPARK) == 1, "expected 1 spark, found " + player.countItem(SPARK));
test.succeed();
});
예: 명령어 결과
mod.gameTests().register("command_returns_sum", test -> {
int result = test.runCommand("showcase add 2 3");
test.assertTrue(result == 5, "/showcase add 2 3 returned " + result);
test.succeed();
});
예: 클라이언트처럼 상호작용하기
interactBlock, interactItem, interactEntity, attackEntity, breakBlock은 접속한 클라이언트의 입력을 서버가 처리하는 방식 그대로 서버 플레이어를 움직입니다. 상호작용·블록 이벤트가 먼저 불리고, 그다음 게임 자체의 검사와 동작이 이어집니다. useBlock과 달리 interactBlock은 손에 든 블록 아이템을 놓기도 합니다. 플레이어는 createMockServerPlayer()(또는 createConnectedMockPlayer())로 만들어야 하며, 다른 플레이어를 넘기면 IllegalArgumentException이 납니다.
mod.gameTests().register("break_event_cancels_breaking", 20, new TestArea(3, 3, 1), test -> {
BlockPos ward = BlockPos.of(0, 1, 0);
test.setBlock(ward, WARDED_BLOCK);
PlayerView player = test.createMockServerPlayer();
test.assertTrue(!test.breakBlock(player, ward), "an empty hand cannot break the warded block");
test.assertBlock(ward, WARDED_BLOCK);
player.give(SPARK, 1);
test.assertTrue(test.breakBlock(player, ward), "a spark breaks the warded block");
test.succeed();
});
예: 리스폰
createConnectedMockPlayer()로 만든 플레이어는 respawn으로 리스폰합니다. 게임이 새 플레이어 객체를 만들고, PlayerEvents.COPY와 RESPAWNED가 불리며, respawn이 새 플레이어를 돌려줍니다. 사망 후 리스폰을 요청했는데 플레이어가 아직 살아 있으면 IllegalStateException이 납니다.
mod.gameTests().register("respawn_fires_copy_and_respawned", 40, test -> {
PlayerView player = test.createConnectedMockPlayer();
player.hurt(ResourceId.minecraft("generic_kill"), 50); // /kill과 같음
PlayerView respawned = test.respawn(player, false); // false: 사망 후
int count = respawned.data(respawns).data().getInt("count", 0);
test.assertTrue(count == 1, "the new player counted 1 respawn, found " + count);
test.disconnect(respawned);
test.succeed();
});
접속한 플레이어는 진짜 플레이어처럼 테스트 월드 전체에 영향을 줍니다. 모든 플레이어에게서 먼 몹은 사라지고, 그 플레이어 근처에는 몹이 생겨 동시에 도는 다른 테스트를 방해합니다. 테스트 함수가 끝나기 전에 disconnect를 부르세요. 테스트가 끝날 때까지 남은 플레이어는 그때 내보냅니다.
examples/native-showcase-mod의 ShowcaseGameTests에 모든 API 영역의 테스트가 있습니다.
다음
- 넓은 공간이나 시간이 필요한 테스트: 테스트 공간과 시간
- 실행 방법: 테스트 실행