test_rover.py 5.02 KB
import os.path
import random
import unittest

from rover import (Rover, OutOfBoundaryException, InvalidHeading,
                   MoveOutOfBounds, InvalidTurn)


class SingleRoverTestCase(unittest.TestCase):
    """
    Simple testcase for a SingleRover object, for developmental ease
    """

    def setUp(self):
        self.rover_input = {
            'start': '1 2 N',
            'route': 'LMLMLMLMM',
            'boundaries': '5 5'
        }
        self.rover_output = {
            'result': '1 3 N',
            'boundaries': [[0, 0], [5, 5]],
            'start_coordinates': [1, 2],
            'coordinates': [1, 3]
        }
        self.rover = Rover(**self.rover_input)
        self.valid_turns = {
            'L': {'N': 'W', 'W': 'S', 'S': 'E', 'E': 'N'},
            'R': {'N': 'E', 'E': 'S', 'S': 'W', 'W': 'N'}
        }

    def test_set_boundaries(self):
        self.rover.set_boundaries('9 9')
        self.assertEqual(self.rover.boundaries, [[0, 0], [9, 9]])

    def test_set_non_int_boundaries(self):
        with self.assertRaises(ValueError):
            self.rover.set_boundaries('not-integer-boundaries')

        # check that boundaries haven't changed
        self.assertEqual(self.rover.boundaries, self.rover_output['boundaries'])

    def test_set_coordinates(self):
        self.rover.set_coordinates(self.rover_output['coordinates'])
        self.assertEqual(self.rover.coordinates, self.rover_output['coordinates'])

    def test_set_out_of_boundary_coordinates(self):
        with self.assertRaises(OutOfBoundaryException):
            self.rover.set_coordinates(['12', '12'])

        # check that coordinates haven't changed
        self.assertEqual(self.rover.coordinates, self.rover_output['start_coordinates'])

    def test_set_heading(self):
        self.rover.set_heading('S')
        self.assertEqual(self.rover.heading, 'S')

    def test_set_invalid_heading(self):
        with self.assertRaises(InvalidHeading):
            self.rover.set_heading('invalid-heading')
        self.assertEqual(self.rover.heading, self.rover_input['start'][-1])

    def test_move(self):
        # starting position is "1 2 N", so if it moves a tile, the coordinates
        # should be "1 3 N"
        self.rover.move()
        self.assertEqual(self.rover.coordinates, self.rover_output['coordinates'])

    def test_out_of_bounds_move(self):
        # doing a random move would be nice
        self.rover.set_coordinates(self.rover_output['boundaries'][1])
        self.rover.set_heading('N')
        with self.assertRaises(MoveOutOfBounds):
            self.rover.move()

    def test_turning_move(self):
        # pick a random starting direction
        heading = random.choice(self.rover.headings)
        self.rover.set_heading(heading)

        # pick a random direction to turn to
        turning_direction = random.choice(self.valid_turns.keys())
        self.rover.turn(turning_direction)

        self.assertEqual(self.rover.heading, self.valid_turns[turning_direction][heading])

    def test_invalid_turning_direction(self):
        with self.assertRaises(InvalidTurn):
            self.rover.turn('G')
        self.assertEqual(self.rover.heading, self.rover_input['start'][-1])

    def test_execute_route(self):
        self.rover.execute_route()
        position = '{} {} {}'.format(self.rover.coordinates[0],
                                     self.rover.coordinates[1],
                                     self.rover.heading)
        self.assertEqual(position, self.rover_output['result'])


class CatawikiRoversTestCase(unittest.TestCase):
    """
    This is a weird testcase in Python terms, as it parses the catawiki input
    file to generate the appropriate output. The SingleRoverTestCase above
    does an actual unittest.
    """

    def setUp(self):
        self.assertTrue(os.path.exists('input.txt'), msg='Inputfile "input.txt"'
                                                         ' does not exist')
        self.instructions = []
        self.rovers = []

        with open('input.txt', 'r') as f:
            self.boundaries = f.readline()

            for line in f:
                line = line.strip()
                if line:
                    self.instructions.append(line)

        it = iter(self.instructions)
        for start, route in zip(it, it):
            self.rovers.append(Rover(start=start, route=route,
                                     boundaries=self.boundaries))

    def test_instructions(self):
        # There should be instructions
        self.assertNotEqual(len(self.instructions), 0, msg='No instructions '
                                                           'found')
        # The number of instructions should be even (i.e. every Rover should
        # have a starting position and some movement instructions)
        self.assertEqual(len(self.instructions) % 2, 0, msg='Missing some '
                                                            'instructions')


if __name__ == '__main__':
    suite = unittest.TestLoader().loadTestsFromTestCase(SingleRoverTestCase)
    unittest.TextTestRunner(verbosity=2).run(suite)