Skip to content

Commit d0a4e86

Browse files
committed
initial version
1 parent 97894f1 commit d0a4e86

File tree

3 files changed

+617
-0
lines changed

3 files changed

+617
-0
lines changed

TODO.md

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
* richer assertion messages, especially for assert_equal
2+
* better Docstrings
3+
* Doctests
4+
* missing assertions:
5+
* assert_not_is_instance
6+
* assert_warns
7+
* assert_warns_regex
8+
* assert_not_almost_equal
9+
* assert_greater
10+
* assert_greater_equal
11+
* assert_less
12+
* assert_less_equal
13+
* assert_not_regex
14+
* assert_count_equal
15+
* assert_almost_equal: add delta argument
16+
* unit test cleanup and completion: for every assertion, there should be at
17+
least three tests: test success, test failure with default message, test
18+
failure with custom message

asserts.py

+254
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
"""
2+
Rich Assertions.
3+
4+
This module contains several rich standard assertions that can be used in unit
5+
tests and in implementations. Users are encouraged to define their own
6+
assertions, possibly using assertions from this package as a basis.
7+
8+
"""
9+
10+
from datetime import datetime, timedelta
11+
import re
12+
13+
14+
def fail(msg=None):
15+
"""Raise an AssertionError with the given message."""
16+
raise AssertionError(msg)
17+
18+
19+
def assert_true(expr, msg=None):
20+
"""Fail the test unless the expression is truthy."""
21+
if not expr:
22+
if not msg:
23+
msg = repr(expr) + " is not true"
24+
fail(msg)
25+
26+
27+
def assert_false(expr, msg=None):
28+
"""Fail the test unless the expression is falsy."""
29+
if expr:
30+
if not msg:
31+
msg = repr(expr) + " is not false"
32+
fail(msg)
33+
34+
35+
def assert_boolean_true(expr, msg=None):
36+
"""Fail the test unless the expression is the constant True."""
37+
assert_is(True, expr, msg)
38+
39+
40+
def assert_boolean_false(expr, msg=None):
41+
"""Fail the test unless the expression is the constant False."""
42+
assert_is(False, expr, msg)
43+
44+
45+
def assert_is_none(expr, msg=None):
46+
"""Fail if actual is not None."""
47+
if expr is not None:
48+
fail(msg or "{!r} is not None".format(expr))
49+
50+
51+
def assert_is_not_none(expr, msg=None):
52+
"""Fail if actual is None."""
53+
if expr is None:
54+
fail(msg or "{!r} is None".format(expr))
55+
56+
57+
def assert_equal(first, second, msg=None):
58+
"""Fail if actual does not equal expected, as determined by the '=='
59+
operator.
60+
61+
"""
62+
if not first == second:
63+
fail(msg or "{!r} != {!r}".format(first, second))
64+
65+
66+
def assert_not_equal(first, second, msg=None):
67+
"""Fail if the two objects are equal as determined by the '=='
68+
operator.
69+
70+
"""
71+
if first == second:
72+
fail(msg or "{!r} == {!r}".format(first, second))
73+
74+
75+
def assert_almost_equal(first, second, places=7, msg=None):
76+
"""Fail if the two objects are unequal when rounded."""
77+
if round(second - first, places) != 0:
78+
fail(msg or "{!r} != {!r} within {} places".format(first, second,
79+
places))
80+
81+
82+
def assert_regex(text, regex, msg=None):
83+
"""Fail if actual does not match the regular expression expected."""
84+
if not re.match(regex, text):
85+
fail(msg or "{!r} does not match {!r}".format(text, regex))
86+
87+
88+
def assert_is(first, second, msg=None):
89+
"""Fail if the two objects are not the same object."""
90+
if first is not second:
91+
fail(msg or "{!r} is not {!r}".format(first, second))
92+
93+
94+
def assert_is_not(first, second, msg=None):
95+
"""Fail if the two objects are the same object."""
96+
if first is second:
97+
fail(msg or "{!r} is {!r}".format(first, second))
98+
99+
100+
def assert_in(first, second, msg=None):
101+
"""Fail if an element is not in a collection."""
102+
msg = msg or "{!r} not in {!r}".format(first, second)
103+
assert_true(first in second, msg)
104+
105+
106+
def assert_not_in(first, second, msg=None):
107+
"""Fail if an element is in a collection."""
108+
msg = msg or "{!r} is in {!r}".format(first, second)
109+
assert_false(first in second, msg)
110+
111+
112+
def assert_between(lower_bound, upper_bound, actual, msg=None):
113+
if not lower_bound <= actual <= upper_bound:
114+
msg = msg or "{!r} is not between {} and {}".format(
115+
actual, lower_bound, upper_bound)
116+
fail(msg)
117+
118+
119+
def assert_is_instance(obj, cls, msg=None):
120+
if not isinstance(obj, cls):
121+
msg = (msg if msg is not None else
122+
repr(obj) + " is of " + repr(obj.__class__) +
123+
" not of " + repr(cls))
124+
raise AssertionError(msg)
125+
126+
127+
def assert_has_attr(obj, attribute):
128+
if not hasattr(obj, attribute):
129+
raise AssertionError(repr(obj) + " is missing attribute '" +
130+
attribute + "'")
131+
132+
133+
_EPSILON_SECONDS = 5
134+
135+
136+
def assert_datetime_about_now(actual):
137+
now = datetime.now()
138+
lower_bound = now - timedelta(seconds=_EPSILON_SECONDS)
139+
upper_bound = now + timedelta(seconds=_EPSILON_SECONDS)
140+
msg = repr(actual) + " is not close to current " + repr(now)
141+
assert_between(lower_bound, upper_bound, actual, msg)
142+
143+
144+
def assert_datetime_about_now_utc(actual):
145+
now = datetime.utcnow()
146+
lower_bound = now - timedelta(seconds=_EPSILON_SECONDS)
147+
upper_bound = now + timedelta(seconds=_EPSILON_SECONDS)
148+
msg = repr(actual) + " is not close to current UTC " + repr(now)
149+
assert_between(lower_bound, upper_bound, actual, msg)
150+
151+
152+
class AssertRaisesContext(object):
153+
154+
def __init__(self, exception, msg=None):
155+
self.exception = exception
156+
self.msg = msg
157+
self._exception_name = getattr(exception, "__name__", str(exception))
158+
self._tests = []
159+
160+
def __enter__(self):
161+
pass
162+
163+
def __exit__(self, exc_type, exc_val, exc_tb):
164+
if not exc_type:
165+
fail(self.msg or "{} not raised".format(self._exception_name))
166+
if not issubclass(exc_type, self.exception):
167+
return False
168+
for test in self._tests:
169+
test(exc_val)
170+
return True
171+
172+
def add_test(self, cb):
173+
"""Add a test callback.
174+
175+
This callback is called after determining that the right exception
176+
class was thrown.
177+
178+
"""
179+
self._tests.append(cb)
180+
181+
182+
def assert_raises(exception, msg=None):
183+
"""Fail unless a specific exception is thrown inside the context.
184+
185+
If a different type of exception is thrown, it will not be caught.
186+
187+
"""
188+
return AssertRaisesContext(exception, msg)
189+
190+
191+
def assert_raises_regex(exception, regex, msg=None):
192+
"""Like assert_raises, but also ensures that the exception message
193+
matches a regular expression.
194+
195+
The regular expression can be a regular expression string or object.
196+
197+
"""
198+
199+
def test(exc):
200+
assert_regex(str(exc), regex, msg)
201+
202+
context = AssertRaisesContext(exception, msg)
203+
context.add_test(test)
204+
return context
205+
206+
207+
def assert_raises_errno(exception, errno, msg=None):
208+
"""Fail unless the context throws an exception of class exc_cls and
209+
its errno attribute equals the supplied one.
210+
211+
"""
212+
213+
def check_errno(exc):
214+
assert_equal(errno, exc.errno, msg)
215+
context = AssertRaisesContext(exception, msg)
216+
context.add_test(check_errno)
217+
return context
218+
219+
220+
def assert_succeeds(exception):
221+
"""Fail if an exception of the provided type is raised within the context.
222+
223+
This assertion should be used for cases, where successfully running a
224+
function signals a successful test, and raising the exception of a
225+
certain type signals a test failure. All other raised exceptions are
226+
passed on and will usually still result in a test error. This can be
227+
used to signal intent of a function call.
228+
229+
>>> l = ["foo", "bar"]
230+
>>> with assert_succeeds(ValueError):
231+
... l.index("foo")
232+
...
233+
>>> def raise_value_error():
234+
... raise ValueError()
235+
...
236+
>>> with assert_succeeds(ValueError):
237+
... raise ValueError()
238+
...
239+
Traceback (most recent call last):
240+
...
241+
AssertionError: ValueError was raised during the execution of raise_value_error
242+
243+
"""
244+
245+
class _AssertSucceeds(object):
246+
247+
def __enter__(self):
248+
pass
249+
250+
def __exit__(self, exc_type, exc_val, exc_tb):
251+
if exc_type and issubclass(exc_type, exception):
252+
fail(exception.__name__ + " was unexpectedly raised")
253+
254+
return _AssertSucceeds()

0 commit comments

Comments
 (0)