Next, I’m going to add some unit tests for the GameCli class. The primary thing this class currently does is print output. To make testing a little easier, I’ll define a class that is in charge of printing instead of printing in the GameCli.
import os
class ViewManager:
def clear(self):
os.system('cls' if os.name == 'nt' else 'clear')
def display(self, text):
print(text)
It will be pretty simple and also control clearing the terminal as well.
Then I’ll replace all prints with the view manager. e.g.
def run(self):
'''Parses and applies user provided commands to the game'''
for user_input in self.user_inputs:
(action_type, action_info) = parse_user_input(user_input)
self.view_manager.clear()
if action_type == UserActionType.SHOW:
self._display_board_layout()
elif action_type == UserActionType.ATTACK:
(row, col) = action_info
self._attack(row, col)
elif action_type == UserActionType.EXIT:
self.view_manager.display('Ending game')
return
else:
self._display_invalid_user_input_message()
if self.game.game_over():
break
self._display_board_layout()
self.view_manager.display('Game is over')
I can now define some simple tests. The first test just verifies the high-level logic. i.e. if there are no user inputs, the game displays the board and ends.
def test_game_cli(self):
game = Mock()
view_manager = Mock()
cli = GameCli([], game, view_manager)
game.get_board_layout.return_value = []
cli.run()
view_manager.display.assert_has_calls([call(''), call('Game is over')])
Next, I’ll test the show command.
def test_game_cli_show(self):
game = Mock()
view_manager = Mock()
user_inputs = ['show']
cli = GameCli(user_inputs, game, view_manager)
hit = CoordInfo(CoordStatus.SHIP_HIT, 'A')
missed = CoordInfo(CoordStatus.SHIP_MISSED, None)
empty = CoordInfo(CoordStatus.EMPTY, None)
game.get_board_layout.return_value = [
[hit, missed],
[empty, empty]
]
cli.run()
view_manager.display.assert_has_calls([call('A,x\n , '), call('Game is over')])
Notice I don’t need to use a real game or view_manager. With mocks, I can specify how they should behave. i.e. when get_board_layout is called, it will return a specific board layout. Then I can check that the board is converted to the expected output format is the provided parameter to the display method.
I’ll add some other tests to verify some of the other expected input/outputs.
def test_game_cli_attack(self):
game = Mock()
view_manager = Mock()
user_inputs = ['a1']
cli = GameCli(user_inputs, game, view_manager)
game.get_board_layout.return_value = []
game.attack.return_value = AttackResult.SUCCESS
cli.run()
game.attack.assert_called_with('a', '1')
view_manager.display.assert_has_calls([call('Ship hit!'), call(''), call('Game is over')])
def test_invalid_input(self):
game = Mock()
view_manager = Mock()
user_inputs = ['blah']
cli = GameCli(user_inputs, game, view_manager)
game.get_board_layout.return_value = []
cli.run()
view_manager.display.assert_has_calls([call('Invalid user input'), call(''), call('Game is over')])
Project changeset.
Leave a comment