296 lines
8.6 KiB
Python
296 lines
8.6 KiB
Python
"""All tests for the Sudoku Solver."""
|
|
|
|
import unittest
|
|
import copy
|
|
from typing import List
|
|
from main import solve_board
|
|
from board import Board
|
|
from square import Square
|
|
|
|
|
|
class TestMain(unittest.TestCase):
|
|
"""TestCase for the main file."""
|
|
|
|
def setUp(self):
|
|
self.strings: List[str] = [(
|
|
".2..785..\n"
|
|
"4...52...\n"
|
|
"..1..3.2.\n"
|
|
".....1...\n"
|
|
"7348.526.\n"
|
|
"2.9.67..5\n"
|
|
".687..3.9\n"
|
|
"342.1.7..\n"
|
|
"19..86..2\n"
|
|
), (
|
|
"..1..6435\n"
|
|
".....1...\n"
|
|
".5.47.98.\n"
|
|
"..2.8.7.9\n"
|
|
"..87..612\n"
|
|
".64.1..5.\n"
|
|
"91.3428.7\n"
|
|
".27....9.\n"
|
|
"6.3.9.5..\n"
|
|
), (
|
|
".....6...\n"
|
|
"..2.35.8.\n"
|
|
"....2..13\n"
|
|
".2....19.\n"
|
|
"93....7..\n"
|
|
"5....4...\n"
|
|
"1......7.\n"
|
|
".7...142.\n"
|
|
".9.4.3...\n"
|
|
), (
|
|
"...82....\n"
|
|
"...6.9..1\n"
|
|
"9.4..1...\n"
|
|
".9.7..28.\n"
|
|
".......57\n"
|
|
"7.5......\n"
|
|
".78962..5\n"
|
|
".2..8.7.3\n"
|
|
".6..74.2.\n"
|
|
), (
|
|
"4......1.\n"
|
|
".15.4.6..\n"
|
|
".....7...\n"
|
|
"...21...8\n"
|
|
"...734..2\n"
|
|
".3..8....\n"
|
|
"8.19...45\n"
|
|
".4....7..\n"
|
|
"2....3...\n"
|
|
), (
|
|
"3........\n"
|
|
".6...791.\n"
|
|
".....325.\n"
|
|
".19.645..\n"
|
|
".......6.\n"
|
|
".85......\n"
|
|
"...8.....\n"
|
|
"..1..97..\n"
|
|
"47..5....\n"
|
|
)]
|
|
|
|
def test_iterate_over_various_board_difficulties(self):
|
|
"""Run the solver on different boards."""
|
|
|
|
for string in self.strings[:3]:
|
|
self.assertIsInstance(string, str)
|
|
solve_board(string)
|
|
|
|
|
|
class TestBoard(unittest.TestCase):
|
|
"""TestCase for Board."""
|
|
|
|
def setUp(self):
|
|
self.string = (
|
|
".2..785..\n"
|
|
"4...52...\n"
|
|
"..1..3.2.\n"
|
|
".....1...\n"
|
|
"7348.526.\n"
|
|
"2.9.67..5\n"
|
|
".687..3.9\n"
|
|
"342.1.7..\n"
|
|
"19..86..2\n"
|
|
)
|
|
|
|
self.board = Board(self.string, debug=False)
|
|
|
|
def test_lines_are_correctly_initialized(self):
|
|
"""There should be 9 lines, each a set of its values."""
|
|
|
|
self.assertIsInstance(self.board.lines, list)
|
|
self.assertEqual(len(self.board.lines), 9)
|
|
|
|
for line in self.board.lines:
|
|
self.assertIsInstance(line, set)
|
|
self.assertNotEqual(len(line), 0)
|
|
|
|
def test_columns_are_correctly_initialized(self):
|
|
"""There should be 9 columns, each a set of its values."""
|
|
|
|
self.assertIsInstance(self.board.columns, list)
|
|
self.assertEqual(len(self.board.columns), 9)
|
|
|
|
for column in self.board.columns:
|
|
self.assertIsInstance(column, set)
|
|
self.assertNotEqual(len(column), 0)
|
|
|
|
def test_grids_are_correctly_initialized(self):
|
|
"""There should be 9 grids, each a set of its values."""
|
|
|
|
self.assertIsInstance(self.board.grids, list)
|
|
self.assertEqual(len(self.board.grids), 9)
|
|
|
|
for grid in self.board.grids:
|
|
self.assertIsInstance(grid, set)
|
|
self.assertNotEqual(len(grid), 0)
|
|
|
|
def test_squares_are_correctly_initialized(self):
|
|
"""There should be 81 squares, each a Square object."""
|
|
|
|
self.assertIsInstance(self.board.squares, list)
|
|
self.assertEqual(len(self.board.squares), 81)
|
|
|
|
for square in self.board.squares:
|
|
self.assertIsInstance(square, Square)
|
|
self.assertIsInstance(square.values, set)
|
|
self.assertLess(len(square.values), 2)
|
|
|
|
def test_compute_possible_values(self):
|
|
"""For each square, values are added if the square was empty.
|
|
|
|
If the square was not empty, it had only one value. Values should not
|
|
be added if the square was not empty.
|
|
"""
|
|
|
|
empty_squares = 0
|
|
squares_with_one_value = 0
|
|
|
|
for square in self.board.squares:
|
|
if len(square.values) == 0:
|
|
empty_squares += 1
|
|
# We already tested that if len(square.values) != 0 it must be 1
|
|
else:
|
|
squares_with_one_value += 1
|
|
|
|
old_squares = copy.deepcopy(self.board.squares)
|
|
|
|
self.board.compute_possible_values()
|
|
|
|
new_squares = copy.deepcopy(self.board.squares)
|
|
|
|
self.assertEqual(len(old_squares), 81)
|
|
self.assertEqual(len(new_squares), 81)
|
|
|
|
for i in range(81):
|
|
if len(old_squares[i].values) != 1:
|
|
self.assertLess(
|
|
len(old_squares[i].values), len(new_squares[i].values)
|
|
)
|
|
else:
|
|
self.assertEqual(
|
|
len(old_squares[i].values), len(new_squares[i].values)
|
|
)
|
|
|
|
squares_with_more_than_one_value = 0
|
|
for square in self.board.squares:
|
|
self.assertGreater(len(square.values), 0)
|
|
if len(square.values) > 1:
|
|
squares_with_more_than_one_value += 1
|
|
|
|
# Maybe only one value was computed.
|
|
self.assertLessEqual(
|
|
squares_with_more_than_one_value, empty_squares
|
|
)
|
|
|
|
def test_get_square(self):
|
|
"""Searching for a square returns the correct square."""
|
|
|
|
valid_square, square_nb = self.board.get_square(3, 4)
|
|
self.assertEqual(valid_square.coordinates, (3, 4))
|
|
self.assertIn(square_nb, range(81))
|
|
|
|
valid_square, square_nb = self.board.get_square(8, 0)
|
|
self.assertEqual(valid_square.coordinates, (8, 0))
|
|
self.assertIn(square_nb, range(81))
|
|
|
|
valid_square, square_nb = self.board.get_square(0, 0)
|
|
self.assertEqual(valid_square.coordinates, (0, 0))
|
|
self.assertIn(square_nb, range(81))
|
|
|
|
invalid_square, square_nb = self.board.get_square(12, 5)
|
|
self.assertIsNone(invalid_square)
|
|
self.assertIsNone(square_nb)
|
|
|
|
invalid_square, square_nb = self.board.get_square(0, 34)
|
|
self.assertIsNone(invalid_square)
|
|
self.assertIsNone(square_nb)
|
|
|
|
invalid_square, square_nb = self.board.get_square(55, 120)
|
|
self.assertIsNone(invalid_square)
|
|
self.assertIsNone(square_nb)
|
|
|
|
invalid_square, square_nb = self.board.get_square(-1, 2)
|
|
self.assertIsNone(invalid_square)
|
|
self.assertIsNone(square_nb)
|
|
|
|
def test_update_board(self):
|
|
"""Updated board is consistent with board.string."""
|
|
|
|
old_string: str = self.board.string
|
|
|
|
self.board.update_board()
|
|
self.assertEqual(old_string, self.board.string)
|
|
|
|
old_string = old_string.replace('\n', '')
|
|
self.assertEqual(len(old_string), 81)
|
|
|
|
for square_nb, square in enumerate(self.board.squares):
|
|
if len(square.values) == 1:
|
|
self.assertEqual(
|
|
str(square.values)[2:-2],
|
|
old_string[square_nb]
|
|
)
|
|
|
|
def test_update_string(self):
|
|
"""Updated board.string is consistent with board."""
|
|
|
|
old_string: str = self.board.string
|
|
|
|
self.board.compute_possible_values()
|
|
|
|
new_string: str = self.board.update_string()
|
|
|
|
self.assertEqual(len(self.string), 90)
|
|
self.assertEqual(len(old_string), 90)
|
|
self.assertEqual(len(new_string), 90)
|
|
self.assertEqual(len(self.board.string), 90)
|
|
self.assertIs(self.board.string, new_string)
|
|
|
|
empty_squares = 0
|
|
for square in self.board.squares:
|
|
if len(square.values) != 1:
|
|
empty_squares += 1
|
|
|
|
self.assertEqual(empty_squares, self.board.string.count('.'))
|
|
|
|
|
|
class TestSquare(unittest.TestCase):
|
|
"""TestCase for Square."""
|
|
|
|
def setUp(self):
|
|
content = '.'
|
|
self.board = [
|
|
Square(line, column, content)
|
|
for line in range(9)
|
|
for column in range(9)
|
|
]
|
|
|
|
self.assertEqual(len(self.board), 81)
|
|
|
|
def test_grid_is_correctly_computed(self):
|
|
"""Square.grid is correctly computed for all values."""
|
|
|
|
correct_grids = (
|
|
"000111222"
|
|
"000111222"
|
|
"000111222"
|
|
"333444555"
|
|
"333444555"
|
|
"333444555"
|
|
"666777888"
|
|
"666777888"
|
|
"666777888"
|
|
)
|
|
|
|
for i, square in enumerate(self.board):
|
|
self.assertEqual(square.grid, int(correct_grids[i]))
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|