2
2
import pathlib
3
3
from collections import Counter
4
4
from string import ascii_lowercase , ascii_uppercase
5
- from typing import Any , List , Tuple
5
+ from typing import Any , List
6
6
7
7
import pytest
8
8
@@ -12,6 +12,14 @@ def read_data(name: str, data_dir: str = "data") -> pathlib.Path:
12
12
return (pathlib .Path (__file__ ).parent / f"{ data_dir } /{ name } " ).resolve ()
13
13
14
14
15
+ def errors_to_list (errors ):
16
+ result = "<ul>"
17
+ for error in errors :
18
+ result += "<li>" + error + "</li>"
19
+ result += "</ul>"
20
+ return result
21
+
22
+
15
23
#
16
24
# Exercise 1: a `greet` function
17
25
#
@@ -22,34 +30,46 @@ def reference_greet(name: str, age: int) -> str:
22
30
return f"Hello, { name } ! You are { age } years old."
23
31
24
32
25
- def test_greet (function_to_test ) -> None :
26
- assert function_to_test .__doc__ is not None , "The function is missing a docstring"
33
+ @pytest .mark .parametrize (
34
+ "name,age" ,
35
+ [
36
+ ("John" , 30 ),
37
+ ],
38
+ )
39
+ def test_greet (
40
+ name : str ,
41
+ age : int ,
42
+ function_to_test ,
43
+ ) -> None :
44
+ errors = []
27
45
28
46
signature = inspect .signature (function_to_test )
29
47
params = signature .parameters
30
48
return_annotation = signature .return_annotation
31
49
32
- assert len (params ) == 2 , "The function should take two arguments"
33
- assert (
34
- "name" in params .keys () and "age" in params .keys ()
35
- ), "The function's parameters should be 'name' and 'age'"
50
+ if function_to_test .__doc__ is None :
51
+ errors .append ("The function is missing a docstring." )
52
+ if len (params ) != 2 :
53
+ errors .append ("The function should take two arguments." )
54
+ if "name" not in params .keys () or "age" not in params .keys ():
55
+ errors .append ("The function's parameters should be 'name' and 'age'." )
56
+ if any (p .annotation == inspect .Parameter .empty for p in params .values ()):
57
+ errors .append ("The function's parameters should have type hints." )
58
+ if return_annotation == inspect .Signature .empty :
59
+ errors .append ("The function's return value is missing the type hint." )
36
60
37
- assert all (
38
- p .annotation != inspect .Parameter .empty for p in params .values ()
39
- ), "The function's parameters should have type hints"
40
- assert (
41
- return_annotation != inspect .Signature .empty
42
- ), "The function's return value is missing the type hint"
61
+ # test signature
62
+ assert not errors , errors_to_list (errors )
63
+ # test result
64
+ assert function_to_test (name , age ) == reference_greet (name , age )
43
65
44
66
45
67
#
46
68
# Exercise 2: calculate area with units
47
69
#
48
70
49
71
50
- def reference_calculate_area (
51
- length : float , width : float , unit : str = "cm"
52
- ) -> Tuple [float , str ] | str :
72
+ def reference_calculate_area (length : float , width : float , unit : str = "cm" ) -> str :
53
73
"""Reference solution for the calculate_area exercise"""
54
74
# Conversion factors from supported units to centimeters
55
75
units = {
@@ -65,70 +85,81 @@ def reference_calculate_area(
65
85
except KeyError :
66
86
return f"Invalid unit: { unit } "
67
87
else :
68
- return ( area , " cm^2")
88
+ return f" { area } cm^2"
69
89
70
90
71
91
def test_calculate_area_signature (function_to_test ) -> None :
72
- assert function_to_test . __doc__ is not None , "The function is missing a docstring"
92
+ errors = []
73
93
74
94
signature = inspect .signature (function_to_test )
75
95
params = signature .parameters
76
96
return_annotation = signature .return_annotation
77
97
78
- assert len (params ) == 3 , "The function should take three arguments"
79
- assert (
80
- "length" in params .keys ()
81
- and "width" in params .keys ()
82
- and "unit" in params .keys ()
83
- ), "The function's parameters should be 'length', 'width' and 'unit'"
84
-
85
- assert all (
86
- p .annotation != inspect .Parameter .empty for p in params .values ()
87
- ), "The function's parameters should have type hints"
88
- assert (
89
- return_annotation != inspect .Signature .empty
90
- ), "The function's return value is missing the type hint"
98
+ if function_to_test .__doc__ is None :
99
+ errors .append ("The function is missing a docstring." )
100
+ if len (params ) != 3 :
101
+ errors .append ("The function should take three arguments." )
102
+ if (
103
+ "length" not in params .keys ()
104
+ or "width" not in params .keys ()
105
+ or "unit" not in params .keys ()
106
+ ):
107
+ errors .append (
108
+ "The function's parameters should be 'length', 'width' and 'unit'."
109
+ )
110
+ if "unit" in params .keys () and not (
111
+ params ["unit" ].kind == inspect .Parameter .POSITIONAL_OR_KEYWORD
112
+ and params ["unit" ].default == "cm"
113
+ ):
114
+ errors .append ("Argument 'unit' should have a default value 'cm'." )
115
+ if any (p .annotation == inspect .Parameter .empty for p in params .values ()):
116
+ errors .append ("The function's parameters should have type hints." )
117
+ if return_annotation == inspect .Signature .empty :
118
+ errors .append ("The function's return value is missing the type hint." )
119
+
120
+ assert not errors , errors_to_list (errors )
91
121
92
122
93
123
@pytest .mark .parametrize (
94
- "length,width,unit,expected " ,
124
+ "length,width,unit" ,
95
125
[
96
- (2.0 , 3.0 , "cm" , ( 6.0 , "cm^2" ) ),
97
- (4.0 , 5.0 , "m" , ( 200000.0 , "cm^2" ) ),
98
- (10.0 , 2.0 , "mm" , ( 2000.0 , "cm^2" ) ),
99
- (2.0 , 8.0 , "yd" , ( 133780.38 , "cm^2" ) ),
100
- (5.0 , 4.0 , "ft" , ( 18580.608 , "cm^2" ) ),
101
- (3.0 , 5.0 , "in" , ( 96.774 , "cm^2" ) ),
126
+ (2.0 , 3.0 , "cm" ),
127
+ (4.0 , 5.0 , "m" ),
128
+ (10.0 , 2.0 , "mm" ),
129
+ (2.0 , 8.0 , "yd" ),
130
+ (5.0 , 4.0 , "ft" ),
131
+ (3.0 , 5.0 , "in" ),
102
132
],
103
133
)
104
134
def test_calculate_area_result (
105
135
length : float ,
106
136
width : float ,
107
137
unit : str ,
108
- expected : Tuple [float , str ],
109
138
function_to_test ,
110
139
) -> None :
111
- result = function_to_test (length , width , unit )
112
- test_result = reference_calculate_area (length , width , unit )
140
+ errors = []
113
141
114
142
if unit in ("cm" , "m" , "mm" , "yd" , "ft" ):
115
- assert isinstance (result , Tuple ), "The function should return a tuple"
116
-
117
- assert "cm^2" in result , "The result should be in squared centimeters (cm^2)"
118
-
119
- # Double-check the reference solution
120
- assert test_result [0 ] == pytest .approx (
121
- expected [0 ], abs = 0.01
122
- ), "The reference solution is incorrect"
123
-
124
- assert result [0 ] == pytest .approx (expected [0 ], abs = 0.01 )
143
+ result = function_to_test (length , width , unit )
144
+
145
+ if not isinstance (result , str ):
146
+ errors .append ("The function should return a string." )
147
+ if "cm^2" not in result :
148
+ errors .append ("The result should be in squared centimeters (cm^2)." )
149
+ if result != reference_calculate_area (length , width , unit ):
150
+ errors .append ("The solution is incorrect." )
125
151
else :
126
- assert isinstance (
127
- result , str
128
- ), "The function should return an error string for unsupported units"
129
- assert (
130
- result == f"Invalid unit: { unit } "
131
- ), "The error message is incorrectly formatted"
152
+ try :
153
+ result = function_to_test (length , width , unit )
154
+ except KeyError :
155
+ errors .append (
156
+ "The function should return an error string for unsupported units."
157
+ )
158
+ else :
159
+ if result != f"Invalid unit: { unit } " :
160
+ errors .append ("The error message is incorrectly formatted." )
161
+
162
+ assert not errors
132
163
133
164
134
165
#
0 commit comments