diff --git a/exercises/practice/perfect-numbers/.docs/instructions.append.md b/exercises/practice/perfect-numbers/.docs/instructions.append.md new file mode 100644 index 0000000000..6ed3e72fcc --- /dev/null +++ b/exercises/practice/perfect-numbers/.docs/instructions.append.md @@ -0,0 +1,14 @@ +# Instructions append + +## Exception messages + +Sometimes it is necessary to [raise an exception](https://docs.python.org/3/tutorial/errors.html#raising-exceptions). When you do this, you should always include a **meaningful error message** to indicate what the source of the error is. This makes your code more readable and helps significantly with debugging. For situations where you know that the error source will be a certain type, you can choose to raise one of the [built in error types](https://docs.python.org/3/library/exceptions.html#base-classes), but should still include a meaningful message. + +This particular exercise requires that you use the [raise statement](https://docs.python.org/3/reference/simple_stmts.html#the-raise-statement) to "throw" a `ValueError` if the `classify()` function is passed a number that is not a _positive integer_. The tests will only pass if you both `raise` the `exception` and include a message with it. + +To raise a `ValueError` with a message, write the message as an argument to the `exception` type: + +```python +# if a number to be classified is less than 1. +raise ValueError("Classification is only possible for positive integers.") +``` diff --git a/exercises/practice/perfect-numbers/.meta/example.py b/exercises/practice/perfect-numbers/.meta/example.py index f2bc2d5a57..cc8a63008c 100644 --- a/exercises/practice/perfect-numbers/.meta/example.py +++ b/exercises/practice/perfect-numbers/.meta/example.py @@ -1,23 +1,33 @@ -def divisor_generator(n): - ''' Returns an unordered list of divisors for n (1 < n). ''' - for i in range(2, int(n ** 0.5) + 1): - if n % i == 0: - yield i - if i * i != n: - yield n // i +def divisor_generator(number): + """Returns an unordered list of divisors for n (1 < number). -def classify(n): - ''' A perfect number equals the sum of its positive divisors. ''' - if n <= 0: - raise ValueError("Classification is only possible" + - " for positive whole numbers.") + :param number: int a positive integer + :return: list of int divisors + """ - aliquot_sum = sum(divisor_generator(n)) + (1 if n > 1 else 0) + for index in range(2, int(number ** 0.5) + 1): + if number % index == 0: + yield index + if index * index != number: + yield number // index - if aliquot_sum < n: + +def classify(number): + """ A perfect number equals the sum of its positive divisors. + + :param number: int a positive integer + :return: str the classification of the input integer + """ + + if number <= 0: + raise ValueError("Classification is only possible for positive integers.") + + aliquot_sum = sum(divisor_generator(number)) + (1 if number > 1 else 0) + + if aliquot_sum < number: return "deficient" - elif aliquot_sum == n: + elif aliquot_sum == number: return "perfect" else: return "abundant" diff --git a/exercises/practice/perfect-numbers/.meta/template.j2 b/exercises/practice/perfect-numbers/.meta/template.j2 index 20901ae8b4..f7bdd525c7 100644 --- a/exercises/practice/perfect-numbers/.meta/template.j2 +++ b/exercises/practice/perfect-numbers/.meta/template.j2 @@ -3,8 +3,10 @@ {%- set input = case["input"] -%} def test_{{ case["description"] | to_snake }}(self): {%- if case["expected"]["error"] is string %} - with self.assertRaisesWithMessage(ValueError): + with self.assertRaises(ValueError) as err: {{ case["property"] | to_snake }}({{input["number"]}}) + self.assertEqual(type(err.exception), ValueError) + self.assertEqual(err.exception.args[0], "{{ case["expected"]["error"] }}") {% else %} self.assertIs( {{ case["property"] | to_snake }}({{input["number"]}}), @@ -20,5 +22,3 @@ class {{ case["description"] | camel_case }}Test(unittest.TestCase): {{ test_case(subcase) }} {% endfor %} {% endfor %} - -{{ macros.footer() }} diff --git a/exercises/practice/perfect-numbers/perfect_numbers.py b/exercises/practice/perfect-numbers/perfect_numbers.py index 0377abc50c..eb093dd6c9 100644 --- a/exercises/practice/perfect-numbers/perfect_numbers.py +++ b/exercises/practice/perfect-numbers/perfect_numbers.py @@ -1,2 +1,7 @@ def classify(number): + """ A perfect number equals the sum of its positive divisors. + + :param number: int a positive integer + :return: str the classification of the input integer + """ pass diff --git a/exercises/practice/perfect-numbers/perfect_numbers_test.py b/exercises/practice/perfect-numbers/perfect_numbers_test.py index 9139a44a9e..783f3c6ad5 100644 --- a/exercises/practice/perfect-numbers/perfect_numbers_test.py +++ b/exercises/practice/perfect-numbers/perfect_numbers_test.py @@ -48,17 +48,19 @@ def test_edge_case_no_factors_other_than_itself_is_classified_correctly(self): class InvalidInputsTest(unittest.TestCase): def test_zero_is_rejected_as_it_is_not_a_positive_integer(self): - with self.assertRaisesWithMessage(ValueError): + with self.assertRaises(ValueError) as err: classify(0) + self.assertEqual(type(err.exception), ValueError) + self.assertEqual( + err.exception.args[0], + "Classification is only possible for positive integers.", + ) def test_negative_integer_is_rejected_as_it_is_not_a_positive_integer(self): - with self.assertRaisesWithMessage(ValueError): + with self.assertRaises(ValueError) as err: classify(-1) - - # Utility functions - def assertRaisesWithMessage(self, exception): - return self.assertRaisesRegex(exception, r".+") - - -if __name__ == "__main__": - unittest.main() + self.assertEqual(type(err.exception), ValueError) + self.assertEqual( + err.exception.args[0], + "Classification is only possible for positive integers.", + ) diff --git a/exercises/practice/phone-number/.docs/instructions.append.md b/exercises/practice/phone-number/.docs/instructions.append.md new file mode 100644 index 0000000000..1001c9dc7e --- /dev/null +++ b/exercises/practice/phone-number/.docs/instructions.append.md @@ -0,0 +1,38 @@ +# Instructions append + +## Exception messages + +Sometimes it is necessary to [raise an exception](https://docs.python.org/3/tutorial/errors.html#raising-exceptions). When you do this, you should always include a **meaningful error message** to indicate what the source of the error is. This makes your code more readable and helps significantly with debugging. For situations where you know that the error source will be a certain type, you can choose to raise one of the [built in error types](https://docs.python.org/3/library/exceptions.html#base-classes), but should still include a meaningful message. + +This particular exercise requires that you use the [raise statement](https://docs.python.org/3/reference/simple_stmts.html#the-raise-statement) to "throw" multiple `ValueErrors` if the `PhoneNumber()` class constructor is passed a number that is not a _valid phone number_. This includes errors for when area code or exchange codes are invalid, when the number has too many (or too few) digits, and for when punctuation or letters are given as input. The tests will only pass if you both `raise` the `exception` and include a message with it. + +To raise a `ValueError` with a message, write the message as an argument to the `exception` type: + +```python +# if a phone number has less than 10 digits. +raise ValueError("incorrect number of digits") + +# if a phone number has more than 11 digits. +raise ValueError("more than 11 digits") + +# if a phone number has 11 digits, but starts with a number other than 1. +raise ValueError("11 digits must start with 1") + +# if a phone number has an exchange code that starts with 0. +raise ValueError("exchange code cannot start with zero") + +# if a phone number has an exchange code that starts with 1. +raise ValueError("exchange code cannot start with one") + +# if a phone number has an area code that starts with 0. +raise ValueError("area code cannot start with zero") + +# if a phone number has an area code that starts with 1. +raise ValueError("area code cannot start with one") + +# if a phone number has punctuation in place of some digits. +raise ValueError("punctuations not permitted") + +# if a phone number has letters in place of some digits. +raise ValueError("letters not permitted") +``` diff --git a/exercises/practice/phone-number/.meta/example.py b/exercises/practice/phone-number/.meta/example.py index c2762ccc3c..4d9b7f8d61 100644 --- a/exercises/practice/phone-number/.meta/example.py +++ b/exercises/practice/phone-number/.meta/example.py @@ -1,4 +1,5 @@ import re +from string import punctuation class PhoneNumber: @@ -9,20 +10,42 @@ def __init__(self, number): self.subscriber_number = self.number[-4:] def pretty(self): - return "({})-{}-{}".format( - self.area_code, self.exchange_code, self.subscriber_number - ) + return f"({self.area_code})-{self.exchange_code}-{self.subscriber_number}" def _clean(self, number): - return self._normalize(re.sub(r"[^\d]", "", number)) + preprocess = re.sub(r"[() +-.]", "", number) + + if any(item for item in preprocess if item.isalpha()): + raise ValueError("letters not permitted") + + if any(item for item in preprocess if item in punctuation): + raise ValueError("punctuations not permitted") + + return self._normalize(preprocess) def _normalize(self, number): + if len(number) < 10: + raise ValueError("incorrect number of digits") + + if len(number) > 11: + raise ValueError("more than 11 digits") + if len(number) == 10 or len(number) == 11 and number.startswith("1"): - valid = number[-10] in "23456789" and number[-7] in "23456789" + if number[-10] == "0": + raise ValueError("area code cannot start with zero") + elif number[-10] == "1": + raise ValueError("area code cannot start with one") + elif number[-7] == "0": + raise ValueError("exchange code cannot start with zero") + elif number[-7] == "1": + raise ValueError("exchange code cannot start with one") + else: + valid = number[-10] in "23456789" and number[-7] in "23456789" + else: valid = False + if number[0] in "023456789": + raise ValueError("11 digits must start with 1") if valid: return number[-10:] - else: - raise ValueError("{} is not a valid phone number".format(number)) diff --git a/exercises/practice/phone-number/.meta/template.j2 b/exercises/practice/phone-number/.meta/template.j2 index b4370784f3..2b2826217c 100644 --- a/exercises/practice/phone-number/.meta/template.j2 +++ b/exercises/practice/phone-number/.meta/template.j2 @@ -6,8 +6,10 @@ class {{ class }}Test(unittest.TestCase): {% for case in cases -%} def test_{{ case["description"] | to_snake }}(self): {% if "error" in case["expected"] -%} - with self.assertRaisesWithMessage(ValueError): + with self.assertRaises(ValueError) as err: {{ class }}("{{ case["input"]["phrase"] }}") + self.assertEqual(type(err.exception), ValueError) + self.assertEqual(err.exception.args[0], "{{ case["expected"]["error"] }}") {% else -%} number = {{ class }}("{{ case["input"]["phrase"] }}").number self.assertEqual(number, "{{ case["expected"] }}") @@ -26,5 +28,3 @@ class {{ class }}Test(unittest.TestCase): number = {{ class }}("{{ case["input"]["phrase"] }}") self.assertEqual(number.{{ case["property"] }}{{ method }}, "{{ case["expected"] }}") {% endfor %} - -{{ macros.footer() }} diff --git a/exercises/practice/phone-number/phone_number_test.py b/exercises/practice/phone-number/phone_number_test.py index 8fb616e390..f16800647c 100644 --- a/exercises/practice/phone-number/phone_number_test.py +++ b/exercises/practice/phone-number/phone_number_test.py @@ -21,12 +21,16 @@ def test_cleans_numbers_with_multiple_spaces(self): self.assertEqual(number, "2234567890") def test_invalid_when_9_digits(self): - with self.assertRaisesWithMessage(ValueError): + with self.assertRaises(ValueError) as err: PhoneNumber("123456789") + self.assertEqual(type(err.exception), ValueError) + self.assertEqual(err.exception.args[0], "incorrect number of digits") def test_invalid_when_11_digits_does_not_start_with_a_1(self): - with self.assertRaisesWithMessage(ValueError): + with self.assertRaises(ValueError) as err: PhoneNumber("22234567890") + self.assertEqual(type(err.exception), ValueError) + self.assertEqual(err.exception.args[0], "11 digits must start with 1") def test_valid_when_11_digits_and_starting_with_1(self): number = PhoneNumber("12234567890").number @@ -37,48 +41,70 @@ def test_valid_when_11_digits_and_starting_with_1_even_with_punctuation(self): self.assertEqual(number, "2234567890") def test_invalid_when_more_than_11_digits(self): - with self.assertRaisesWithMessage(ValueError): + with self.assertRaises(ValueError) as err: PhoneNumber("321234567890") + self.assertEqual(type(err.exception), ValueError) + self.assertEqual(err.exception.args[0], "more than 11 digits") def test_invalid_with_letters(self): - with self.assertRaisesWithMessage(ValueError): + with self.assertRaises(ValueError) as err: PhoneNumber("123-abc-7890") + self.assertEqual(type(err.exception), ValueError) + self.assertEqual(err.exception.args[0], "letters not permitted") def test_invalid_with_punctuations(self): - with self.assertRaisesWithMessage(ValueError): + with self.assertRaises(ValueError) as err: PhoneNumber("123-@:!-7890") + self.assertEqual(type(err.exception), ValueError) + self.assertEqual(err.exception.args[0], "punctuations not permitted") def test_invalid_if_area_code_starts_with_0(self): - with self.assertRaisesWithMessage(ValueError): + with self.assertRaises(ValueError) as err: PhoneNumber("(023) 456-7890") + self.assertEqual(type(err.exception), ValueError) + self.assertEqual(err.exception.args[0], "area code cannot start with zero") def test_invalid_if_area_code_starts_with_1(self): - with self.assertRaisesWithMessage(ValueError): + with self.assertRaises(ValueError) as err: PhoneNumber("(123) 456-7890") + self.assertEqual(type(err.exception), ValueError) + self.assertEqual(err.exception.args[0], "area code cannot start with one") def test_invalid_if_exchange_code_starts_with_0(self): - with self.assertRaisesWithMessage(ValueError): + with self.assertRaises(ValueError) as err: PhoneNumber("(223) 056-7890") + self.assertEqual(type(err.exception), ValueError) + self.assertEqual(err.exception.args[0], "exchange code cannot start with zero") def test_invalid_if_exchange_code_starts_with_1(self): - with self.assertRaisesWithMessage(ValueError): + with self.assertRaises(ValueError) as err: PhoneNumber("(223) 156-7890") + self.assertEqual(type(err.exception), ValueError) + self.assertEqual(err.exception.args[0], "exchange code cannot start with one") def test_invalid_if_area_code_starts_with_0_on_valid_11_digit_number(self): - with self.assertRaisesWithMessage(ValueError): + with self.assertRaises(ValueError) as err: PhoneNumber("1 (023) 456-7890") + self.assertEqual(type(err.exception), ValueError) + self.assertEqual(err.exception.args[0], "area code cannot start with zero") def test_invalid_if_area_code_starts_with_1_on_valid_11_digit_number(self): - with self.assertRaisesWithMessage(ValueError): + with self.assertRaises(ValueError) as err: PhoneNumber("1 (123) 456-7890") + self.assertEqual(type(err.exception), ValueError) + self.assertEqual(err.exception.args[0], "area code cannot start with one") def test_invalid_if_exchange_code_starts_with_0_on_valid_11_digit_number(self): - with self.assertRaisesWithMessage(ValueError): + with self.assertRaises(ValueError) as err: PhoneNumber("1 (223) 056-7890") + self.assertEqual(type(err.exception), ValueError) + self.assertEqual(err.exception.args[0], "exchange code cannot start with zero") def test_invalid_if_exchange_code_starts_with_1_on_valid_11_digit_number(self): - with self.assertRaisesWithMessage(ValueError): + with self.assertRaises(ValueError) as err: PhoneNumber("1 (223) 156-7890") + self.assertEqual(type(err.exception), ValueError) + self.assertEqual(err.exception.args[0], "exchange code cannot start with one") # Additional tests for this track def test_area_code(self): @@ -92,11 +118,3 @@ def test_pretty_print(self): def test_pretty_print_with_full_us_phone_number(self): number = PhoneNumber("12234567890") self.assertEqual(number.pretty(), "(223)-456-7890") - - # Utility functions - def assertRaisesWithMessage(self, exception): - return self.assertRaisesRegex(exception, r".+") - - -if __name__ == "__main__": - unittest.main() diff --git a/exercises/practice/pov/.docs/instructions.append.md b/exercises/practice/pov/.docs/instructions.append.md new file mode 100644 index 0000000000..83ab12475c --- /dev/null +++ b/exercises/practice/pov/.docs/instructions.append.md @@ -0,0 +1,17 @@ +# Instructions append + +## Exception messages + +Sometimes it is necessary to [raise an exception](https://docs.python.org/3/tutorial/errors.html#raising-exceptions). When you do this, you should always include a **meaningful error message** to indicate what the source of the error is. This makes your code more readable and helps significantly with debugging. For situations where you know that the error source will be a certain type, you can choose to raise one of the [built in error types](https://docs.python.org/3/library/exceptions.html#base-classes), but should still include a meaningful message. + +This particular exercise requires that you use the [raise statement](https://docs.python.org/3/reference/simple_stmts.html#the-raise-statement) to "throw" multiple `ValueErrors` if the `Tree()` class is passed a tree that cannot be reoriented, or a path cannot be found between a `start node` and an `end node`. The tests will only pass if you both `raise` the `exception` and include a message with it. + +To raise a `ValueError` with a message, write the message as an argument to the `exception` type: + +```python +# when a tree cannot be oriented to a new node POV +raise ValueError("Tree could not be reoriented") + +#when a path cannot be found between a start and end node on the tree. +raise ValueError("No path found") +``` \ No newline at end of file diff --git a/exercises/practice/pov/.meta/example.py b/exercises/practice/pov/.meta/example.py index 5c17af1d0f..cd0608480f 100644 --- a/exercises/practice/pov/.meta/example.py +++ b/exercises/practice/pov/.meta/example.py @@ -7,7 +7,7 @@ def __init__(self, label, children=[]): self.children = children def __dict__(self): - return {self.label: [c.__dict__() for c in sorted(self.children)]} + return {self.label: [member.__dict__() for member in sorted(self.children)]} def __str__(self, indent=None): return dumps(self.__dict__(), indent=indent) @@ -21,11 +21,11 @@ def __eq__(self, other): def __iter__(self): yield self.label for child in self.children: - for gchild in child: - yield gchild + for grandchild in child: + yield grandchild def dup(self): - return Tree(self.label, [c.dup() for c in self.children]) + return Tree(self.label, [member.dup() for member in self.children]) def add(self, other): tree = self.dup() @@ -44,21 +44,28 @@ def remove(self, node): def from_pov(self, from_node): stack = [self] visited = set() + while stack: tree = stack.pop(0) if tree.label in visited: continue + visited.add(tree.label) if from_node == tree.label: return tree + for child in tree.children: stack.append(child.add(tree.remove(child.label))) + raise ValueError("Tree could not be reoriented") + + def path_to(self, from_node, to_node): reordered = self.from_pov(from_node) stack = reordered.children path = [from_node] + while path[-1] != to_node: try: tree = stack.pop() diff --git a/exercises/practice/pov/.meta/template.j2 b/exercises/practice/pov/.meta/template.j2 index 8669f460dd..aee59684a1 100644 --- a/exercises/practice/pov/.meta/template.j2 +++ b/exercises/practice/pov/.meta/template.j2 @@ -15,8 +15,10 @@ expected = {{ write_tree(case["expected"]) }} self.assertTreeEquals(tree.{{ case["property"] | to_snake }}("{{ case["input"]["from"] }}"), expected) {% else -%} - with self.assertRaisesWithMessage(ValueError): + with self.assertRaises(ValueError) as err: tree.{{ case["property"] | to_snake }}("{{ case["input"]["from"] }}") + self.assertEqual(type(err.exception), ValueError) + self.assertEqual(err.exception.args[0], "Tree could not be reoriented") {% endif -%} {% endmacro -%} @@ -25,8 +27,14 @@ expected = {{ case["expected"] }} self.assertEqual(tree.{{ case["property"] | to_snake }}("{{ case["input"]["from"] }}", "{{ case["input"]["to"]}}"), expected) {% else -%} - with self.assertRaisesWithMessage(ValueError): + with self.assertRaises(ValueError) as err: tree.{{ case["property"] | to_snake }}("{{ case["input"]["from"] }}", "{{ case["input"]["to"]}}") + self.assertEqual(type(err.exception), ValueError) + {% if "Errors if source does not exist" == case["description"] %} + self.assertEqual(err.exception.args[0], "Tree could not be reoriented") + {% else %} + self.assertEqual(err.exception.args[0], "No path found") + {% endif %} {% endif -%} {% endmacro -%} @@ -52,4 +60,4 @@ class {{ exercise | camel_case }}Test(unittest.TestCase): def assertTreeEquals(self, result, expected): self.assertEqual(result, expected, '{} != {}'.format(result, expected)) -{{ macros.footer(True) }} + diff --git a/exercises/practice/pov/pov_test.py b/exercises/practice/pov/pov_test.py index 5d8832e287..4bbdbaa871 100644 --- a/exercises/practice/pov/pov_test.py +++ b/exercises/practice/pov/pov_test.py @@ -76,8 +76,10 @@ def test_can_reroot_a_complex_tree_with_cousins(self): def test_errors_if_target_does_not_exist_in_a_singleton_tree(self): tree = Tree("x") - with self.assertRaisesWithMessage(ValueError): + with self.assertRaises(ValueError) as err: tree.from_pov("nonexistent") + self.assertEqual(type(err.exception), ValueError) + self.assertEqual(err.exception.args[0], "Tree could not be reoriented") def test_errors_if_target_does_not_exist_in_a_large_tree(self): tree = Tree( @@ -88,8 +90,10 @@ def test_errors_if_target_does_not_exist_in_a_large_tree(self): Tree("sibling-1"), ], ) - with self.assertRaisesWithMessage(ValueError): + with self.assertRaises(ValueError) as err: tree.from_pov("nonexistent") + self.assertEqual(type(err.exception), ValueError) + self.assertEqual(err.exception.args[0], "Tree could not be reoriented") def test_can_find_path_to_parent(self): tree = Tree("parent", [Tree("x"), Tree("sibling")]) @@ -141,8 +145,11 @@ def test_errors_if_destination_does_not_exist(self): Tree("sibling-1"), ], ) - with self.assertRaisesWithMessage(ValueError): + with self.assertRaises(ValueError) as err: tree.path_to("x", "nonexistent") + self.assertEqual(type(err.exception), ValueError) + + self.assertEqual(err.exception.args[0], "No path found") def test_errors_if_source_does_not_exist(self): tree = Tree( @@ -153,17 +160,12 @@ def test_errors_if_source_does_not_exist(self): Tree("sibling-1"), ], ) - with self.assertRaisesWithMessage(ValueError): + with self.assertRaises(ValueError) as err: tree.path_to("nonexistent", "x") + self.assertEqual(type(err.exception), ValueError) + + self.assertEqual(err.exception.args[0], "Tree could not be reoriented") # Custom Utility Functions def assertTreeEquals(self, result, expected): self.assertEqual(result, expected, "{} != {}".format(result, expected)) - - # Utility functions - def assertRaisesWithMessage(self, exception): - return self.assertRaisesRegex(exception, r".+") - - -if __name__ == "__main__": - unittest.main() diff --git a/exercises/practice/queen-attack/.docs/instructions.append.md b/exercises/practice/queen-attack/.docs/instructions.append.md new file mode 100644 index 0000000000..894b29361f --- /dev/null +++ b/exercises/practice/queen-attack/.docs/instructions.append.md @@ -0,0 +1,23 @@ +# Instructions append + +## Exception messages + +Sometimes it is necessary to [raise an exception](https://docs.python.org/3/tutorial/errors.html#raising-exceptions). When you do this, you should always include a **meaningful error message** to indicate what the source of the error is. This makes your code more readable and helps significantly with debugging. For situations where you know that the error source will be a certain type, you can choose to raise one of the [built in error types](https://docs.python.org/3/library/exceptions.html#base-classes), but should still include a meaningful message. + +This particular exercise requires that you use the [raise statement](https://docs.python.org/3/reference/simple_stmts.html#the-raise-statement) to "throw" `ValueErrors` if the `Queen` constructor is passed numbers that are negative, or numbers that are not a valid row or column. The tests will only pass if you both `raise` the `exception` and include a message with it. + +To raise a `ValueError` with a message, write the message as an argument to the `exception` type: + +```python +# if the row parameter is negative +raise ValueError("row not positive") + +# if the row parameter is not on the defined board +raise ValueError("row not on board") + +# if the column parameter is negative +raise ValueError("column not positive") + +# if the column parameter is not on the defined board +raise ValueError("column not on board") +``` diff --git a/exercises/practice/queen-attack/.meta/example.py b/exercises/practice/queen-attack/.meta/example.py index 09d1a0fee7..1812d29c58 100644 --- a/exercises/practice/queen-attack/.meta/example.py +++ b/exercises/practice/queen-attack/.meta/example.py @@ -1,7 +1,13 @@ class Queen: def __init__(self, row, column): - if not 0 <= row <= 7 or not 0 <= column <= 7: - raise ValueError("Invalid queen position: queen out of the board") + if row < 0: + raise ValueError("row not positive") + if not 0 <= row <= 7: + raise ValueError("row not on board") + if column < 0: + raise ValueError("column not positive") + if not 0 <= column <= 7: + raise ValueError("column not on board") self.row = row self.column = column @@ -9,8 +15,7 @@ def can_attack(self, another_queen): dx = abs(self.row - another_queen.row) dy = abs(self.column - another_queen.column) if dx == dy == 0: - raise ValueError( - 'Invalid queen position: both queens in the same square') + raise ValueError('Invalid queen position: both queens in the same square') elif dx == dy or dx == 0 or dy == 0: return True else: diff --git a/exercises/practice/queen-attack/.meta/template.j2 b/exercises/practice/queen-attack/.meta/template.j2 index 10231bd332..6a08c5251e 100644 --- a/exercises/practice/queen-attack/.meta/template.j2 +++ b/exercises/practice/queen-attack/.meta/template.j2 @@ -10,16 +10,20 @@ class {{ exercise | camel_case }}Test(unittest.TestCase): {% set white_queen = case["input"]["white_queen"]["position"] -%} {% set black_queen = case["input"]["black_queen"]["position"] -%} {% if case is error_case -%} - with self.assertRaisesWithMessage(ValueError): + with self.assertRaises(ValueError) as err: Queen({{ white_queen["row"] }}, {{ white_queen["column"] }}).can_attack(Queen({{ black_queen["row"] }}, {{ black_queen["column"] }})) + self.assertEqual(type(err.exception), ValueError) + self.assertEqual(err.exception.args[0], "{{ case["expected"]["error"] }}") {% else -%} self.assertIs(Queen({{ white_queen["row"] }}, {{ white_queen["column"] }}).can_attack(Queen({{ black_queen["row"] }}, {{ black_queen["column"] }})), {{ case["expected"] }}) {% endif %} {% else -%} {% set queen = case["input"]["queen"]["position"] -%} {% if case is error_case -%} - with self.assertRaisesWithMessage(ValueError): + with self.assertRaises(ValueError) as err: Queen({{ queen["row"] }}, {{ queen["column"] }}) + self.assertEqual(type(err.exception), ValueError) + self.assertEqual(err.exception.args[0], "{{ case["expected"]["error"] }}") {% else -%} Queen({{ queen["row"] }}, {{ queen["column"] }}) {% endif %} @@ -39,5 +43,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase): {{ test_case(case) }} {% endfor %} {% endif %} - -{{ macros.footer() }} diff --git a/exercises/practice/queen-attack/queen_attack_test.py b/exercises/practice/queen-attack/queen_attack_test.py index 8f9438dc67..d1aad0582a 100644 --- a/exercises/practice/queen-attack/queen_attack_test.py +++ b/exercises/practice/queen-attack/queen_attack_test.py @@ -13,20 +13,28 @@ def test_queen_with_a_valid_position(self): Queen(2, 2) def test_queen_must_have_positive_row(self): - with self.assertRaisesWithMessage(ValueError): + with self.assertRaises(ValueError) as err: Queen(-2, 2) + self.assertEqual(type(err.exception), ValueError) + self.assertEqual(err.exception.args[0], "row not positive") def test_queen_must_have_row_on_board(self): - with self.assertRaisesWithMessage(ValueError): + with self.assertRaises(ValueError) as err: Queen(8, 4) + self.assertEqual(type(err.exception), ValueError) + self.assertEqual(err.exception.args[0], "row not on board") def test_queen_must_have_positive_column(self): - with self.assertRaisesWithMessage(ValueError): + with self.assertRaises(ValueError) as err: Queen(2, -2) + self.assertEqual(type(err.exception), ValueError) + self.assertEqual(err.exception.args[0], "column not positive") def test_queen_must_have_column_on_board(self): - with self.assertRaisesWithMessage(ValueError): + with self.assertRaises(ValueError) as err: Queen(4, 8) + self.assertEqual(type(err.exception), ValueError) + self.assertEqual(err.exception.args[0], "column not on board") # Test the ability of one queen to attack another def test_cannot_attack(self): @@ -52,13 +60,10 @@ def test_can_attack_on_fourth_diagonal(self): # Track-specific tests def test_queens_same_position_can_attack(self): - with self.assertRaisesWithMessage(ValueError): + with self.assertRaises(ValueError) as err: Queen(2, 2).can_attack(Queen(2, 2)) - - # Utility functions - def assertRaisesWithMessage(self, exception): - return self.assertRaisesRegex(exception, r".+") - - -if __name__ == "__main__": - unittest.main() + self.assertEqual(type(err.exception), ValueError) + self.assertEqual( + err.exception.args[0], + "Invalid queen position: both queens in the same square", + )