/
kiraru
/
DeadBlocks
Обзор
Документация
Войти
/
kiraru
/
DeadBlocks
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
tests/test_game_controller.py
214 строк
8 KB
kiraru
маленький фикс + коментарии
23 июн 2026, 09:51
23 июн 2026, 09:51
516b2d4
Код
Авторство
О чём код?
# test_game_controller.py — интеграционные тесты GameController import sys import os import unittest import unittest.mock as mock sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) # мокаем pygame ДО любых игровых импортов, чтобы тесты работали без дисплея. # только controllers/ и main.py зависят от pygame; models/ и systems/ — нет. _pygame_mock = mock.MagicMock() _pygame_mock.K_LEFT = 276 _pygame_mock.K_RIGHT = 275 _pygame_mock.K_SPACE = 32 _pygame_mock.K_LSHIFT = 304 _pygame_mock.K_RSHIFT = 303 _pygame_mock.K_a = 97; _pygame_mock.K_d = 100 _pygame_mock.K_w = 119; _pygame_mock.K_s = 115 _pygame_mock.K_UP = 273; _pygame_mock.K_DOWN = 274 _pygame_mock.K_z = 122; _pygame_mock.K_j = 106; _pygame_mock.K_x = 120 _pygame_mock.K_e = 101; _pygame_mock.K_RETURN = 13 _pygame_mock.K_ESCAPE = 27; _pygame_mock.K_p = 112 _pygame_mock.K_1=49; _pygame_mock.K_2=50; _pygame_mock.K_3=51; _pygame_mock.K_4=52 _pygame_mock.K_5=53; _pygame_mock.K_6=54; _pygame_mock.K_7=55 _pygame_mock.key.get_pressed.return_value = {} sys.modules["pygame"] = _pygame_mock from constants import Screen, Difficulty from models.game_state import GameState from systems.save_system import SaveSystem from controllers.game_controller import GameController from controllers.input_handler import InputState from systems.level_generator import LevelGenerator, Tile def make_controller_with_room(difficulty=Difficulty.EASY, seed=0): """вспомогательная фабрика: GameState + GameController с одной готовой комнатой.""" gs = GameState() gs.start_new_game(difficulty) gen = LevelGenerator(difficulty, gs.boss_weak_type, seed=seed) gs.rooms = gen.generate_all_rooms() room = gs.rooms[0] wx, wy = room.player_spawn_world() gs.player.rect.x = wx gs.player.rect.y = wy save = SaveSystem(path="/tmp/test_save_game_controller.json") controller = GameController(gs, save) return gs, controller class TestDoorNoLongerGivesArtifact(unittest.TestCase): def test_door_advances_room_without_artifact_screen(self): gs, controller = make_controller_with_room() room = gs.rooms[0] for enemy in room.enemies: enemy.hp = 0 door_col, door_row = room.exit_pos wx, wy = room.world_pos(door_col, door_row) gs.player.rect.x = wx gs.player.rect.y = wy controller._try_advance_room() self.assertEqual( gs.current_screen, Screen.GAMEPLAY, "door must NOT switch to artifact screen anymore" ) self.assertEqual(gs.current_room_index, 1, "door must advance to next room") def test_door_blocked_while_enemies_alive(self): gs, controller = make_controller_with_room() initial_index = gs.current_room_index controller._try_advance_room() self.assertEqual(gs.current_room_index, initial_index, "door must stay locked") self.assertEqual(gs.current_screen, Screen.GAMEPLAY) class TestChestGivesArtifactChoice(unittest.TestCase): """регрессионный тест: сундук теперь открывает экран выбора артефакта""" def _build_input(self, interact=True): inp = InputState() inp.interact = interact return inp def test_opening_chest_shows_artifact_screen(self): gs, controller = make_controller_with_room(difficulty=Difficulty.EASY, seed=0) room = gs.rooms[0] if not room.chest_positions: self.skipTest("no chest in room 0 for this seed") chest_col, chest_row = room.chest_positions[0] wx, wy = room.world_pos(chest_col, chest_row) gs.player.rect.x = wx gs.player.rect.y = wy inp = self._build_input(interact=True) controller._update_gameplay(inp, dt=1 / 60) self.assertEqual( gs.current_screen, Screen.ARTIFACT, "opening a chest must show the artifact choice screen" ) self.assertGreater(len(gs.offered_artifacts), 0) def test_chest_offers_count_matches_difficulty_artifact_count(self): from constants import DIFFICULTY_SETTINGS for difficulty in [Difficulty.EASY, Difficulty.MEDIUM, Difficulty.HARD, Difficulty.PLAGUE]: gs, controller = make_controller_with_room(difficulty=difficulty, seed=2) expected_count = DIFFICULTY_SETTINGS[difficulty]["artifact_count"] chest_room = next((r for r in gs.rooms if r.chest_positions), None) if chest_room is None: continue chest_col, chest_row = chest_room.chest_positions[0] gs.current_room_index = chest_room.room_index wx, wy = chest_room.world_pos(chest_col, chest_row) gs.player.rect.x = wx gs.player.rect.y = wy inp = self._build_input(interact=True) controller._update_gameplay(inp, dt=1 / 60) self.assertEqual( len(gs.offered_artifacts), expected_count, f"{difficulty}: chest must offer exactly {expected_count} artifacts" ) def test_chest_removed_after_opening(self): gs, controller = make_controller_with_room(difficulty=Difficulty.EASY, seed=0) room = gs.rooms[0] if not room.chest_positions: self.skipTest("no chest in room 0 for this seed") chest_pos = room.chest_positions[0] wx, wy = room.world_pos(*chest_pos) gs.player.rect.x = wx gs.player.rect.y = wy inp = self._build_input(interact=True) controller._update_gameplay(inp, dt=1 / 60) self.assertNotIn(chest_pos, room.chest_positions) self.assertEqual(room.get_tile(*chest_pos), Tile.FLOOR) class TestApplyChosenArtifactDoesNotAdvanceRoom(unittest.TestCase): """ Регрессионный тест: применение артефакта (выбранного в сундуке) """ def test_apply_chosen_artifact_stays_in_same_room(self): gs, controller = make_controller_with_room(difficulty=Difficulty.EASY, seed=0) gs.current_screen = Screen.ARTIFACT gs.prepare_artifact_choice() initial_room_index = gs.current_room_index controller.apply_chosen_artifact(0) self.assertEqual( gs.current_room_index, initial_room_index, "applying an artifact from a chest must NOT advance the room" ) self.assertEqual(gs.current_screen, Screen.GAMEPLAY) def test_apply_chosen_artifact_grants_the_artifact(self): gs, controller = make_controller_with_room(difficulty=Difficulty.EASY, seed=0) gs.prepare_artifact_choice() initial_artifact_count = len(gs.player.artifacts) controller.apply_chosen_artifact(0) self.assertEqual(len(gs.player.artifacts), initial_artifact_count + 1) class TestEnemyBuffInterval(unittest.TestCase): """Регрессионный тест на изменение интервала баффа врагов (60с -> 15с).""" def test_buff_interval_constant_is_15_seconds(self): from constants import ENEMY_BUFF_INTERVAL self.assertEqual(ENEMY_BUFF_INTERVAL, 15.0) def test_mob_buffs_after_15_seconds(self): """ Баф применяется через apply_global_buff(count), где count вычисляет контроллер из elapsed_time. После 15 секунд count=1. """ from models.enemy import Mob from constants import ENEMY_BUFF_INTERVAL mob = Mob(0, 0, hp_mult=1.0, dmg_mult=1.0) initial_max_hp = mob.max_hp # Симулируем вызов контроллера: прошло 15.1с → buff_count = 1 global_buff_count = int(15.1 / ENEMY_BUFF_INTERVAL) mob.apply_global_buff(global_buff_count) self.assertGreater(mob.max_hp, initial_max_hp, "mob should buff after 15s") if __name__ == "__main__": unittest.main(verbosity=2)