Skip to content

Commit bac3c13

Browse files
authored
[Practice Exercises]: Add Better Error Handling Instructions & Tests for Error Raising Messages (# 2 of 8) (#2691)
* Edited instrucitons.apped to have better error handling instructions. * Edited Jinja2 template and exemplar to have proper error message. * Regenerated test file. * Added instructions append with error handling message. * Altered JinJa2 template for error handling and adjusted example to have error classes. * Regenerated test cases. * Updated stub wit new error classes. * Added error handling instructions append. * Adjusted JinJa2 template to test for specific message. * Regenerated test file. * Added instructions apped for error handling. * Added instructions append for error handling and adjusted example and test file. * Corrected syntax error.
1 parent 1abb6a6 commit bac3c13

File tree

16 files changed

+250
-91
lines changed

16 files changed

+250
-91
lines changed
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Instructions append
2+
3+
## Exception messages
4+
5+
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.
6+
7+
This particular exercise requires that you use the [raise statement](https://docs.python.org/3/reference/simple_stmts.html#the-raise-statement) to "throw" an error when the scoring or playing rules are not followed. The tests will only pass if you both `raise` the `exception` and include a message with it.
8+
9+
To raise a `ValueError` with a message, write the message as an argument to the `exception` type:
10+
11+
```python
12+
# example when a bonus is attempted with an open frame
13+
raise IndexError("cannot throw bonus with an open tenth frame")
14+
15+
# example when fill balls are invalid
16+
raise ValueError("invalid fill balls")
17+
```
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Instructions append
2+
3+
## Exception messages
4+
5+
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.
6+
7+
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` when change cannot be made with the coins given. The tests will only pass if you both `raise` the `exception` and include a message with it.
8+
9+
To raise a `ValueError` with a message, write the message as an argument to the `exception` type:
10+
11+
```python
12+
# example when change cannot be made with the coins passed in
13+
raise ValueError("can't make target with given coins")
14+
```

exercises/practice/change/.meta/template.j2

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,13 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
77
{%- set coins = case["input"]["coins"] %}
88
{%- set target = case["input"]["target"] %}
99
{%- set expected = case["expected"] %}
10+
{%- set exp_error = expected["error"] %}
1011
{%- if case is error_case %}
11-
with self.assertRaisesWithMessage(ValueError):
12+
with self.assertRaises(ValueError) as err:
1213
{{case["property"] |to_snake }}({{coins}}, {{target}})
14+
self.assertEqual(type(err.exception), ValueError)
15+
self.assertEqual(err.exception.args[0], "{{ exp_error }}")
1316
{% else %}
1417
self.assertEqual({{ case["property"] |to_snake }}({{coins}}, {{target}}), {{expected}})
1518
{% endif %}
1619
{% endfor %}
17-
18-
{{ macros.footer() }}

exercises/practice/change/change_test.py

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -36,21 +36,23 @@ def test_no_coins_make_0_change(self):
3636
self.assertEqual(find_fewest_coins([1, 5, 10, 21, 25], 0), [])
3737

3838
def test_error_testing_for_change_smaller_than_the_smallest_of_coins(self):
39-
with self.assertRaisesWithMessage(ValueError):
39+
with self.assertRaises(ValueError) as err:
4040
find_fewest_coins([5, 10], 3)
41+
self.assertEqual(type(err.exception), ValueError)
42+
self.assertEqual(
43+
err.exception.args[0], "can't make target with given coins"
44+
)
4145

4246
def test_error_if_no_combination_can_add_up_to_target(self):
43-
with self.assertRaisesWithMessage(ValueError):
47+
with self.assertRaises(ValueError) as err:
4448
find_fewest_coins([5, 10], 94)
49+
self.assertEqual(type(err.exception), ValueError)
50+
self.assertEqual(
51+
err.exception.args[0], "can't make target with given coins"
52+
)
4553

4654
def test_cannot_find_negative_change_values(self):
47-
with self.assertRaisesWithMessage(ValueError):
55+
with self.assertRaises(ValueError) as err:
4856
find_fewest_coins([1, 2, 5], -5)
49-
50-
# Utility functions
51-
def assertRaisesWithMessage(self, exception):
52-
return self.assertRaisesRegex(exception, r".+")
53-
54-
55-
if __name__ == "__main__":
56-
unittest.main()
57+
self.assertEqual(type(err.exception), ValueError)
58+
self.assertEqual(err.exception.args[0], "target can't be negative")
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Instructions append
2+
3+
## Customizing and Raising Exceptions
4+
5+
Sometimes it is necessary to both [customize](https://docs.python.org/3/tutorial/errors.html#user-defined-exceptions) and [`raise`](https://docs.python.org/3/tutorial/errors.html#raising-exceptions) exceptions in your code. 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.
6+
7+
Custom exceptions can be created through new exception classes (see [`classes`](https://docs.python.org/3/tutorial/classes.html#tut-classes) for more detail.) that are typically subclasses of [`Exception`](https://docs.python.org/3/library/exceptions.html#Exception).
8+
9+
For situations where you know the error source will be a derivative of a certain exception type, you can choose to inherit from one of the [`built in error types`](https://docs.python.org/3/library/exceptions.html#base-classes) under the _Exception_ class. When raising the error, you should still include a meaningful message.
10+
11+
This particular exercise requires that you create two _custom exceptions_. One exception to be [raised](https://docs.python.org/3/reference/simple_stmts.html#the-raise-statement)/"thrown" when your circular buffer is **full**, and one for when it is **empty**. The tests will only pass if you customize appropriate exceptions, `raise` those exceptions, and include appropriate error messages.
12+
13+
To customize a `built-in exception`, create a `class` that inherits from that exception. When raising the custom exception with a message, write the message as an argument to the `exception` type:
14+
15+
```python
16+
# subclassing the built-in BufferError to create BufferFullException
17+
class BufferFullException(BufferError):
18+
"""Exception raised when CircularBuffer is full.
19+
20+
message: explanation of the error.
21+
22+
"""
23+
def __init__(self, message):
24+
self.message = message
25+
26+
27+
# raising a BufferFullException
28+
raise BufferFullException("Circular buffer is full")
29+
```

exercises/practice/circular-buffer/.meta/example.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,21 @@
1-
class BufferFullException(Exception):
2-
pass
1+
class BufferFullException(BufferError):
2+
"""Exception raised when CircularBuffer is full.
33
4+
message: explanation of the error.
45
5-
class BufferEmptyException(Exception):
6-
pass
6+
"""
7+
def __init__(self, message):
8+
self.message = message
9+
10+
11+
class BufferEmptyException(BufferError):
12+
"""Exception raised when CircularBuffer is empty.
13+
14+
message: explanation of the error.
15+
16+
"""
17+
def __init__(self, message):
18+
self.message = message
719

820

921
class CircularBuffer:

exercises/practice/circular-buffer/.meta/template.j2

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@ buf.{{ op["operation"] }}(
66
{%- endif -%}
77
)
88
{%- endmacro -%}
9-
{{ macros.header(["CircularBuffer"]) }}
9+
{{ macros.header(["CircularBuffer","BufferEmptyException", "BufferFullException"]) }}
1010

1111
class {{ exercise | camel_case }}Test(unittest.TestCase):
1212
{% for case in cases %}
1313
{%- set input = case["input"] -%}
14-
def test_{{ case["description"] | to_snake }}(self):
14+
{%- set case_name = case["description"] | to_snake %}
15+
def test_{{ case_name }}(self):
1516
buf = CircularBuffer({{ input["capacity"] }})
1617
{% for op in input["operations"] -%}
1718
{% if op.get("should_succeed", True) -%}
@@ -21,10 +22,10 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
2122
{{ call_op(op) }}
2223
{% endif -%}
2324
{% else -%}
24-
with self.assertRaisesWithMessage(BaseException):
25+
with self.assertRaises(BufferError) as err:
2526
{{ call_op(op) }}
27+
self.assertEqual(type(err.exception), (BufferEmptyException, BufferFullException))
28+
self.assertEqual(err.exception.args[0], ("Circular buffer is empty", "Circular buffer is full"))
2629
{% endif -%}
2730
{% endfor %}
2831
{% endfor %}
29-
30-
{{ macros.footer(True) }}

exercises/practice/circular-buffer/circular_buffer.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,21 @@
1-
class BufferFullException(Exception):
2-
pass
1+
class BufferFullException(BufferError):
2+
"""Exception raised when CircularBuffer is full.
33
4+
message: explanation of the error.
45
5-
class BufferEmptyException(Exception):
6-
pass
6+
"""
7+
def __init__(self, message):
8+
pass
9+
10+
11+
class BufferEmptyException(BufferError):
12+
"""Exception raised when CircularBuffer is empty.
13+
14+
message: explanation of the error.
15+
16+
"""
17+
def __init__(self, message):
18+
pass
719

820

921
class CircularBuffer:

exercises/practice/circular-buffer/circular_buffer_test.py

Lines changed: 42 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
from circular_buffer import (
44
CircularBuffer,
5+
BufferEmptyException,
6+
BufferFullException,
57
)
68

79
# Tests adapted from `problem-specifications//canonical-data.json`
@@ -10,8 +12,15 @@
1012
class CircularBufferTest(unittest.TestCase):
1113
def test_reading_empty_buffer_should_fail(self):
1214
buf = CircularBuffer(1)
13-
with self.assertRaisesWithMessage(BaseException):
15+
with self.assertRaises(BufferError) as err:
1416
buf.read()
17+
self.assertEqual(
18+
type(err.exception), (BufferEmptyException, BufferFullException)
19+
)
20+
self.assertEqual(
21+
err.exception.args[0],
22+
("Circular buffer is empty", "Circular buffer is full"),
23+
)
1524

1625
def test_can_read_an_item_just_written(self):
1726
buf = CircularBuffer(1)
@@ -22,8 +31,15 @@ def test_each_item_may_only_be_read_once(self):
2231
buf = CircularBuffer(1)
2332
buf.write("1")
2433
self.assertEqual(buf.read(), "1")
25-
with self.assertRaisesWithMessage(BaseException):
34+
with self.assertRaises(BufferError) as err:
2635
buf.read()
36+
self.assertEqual(
37+
type(err.exception), (BufferEmptyException, BufferFullException)
38+
)
39+
self.assertEqual(
40+
err.exception.args[0],
41+
("Circular buffer is empty", "Circular buffer is full"),
42+
)
2743

2844
def test_items_are_read_in_the_order_they_are_written(self):
2945
buf = CircularBuffer(2)
@@ -35,8 +51,15 @@ def test_items_are_read_in_the_order_they_are_written(self):
3551
def test_full_buffer_can_t_be_written_to(self):
3652
buf = CircularBuffer(1)
3753
buf.write("1")
38-
with self.assertRaisesWithMessage(BaseException):
54+
with self.assertRaises(BufferError) as err:
3955
buf.write("2")
56+
self.assertEqual(
57+
type(err.exception), (BufferEmptyException, BufferFullException)
58+
)
59+
self.assertEqual(
60+
err.exception.args[0],
61+
("Circular buffer is empty", "Circular buffer is full"),
62+
)
4063

4164
def test_a_read_frees_up_capacity_for_another_write(self):
4265
buf = CircularBuffer(1)
@@ -58,8 +81,15 @@ def test_items_cleared_out_of_buffer_can_t_be_read(self):
5881
buf = CircularBuffer(1)
5982
buf.write("1")
6083
buf.clear()
61-
with self.assertRaisesWithMessage(BaseException):
84+
with self.assertRaises(BufferError) as err:
6285
buf.read()
86+
self.assertEqual(
87+
type(err.exception), (BufferEmptyException, BufferFullException)
88+
)
89+
self.assertEqual(
90+
err.exception.args[0],
91+
("Circular buffer is empty", "Circular buffer is full"),
92+
)
6393

6494
def test_clear_frees_up_capacity_for_another_write(self):
6595
buf = CircularBuffer(1)
@@ -112,13 +142,12 @@ def test_initial_clear_does_not_affect_wrapping_around(self):
112142
buf.overwrite("4")
113143
self.assertEqual(buf.read(), "3")
114144
self.assertEqual(buf.read(), "4")
115-
with self.assertRaisesWithMessage(BaseException):
145+
with self.assertRaises(BufferError) as err:
116146
buf.read()
117-
118-
# Utility functions
119-
def assertRaisesWithMessage(self, exception):
120-
return self.assertRaisesRegex(exception, r".+")
121-
122-
123-
if __name__ == "__main__":
124-
unittest.main()
147+
self.assertEqual(
148+
type(err.exception), (BufferEmptyException, BufferFullException)
149+
)
150+
self.assertEqual(
151+
err.exception.args[0],
152+
("Circular buffer is empty", "Circular buffer is full"),
153+
)
Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,14 @@
1-
# Notes
1+
# Instructions append
22

3-
The Collatz Conjecture is only concerned with strictly positive integers, so your solution should raise a `ValueError` with a meaningful message if given 0 or a negative integer.
3+
## Exception messages
4+
5+
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.
6+
7+
The Collatz Conjecture is only concerned with **strictly positive integers**, so this exercise expects you to use the [raise statement](https://docs.python.org/3/reference/simple_stmts.html#the-raise-statement) and "throw" a `ValueError` in your solution if the given value is zero or a negative integer. The tests will only pass if you both `raise` the `exception` and include a message with it.
8+
9+
To raise a `ValueError` with a message, write the message as an argument to the `exception` type:
10+
11+
```python
12+
# example when argument is zero or a negative integer
13+
raise ValueError("Only positive numbers are allowed")
14+
```

0 commit comments

Comments
 (0)