22import pathlib
33from collections import Counter
44from string import ascii_lowercase , ascii_uppercase
5- from typing import Any , List , Tuple
5+ from typing import Any , List
66
77import pytest
88
@@ -12,6 +12,14 @@ def read_data(name: str, data_dir: str = "data") -> pathlib.Path:
1212 return (pathlib .Path (__file__ ).parent / f"{ data_dir } /{ name } " ).resolve ()
1313
1414
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+
1523#
1624# Exercise 1: a `greet` function
1725#
@@ -22,34 +30,46 @@ def reference_greet(name: str, age: int) -> str:
2230 return f"Hello, { name } ! You are { age } years old."
2331
2432
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 = []
2745
2846 signature = inspect .signature (function_to_test )
2947 params = signature .parameters
3048 return_annotation = signature .return_annotation
3149
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." )
3660
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 )
4365
4466
4567#
4668# Exercise 2: calculate area with units
4769#
4870
4971
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 :
5373 """Reference solution for the calculate_area exercise"""
5474 # Conversion factors from supported units to centimeters
5575 units = {
@@ -65,70 +85,81 @@ def reference_calculate_area(
6585 except KeyError :
6686 return f"Invalid unit: { unit } "
6787 else :
68- return ( area , " cm^2")
88+ return f" { area } cm^2"
6989
7090
7191def 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 = []
7393
7494 signature = inspect .signature (function_to_test )
7595 params = signature .parameters
7696 return_annotation = signature .return_annotation
7797
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 )
91121
92122
93123@pytest .mark .parametrize (
94- "length,width,unit,expected " ,
124+ "length,width,unit" ,
95125 [
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" ),
102132 ],
103133)
104134def test_calculate_area_result (
105135 length : float ,
106136 width : float ,
107137 unit : str ,
108- expected : Tuple [float , str ],
109138 function_to_test ,
110139) -> None :
111- result = function_to_test (length , width , unit )
112- test_result = reference_calculate_area (length , width , unit )
140+ errors = []
113141
114142 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." )
125151 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
132163
133164
134165#
0 commit comments