$ unzip game.zip $ cd game $ python3 sokoban.py
from sokoban import World level = [ "#########", "#.$ @ #", "#########", ] world = World(level) print(world.nrows) # 3 print(world.ncols) # 9 print(world.worker_pos) # [(4, 1)] print(world.box_pos) # [(2, 1)] print(world.dock_pos) # [(1, 1)]
from sokoban import World SIMPLE_LEVEL = [ "#########", "#.$ @ #", "#########", ] def test_rect_world_dimensions(): world = World(SIMPLE_LEVEL) assert world.nrows == 3 assert world.ncols == 9
$ py.test
pytest.raises(exception, func, *args)
def test_invalid_character_error(): level = [ "########!", "#.$ #", "#########", ] pytest.raises(ValueError, World, level)
def test_world_get_wall(): world = World([ "#########", "#.$ @ #", "#########", ]) expected = Tile(wall=True, worker=False, dock=False, box=False) got = world.get((0, 0)) assert got == expected
def test_world_move_worker(): world = World([ "#########", "#.$ #", "# @ #", "#########", ]) world.move_worker((2, 2)) assert world.worker_pos == [(2, 2)]
def test_world_push_box(): world = World([ "#########", "#.$ #", "# @ #", "#########", ]) world.push_box((2, 1), (2, 2)) assert world.box_pos == [(2, 2)]
>>> from unittest.mock import Mock >>> m = Mock(a=1, b=2) >>> m.a 1 >>> m.b 2 >>> m.b = 3 >>> m.b 3
>>> mfunc = Mock(return_value=10) >>> mfunc() 10 >>> mfunc() 10
>>> mfunc = Mock(side_effect=["a", "b", "c"]) >>> mfunc() 'a' >>> mfunc() 'b' >>> mfunc() 'c' >>> mfunc() ... StopIteration
>>> mfunc = Mock(side_effect=ValueError("test error")) >>> mfunc() ... ValueError: test error
>>> m = Mock() >>> m.a = 10 >>> print(m.a) 10
>>> m = Mock() >>> m.b <Mock ...> >>> m = Mock() >>> m.b.c = 30 >>> m.b <Mock ...> >>> m.b.c 30
>>> m = Mock() >>> m.func.return_value = 20 >>> m.func() 20
>>> mfunc = Mock(return_value=None) >>> mfunc(1, 2) >>> mfunc(2, 3) >>> mfunc.call_args_list [calls(1, 2), calls(2, 3)]
>>> mfunc = Mock(return_value=None) >>> mfunc(1, 2) >>> mfunc(2, 3) >>> mfunc.assert_called_with(2, 3) >>> mfunc.assert_called_with(1, 2) ... AssertionError
>>> mfunc = Mock(return_value=None) >>> mfunc(1, 2) >>> mfunc(2, 3) >>> mfunc.assert_any_call(1, 2) >>> mfunc.assert_any_call(2, 3)
class GameEngine: def move(): ... def is_game_over(): ...
class World: def get(): ... def move_worker(): ... def push_box(): ...
def is_game_over(world): for box in world.box_pos: if box not in world.dock_pos: return False return True
from unittest.mock import Mock from sokoban import GameEngine def test_game_is_over_true(): # Arrange engine = GameEngine() world = Mock() world.box_pos = [(1, 1), (1, 2)] world.dock_pos = [(1, 1), (1, 2)] # Act result = engine.is_game_over(world) # Assert assert result == True
def move(direction, world): x, y = world.worker_pos[0] if direction == Dir.UP: next_pos = (x, y - 1) elif direction == Dir.DN: next_pos = (x, y + 1) elif direction == Dir.RT: next_pos = (x + 1, y) else: # if direction == Dir.LT: next_pos = (x - 1, y) next_tile = world.get(next_pos) if next_tile.wall: return world.move_worker(next_pos)
mock_world = Mock() mock_world.worker_pos = [(3, 1)] mock_world.get.return_value = Tile(wall=False, dock=False, worker=False, box=False) engine = GameEngine() engine.move(Dir.RT, mock_world) mock_world.move_worker.assert_called_with((4, 1))
mock_world = Mock() mock_world.worker_pos = [(3, 1)] mock_world.get.return_value = Tile(wall=True, dock=False, worker=False, box=False) engine = GameEngine() engine.move(Dir.RT, mock_world) assert mock_world.move_worker.called == False
mock_world = Mock() mock_world.worker_pos = [(2, 1)] mock_world.get.side_effect = [ Tile(wall=False, dock=False, worker=False, box=True), Tile(wall=False, dock=False, worker=False, box=False) ] engine = GameEngine() engine.move(Dir.RT, mock_world) mock_world.push_box.assert_called_with((3, 1), (4, 1)) mock_world.move_worker.assert_called_with((3, 1))
def __init__(self): self._screen = None self._images = {} self._done = False pygame.init() pygame.display.set_caption("Sokoban!")
>>> import sokoban >>> sokoban.pygame <module ...> >>> mpygame = Mock() >>> sokoban.pygame = mpygame
>>> view = sokoban.GameView() >>> mpygame.init.called True >>> mpygame.display.set_caption.call_args_list [call("Sokoban!")]
>>> patcher = patch("sokoban.pygame") >>> mpygame = patcher.start() >>> import sokoban >>> sokoban.pygame <MagicMock ...> >>> patcher.stop() >>> sokoban.pygame <module ...>
>>> mpygame = Mock() >>> patcher = patch("sokoban.pygame", mpygame)
from unittest.mock import patch from sokoban import GameView def test_set_window_title(): patcher = patch("sokoban.pygame") mock_pygame = patcher.start() view = GameView() mock_pygame.display.set_caption.assert_called_with("Sokoban!") patcher.stop()
def load_levels(): """Returns levels loaded from a JSON file.""" try: return json.load(xopen("levels.json")) except (OSError, IOError, ValueError): print("sokoban: loading levels failed!", file=sys.stderr) exit(1)
from unittest.mock import Mock, patch from io import StringIO from sokoban import load_levels def test_load_levels_success(): mock_open = Mock() mock_open.return_value = StringIO("[]") patcher = patch("sokoban.xopen", mock_open) patcher.start() levels = load_levels() assert levels == [] patcher.stop()
def test_load_levels_invalid_json(): mock_open = Mock() mock_open.return_value = StringIO("!!!") patcher = patch("sokoban.xopen", mock_open) patcher.start() pytest.raises(SystemExit, load_levels) patcher.stop()
def test_load_levels_os_error(): mock_open = Mock() mock_open.side_effect = OSError patcher = patch("sokoban.xopen", mock_open) patcher.start() pytest.raises(SystemExit, load_levels) patcher.stop()