Reputation: 5590
Frequently when I am writing code I add assertions for my own sanity, specifically for when I am first writing the code to ensure I do not implement it with bugs (I understand assertions are ignored in production builds of code). Whenever I write an assertion, I find myself verbosely writing something along the lines of:
assert a == b, f"{a} != {b}"
So that if my assertion fails, I have some way of interpreting why. Unfortunately, this often clutters my code, as if the variable names are long (or I am in nested structurees), adding the message at the end often makes the line too long to be consistent with PEP 8. So I spend the time breaking it up over several lines, which also makes a basic assert a multi-lined statement.
unittest
provides helpful assert methods like assertEqual
which shorten this work, make it less prone to me leaving out the error message, and allows me to continue on with my work rather than worrying about assertions. The only two options I can come up with are:
unittest.TestCase()
each time I want to make an assertion.The problem with number 2 is it is unbelievably slow:
>>> min(timeit.repeat('unittest.TestCase().assertEqual(a, b)', setup='import unittest; a = 1; b = 1'))
1.7452130000000352
>>> min(timeit.repeat('assert a == b, f"{a} != {b}"', setup='a = 1; b = 1'))
0.01641979999999421
Which is a hint that this would be bad practice ie. unittest
should be used for unit testing, not generic assertions.
I'll admit this question is a bit nit-picky, but StackOverflow often has the ability the enlighten me on libraries/ideas that make programming faster, safer, and better, so I'd like to know: is there a better (built-in?) method for verbose assertions, or should I simply continue writing them verbosely as I have before?
Upvotes: 0
Views: 817
Reputation: 4368
You could adapt some code from unittest
to create some functions which do not require to instantiate a full-blown TestCase, here is a small example :
import difflib
import pprint
from unittest.util import safe_repr, _common_shorten_repr
class Asserter: # this class consists mostly of code from `unittest.TestCase`
def assertEqual(self, first, second, msg=None):
assertion_func = self._getAssertEqualityFunc(first, second)
assertion_func(first, second, msg=msg)
def _getAssertEqualityFunc(self, first, second):
if type(first) is type(second):
asserter = {
dict: self.assertDictEqual,
# ...
}.get(type(first))
if asserter is not None:
return asserter
return self._baseAssertEqual
def _baseAssertEqual(self, first, second, msg=None):
"""The default assertEqual implementation, not type specific."""
if not first == second:
standardMsg = '%s != %s' % _common_shorten_repr(first, second)
raise AssertionError('%s : %s' % (standardMsg, msg))
def assertDictEqual(self, d1, d2, msg=None):
self.assertIsInstance(d1, dict, 'First argument is not a dictionary')
self.assertIsInstance(d2, dict, 'Second argument is not a dictionary')
if d1 != d2:
standardMsg = '%s != %s' % _common_shorten_repr(d1, d2)
diff = ('\n' + '\n'.join(difflib.ndiff(
pprint.pformat(d1).splitlines(),
pprint.pformat(d2).splitlines())))
standardMsg = standardMsg + diff
raise AssertionError('%s : %s' % (standardMsg, msg))
def assertIsInstance(self, obj, cls, msg=None):
if not isinstance(obj, cls):
standardMsg = '%s is not an instance of %r' % (safe_repr(obj), cls)
raise AssertionError('%s : %s' % (standardMsg, msg))
__ASSERTER = Asserter() # only one required
assertEqual = __ASSERTER.assertEqual # get a reference to the method
def main():
assertEqual({'a': 1, 'b': 2},
{'a': 1, 'b': 456789})
if __name__ == "__main__":
main()
which produces :
Traceback (most recent call last):
File "/home/stack_overflow/so70675609.py", line 56, in <module>
main()
File "/home/stack_overflow/so70675609.py", line 51, in main
assertEqual({'a': 1, 'b': 2},
File "/home/stack_overflow/so70675609.py", line 11, in assertEqual
assertion_func(first, second, msg=msg)
File "/home/stack_overflow/so70675609.py", line 38, in assertDictEqual
raise AssertionError('%s : %s' % (standardMsg, msg))
AssertionError: {'a': 1, 'b': 2} != {'a': 1, 'b': 456789}
- {'a': 1, 'b': 2}
? ^
+ {'a': 1, 'b': 456789}
? ^^^^^^
: None
Upvotes: 2