Skip to content

Commit fe170b5

Browse files
committed
Use AssertionError when inside decorated test case
- Stops at first failure like most other test frameworks - Single test passed message regardless of the number of assertions - Discourage fat test cases - Allow using external assertions - `np.testing.assert_equal` - `pd.testing.assert_frame_equal` - assertion packages
1 parent efccb92 commit fe170b5

File tree

5 files changed

+119
-66
lines changed

5 files changed

+119
-66
lines changed

codewars_test/test_framework.py

Lines changed: 68 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from __future__ import print_function
2+
import inspect
23

34

45
class AssertException(Exception):
@@ -17,14 +18,43 @@ def display(type, message, label="", mode=""):
1718
)
1819

1920

20-
def expect(passed=None, message=None, allow_raise=False):
21+
# TODO Currently this only works if assertion functions are written directly in the test case.
22+
def _is_in_test_case():
23+
frame = inspect.currentframe()
24+
caller_frame = frame.f_back
25+
test_case_frame = caller_frame.f_back
26+
decorator_frame = test_case_frame.f_back
27+
if not decorator_frame:
28+
return False
29+
if not "func" in decorator_frame.f_locals:
30+
return False
31+
func = decorator_frame.f_locals["func"]
32+
code = test_case_frame.f_code
33+
if func and func.__code__ == code and func.test_case_func:
34+
return True
35+
return False
36+
37+
38+
def _handle_test_result(passed, message=None, allow_raise=False, in_test_case=False):
2139
if passed:
22-
display("PASSED", "Test Passed")
40+
if not in_test_case:
41+
display("PASSED", "Test Passed")
2342
else:
24-
message = message or "Value is not what was expected"
25-
display("FAILED", message)
26-
if allow_raise:
27-
raise AssertException(message)
43+
if not message:
44+
message = "Value is not what was expected"
45+
if in_test_case:
46+
raise AssertionError(message)
47+
else:
48+
display("FAILED", message)
49+
if allow_raise:
50+
# TODO Use AssertionError?
51+
raise AssertException(message)
52+
53+
54+
def expect(passed=None, message=None, allow_raise=False):
55+
_handle_test_result(
56+
passed, message, allow_raise, _is_in_test_case(),
57+
)
2858

2959

3060
def assert_equals(actual, expected, message=None, allow_raise=False):
@@ -34,7 +64,9 @@ def assert_equals(actual, expected, message=None, allow_raise=False):
3464
else:
3565
message += ": " + equals_msg
3666

37-
expect(actual == expected, message, allow_raise)
67+
_handle_test_result(
68+
actual == expected, message, allow_raise, _is_in_test_case(),
69+
)
3870

3971

4072
def assert_not_equals(actual, expected, message=None, allow_raise=False):
@@ -45,7 +77,9 @@ def assert_not_equals(actual, expected, message=None, allow_raise=False):
4577
else:
4678
message += ": " + equals_msg
4779

48-
expect(not (actual == expected), message, allow_raise)
80+
_handle_test_result(
81+
not (actual == expected), message, allow_raise, _is_in_test_case(),
82+
)
4983

5084

5185
def expect_error(message, function, exception=Exception):
@@ -56,26 +90,35 @@ def expect_error(message, function, exception=Exception):
5690
passed = True
5791
except Exception:
5892
pass
59-
expect(passed, message)
93+
_handle_test_result(
94+
passed, message, False, _is_in_test_case(),
95+
)
6096

6197

6298
def expect_no_error(message, function, exception=BaseException):
99+
passed = True
63100
try:
64101
function()
65102
except exception as e:
66-
fail("{}: {}".format(message or "Unexpected exception", repr(e)))
67-
return
103+
passed = False
104+
message = "{}: {}".format(message or "Unexpected exception", repr(e))
68105
except Exception:
69106
pass
70-
pass_()
107+
_handle_test_result(
108+
passed, message, False, _is_in_test_case(),
109+
)
71110

72111

73112
def pass_():
74-
expect(True)
113+
if not _is_in_test_case():
114+
display("PASSED", "Test Passed")
75115

76116

77117
def fail(message):
78-
expect(False, message)
118+
if _is_in_test_case():
119+
raise AssertionError(message)
120+
else:
121+
display("FAILED", message)
79122

80123

