Skip to content

Commit 23018a8

Browse files
pi-gjddpgeorge
authored andcommitted
unittest: Add subtest usage examples.
This work was funded by Planet Innovation.
1 parent e3371be commit 23018a8

File tree

1 file changed

+214
-0
lines changed

1 file changed

+214
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
"""Tests using unittest.subtest as an example reference for how it is used"""
2+
3+
import unittest
4+
5+
6+
def factorial(value: int) -> int:
7+
"""Iterative factorial algorithm implementation"""
8+
result = 1
9+
10+
for i in range(1, value + 1):
11+
result *= i
12+
13+
return result
14+
15+
16+
class Person:
17+
"""Represents a person with a name, age, and who can make friends"""
18+
19+
def __init__(self, name: str, age: int) -> None:
20+
self.name = name
21+
self.age = age
22+
self.friends = set()
23+
24+
def __repr__(self) -> str:
25+
return f"Person({self.name})"
26+
27+
def add_friend(self, friend):
28+
"""Logs that this Person has made a new friend"""
29+
self.friends.add(friend)
30+
31+
def has_friend(self, friend_candidate) -> bool:
32+
"""Determines if this Person has the friend `friend_candidate`"""
33+
return friend_candidate in self.friends
34+
35+
def is_oldest_friend(self) -> bool:
36+
"""Determines whether this Person is the oldest out of themself and their friends"""
37+
return self.age > max(friend.age for friend in self.friends)
38+
39+
40+
class TestSubtest(unittest.TestCase):
41+
"""Examples/tests of unittest.subTest()"""
42+
43+
def test_sorted(self) -> None:
44+
"""Test that the selection sort function correctly sorts lists"""
45+
tests = [
46+
{
47+
"unsorted": [-68, 15, 52, -54, -64, 20, 2, 66, 33],
48+
"sorted": [-68, -64, -54, 2, 15, 20, 33, 52, 66],
49+
"correct": True,
50+
},
51+
{
52+
"unsorted": [-68, 15, 52, -54, -64, 20, 2, 66, 33],
53+
"sorted": [-68, -54, -64, 2, 15, 20, 33, 52, 66],
54+
"correct": False,
55+
},
56+
{
57+
"unsorted": [-68, 15, 52, 54, -64, 20, 2, 66, 33],
58+
"sorted": [-68, -64, -54, 2, 15, 20, 33, 52, 66],
59+
"correct": False,
60+
},
61+
{
62+
"unsorted": [],
63+
"sorted": [],
64+
"correct": True,
65+
},
66+
{
67+
"unsorted": [42],
68+
"sorted": [42],
69+
"correct": True,
70+
},
71+
{
72+
"unsorted": [42],
73+
"sorted": [],
74+
"correct": False,
75+
},
76+
{
77+
"unsorted": [],
78+
"sorted": [24],
79+
"correct": False,
80+
},
81+
{
82+
"unsorted": [43, 44],
83+
"sorted": [43, 44],
84+
"correct": True,
85+
},
86+
{
87+
"unsorted": [44, 43],
88+
"sorted": [43, 44],
89+
"correct": True,
90+
},
91+
]
92+
93+
for test in tests:
94+
with self.subTest(): # Subtests continue to be tested, even if an earlier one fails
95+
if test["correct"]: # Tests that match what is expected
96+
self.assertEqual(sorted(test["unsorted"]), test["sorted"])
97+
else: # Tests that are meant to fail
98+
with self.assertRaises(AssertionError):
99+
self.assertEqual(sorted(test["unsorted"]), test["sorted"])
100+
101+
def test_factorial(self) -> None:
102+
"""Test that the factorial fuction correctly calculates factorials
103+
104+
Makes use of `msg` argument in subtest method to clarify which subtests had an
105+
error in the results
106+
"""
107+
tests = [
108+
{
109+
"operand": 0,
110+
"result": 1,
111+
"correct": True,
112+
},
113+
{
114+
"operand": 1,
115+
"result": 1,
116+
"correct": True,
117+
},
118+
{
119+
"operand": 1,
120+
"result": 0,
121+
"correct": False,
122+
},
123+
{
124+
"operand": 2,
125+
"result": 2,
126+
"correct": True,
127+
},
128+
{
129+
"operand": 3,
130+
"result": 6,
131+
"correct": True,
132+
},
133+
{
134+
"operand": 3,
135+
"result": -6,
136+
"correct": False,
137+
},
138+
{
139+
"operand": 4,
140+
"result": 24,
141+
"correct": True,
142+
},
143+
{
144+
"operand": 15,
145+
"result": 1_307_674_368_000,
146+
"correct": True,
147+
},
148+
{
149+
"operand": 15,
150+
"result": 1_307_674_368_001,
151+
"correct": False,
152+
},
153+
{
154+
"operand": 11,
155+
"result": 39_916_800,
156+
"correct": True,
157+
},
158+
]
159+
160+
for test in tests:
161+
with self.subTest(
162+
f"{test['operand']}!"
163+
): # Let's us know we were testing "x!" when we get an error
164+
if test["correct"]:
165+
self.assertEqual(factorial(test["operand"]), test["result"])
166+
else:
167+
with self.assertRaises(AssertionError):
168+
self.assertEqual(factorial(test["operand"]), test["result"])
169+
170+
def test_person(self) -> None:
171+
"""Test the Person class and its friend-making ability
172+
173+
Makes use of subtest's params to specify relevant data about the tests, which is
174+
helpful for debugging
175+
"""
176+
# Create a friendship
177+
alice = Person("Alice", 22)
178+
bob = Person("Bob", 23)
179+
alice.add_friend(bob)
180+
181+
# Test friendship init
182+
with self.subTest(
183+
"Alice should have Bob as a friend", name=alice.name, friends=bob.friends
184+
): # Params `name` and `friends` provide useful data for debugging purposes
185+
self.assertTrue(alice.has_friend(bob))
186+
187+
with self.subTest(
188+
"Bob should not have Alice as a friend", name=bob.name, friends=bob.friends
189+
):
190+
self.assertFalse(bob.has_friend(alice))
191+
192+
# Friendship is not always commutative, so Bob is not implicitly friends with Alice
193+
with self.subTest("Alice and Bob should not both be friends with eachother"):
194+
with self.assertRaises(AssertionError):
195+
self.assertTrue(bob.has_friend(alice) and alice.has_friend(bob))
196+
197+
# Bob becomes friends with Alice
198+
bob.add_friend(alice)
199+
200+
with self.subTest(
201+
"Bob should now have Alice as a friend", name=bob.name, friends=bob.friends
202+
):
203+
self.assertTrue(bob.has_friend(alice))
204+
205+
with self.subTest(
206+
"Bob should be the oldest of his friends",
207+
age=bob.age,
208+
friend_ages=[friend.age for friend in bob.friends],
209+
): # Different params can be used for different subtests in the same test
210+
self.assertTrue(bob.is_oldest_friend())
211+
212+
213+
if __name__ == "__main__":
214+
unittest.main()

0 commit comments

Comments
 (0)