81124
def assert_approx_equals(
@@ -88,7 +131,12 @@ def assert_approx_equals(
88131
else:
89132
message += ": " + equals_msg
90133
div = max(abs(actual), abs(expected), 1)
91-
expect(abs((actual - expected) / div) < margin, message, allow_raise)
134+
_handle_test_result(
135+
abs((actual - expected) / div) < margin,
136+
message,
137+
allow_raise,
138+
_is_in_test_case(),
139+
)
92140

93141

94142
"""
@@ -108,13 +156,18 @@ def _timed_block_factory(opening_text):
108156

109157
def _timed_block_decorator(s, before=None, after=None):
110158
display(opening_text, s)
159+
is_test_case = opening_text == "IT"
111160

112161
def wrapper(func):
113162
if callable(before):
114163
before()
115164
time = timer()
165+
if is_test_case:
166+
func.test_case_func = True
116167
try:
117168
func()
169+
if is_test_case:
170+
display("PASSED", "Test Passed")
118171
except AssertionError as e:
119172
display("FAILED", str(e))
120173
except Exception:
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
2+
<DESCRIBE::>group 1
3+
4+
<IT::>test 1
5+
6+
<PASSED::>Test Passed
7+
8+
<COMPLETEDIN::>0.00
9+
10+
<IT::>test 2
11+
12+
<FAILED::>Expected 1 to equal 2
13+
14+
<COMPLETEDIN::>0.01
15+
16+
<IT::>test 3
17+
18+
<FAILED::>using assert
19+
20+
<COMPLETEDIN::>0.00
21+
22+
<COMPLETEDIN::>0.03

tests/fixtures/custom_assertion.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import codewars_test as test
2+
3+
4+
def custom_assert_equal(a, b):
5+
if a != b:
6+
raise AssertionError("Expected {} to equal {}".format(a, b))
7+
8+
9+
@test.describe("group 1")
10+
def group_1():
11+
@test.it("test 1")
12+
def test_1():
13+
custom_assert_equal(1, 1)
14+
15+
@test.it("test 2")
16+
def test_2():
17+
custom_assert_equal(1, 2)
18+
19+
@test.it("test 3")
20+
def test_3():
21+
assert 1 == 2, "using assert"

tests/fixtures/expect_error_sample.expected.txt

Lines changed: 2 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -5,72 +5,24 @@
55

66
<FAILED::>f0 did not raise any exception
77

8-
<FAILED::>f0 did not raise Exception
9-
10-
<FAILED::>f0 did not raise ArithmeticError
11-
12-
<FAILED::>f0 did not raise ZeroDivisionError
13-
14-
<FAILED::>f0 did not raise LookupError
15-
16-
<FAILED::>f0 did not raise KeyError
17-
18-
<FAILED::>f0 did not raise OSError
19-
20-
<COMPLETEDIN::>0.03
8+
<COMPLETEDIN::>0.02
219

2210
<IT::>f1 raises Exception
2311

24-
<PASSED::>Test Passed
25-
26-
<PASSED::>Test Passed
27-
2812
<FAILED::>f1 did not raise ArithmeticError
2913

30-
<FAILED::>f1 did not raise ZeroDivisionError
31-
32-
<FAILED::>f1 did not raise LookupError
33-
34-
<FAILED::>f1 did not raise KeyError
35-
36-
<FAILED::>f1 did not raise OSError
37-
3814
<COMPLETEDIN::>0.02
3915

4016
<IT::>f2 raises Exception >> ArithmeticError >> ZeroDivisionError
4117

42-
<PASSED::>Test Passed
43-
44-
<PASSED::>Test Passed
45-
46-
<PASSED::>Test Passed
47-
48-
<PASSED::>Test Passed
49-
5018
<FAILED::>f2 did not raise LookupError
5119

52-
<FAILED::>f2 did not raise KeyError
53-
54-
<FAILED::>f2 did not raise OSError
55-
5620
<COMPLETEDIN::>0.02
5721

5822
<IT::>f3 raises Exception >> LookupError >> KeyError
5923

60-
<PASSED::>Test Passed
61-
62-
<PASSED::>Test Passed
63-
6424
<FAILED::>f3 did not raise ArithmeticError
6525

66-
<FAILED::>f3 did not raise ZeroDivisionError
67-
68-
<PASSED::>Test Passed
69-
70-
<PASSED::>Test Passed
71-
72-
<FAILED::>f3 did not raise OSError
73-
7426
<COMPLETEDIN::>0.02
7527

76-
<COMPLETEDIN::>0.11
28+
<COMPLETEDIN::>0.10

tests/test_outputs.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,12 @@ def test(self):
2323
expected = re.sub(
2424
r"(?<=<COMPLETEDIN::>)\d+(?:\.\d+)?", r"\\d+(?:\\.\\d+)?", r.read()
2525
)
26-
self.assertRegex(result.stdout.decode("utf-8"), expected)
26+
actual = result.stdout.decode("utf-8")
27+
self.assertRegex(
28+
actual,
29+
expected,
30+
"Expected Pattern:\n{}\n\nGot:\n{}\n".format(expected, actual),
31+
)
2732

2833
return test
2934

0 commit comments

Comments
 (0)