How to use `assertEqual()` [or equivalent] without much baggage?

Viewed 4066

I am looking for a method (if available) that can compare two values and raise an assertion error with a meaningful message when the comparison fails.

If I use assert, the failure message does not contain what values were compared when then assertion failed.

>>> a = 3
>>> b = 4
>>> assert a == b
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AssertionError
>>> 

If I use the assertEqual() method from the unittest.TestCase package, the assertion message contains the values that were compared.

        a = 3
        b = 4
>       self.assertEqual(a, b)
E       AssertionError: 3 != 4

Note that, here, the assertion error message contains the values that were compared. That is very useful in real-life scenarios and hence necessary for me. The plain assert (see above) does not do that.

However, so far, I could use assertEqual() only in the class that inherits from unittest.TestCase and provides few other required methods like runTest(). I want to use assertEqual() anywhere, not only in the inherited classes. Is that possible?

I tried the following but they did not work.

>>> import unittest
>>> unittest.TestCase.assertEqual(a, b)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unbound method failUnlessEqual() must be called with TestCase instance as first argument (got int instance instead)
>>> 
>>> 
>>> tc = unittest.TestCase()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib64/python2.6/unittest.py", line 215, in __init__
    (self.__class__, methodName)
ValueError: no such test method in <class 'unittest.TestCase'>: runTest
>>> 

Is there any other package or library that offers similar methods like assertEqual() that can be easily used without additional constraints?

5 Answers

You have to give the assertion message by hand:

assert a == b, '%s != %s' % (a, b)
# AssertionError: 3 != 4

It is possible to create a "helper" new module that provides access to the assert functions. AssertsAccessor in this case:

from unittest import TestCase

# Dummy TestCase instance, so we can initialize an instance
# and access the assert instance methods
class DummyTestCase(TestCase):
    def __init__(self):
        super(DummyTestCase, self).__init__('_dummy')

    def _dummy(self):
        pass

# A metaclass that makes __getattr__ static
class AssertsAccessorType(type):
    dummy = DummyTestCase()

    def __getattr__(cls, key):
        return getattr(AssertsAccessor.dummy, key)

# The actual accessor, a static class, that redirect the asserts
class AssertsAccessor(object):
    __metaclass__ = AssertsAccessorType

The module needs to be created just once, and then all asserts from the unittest package are accessible, e.g.:

AssertsAccessor.assertEquals(1, 2)

AssertionError: 1 != 2

Or another example:

AssertsAccessor.assertGreater(1, 2)

Which results:

AssertionError: 1 not greater than 2

Assuming the module created for the accessor is named assertions, the common usage in the code would look like:

from assertions import AssertsAccessor

def foo(small_arg, big_arg):
    AssertsAccessor.assertGreater(big_arg, small_arg)
    # some logic here

have you looked at numpy.testing?

https://docs.scipy.org/doc/numpy-1.13.0/reference/routines.testing.html

Amongst others it has: assert_almost_equal(actual, desired[, ...]) Raises an AssertionError if two items are not equal up to desired precision.

This assert prints out actual and desired. If you ramp up precision the comparison is arbitrarily close to == (For floats)

I had similar in the past and end-up writing short custom assert which accept any condition as input.

import inspect

def custom_assert(condition):
    if not condition:
        frame = inspect.currentframe()
        frame = inspect.getouterframes(frame)[1]
        call_signature = inspect.getframeinfo(frame[0]).code_context[0].strip()

        import re
        argument = re.search('\((.+)\)', call_signature).group(1)
        if '!=' in argument:
            argument = argument.replace('!=','==')
        elif '==' in argument:
            argument = argument.replace('==','!=')
        elif '<' in argument:
            argument = argument.replace('<','>=')
        elif '>' in argument:
            argument = argument.replace('>','<=')
        elif '>=' in argument:
            argument = argument.replace('>=','<')
        elif '<=' in argument:
            argument = argument.replace('<=','>')

        raise AssertionError(argument)

if __name__ == '__main__':
    custom_assert(2 == 1)

Output:

Traceback (most recent call last):
  File "custom_assert.py", line 27, in <module>
    custom_assert(2 == 1)
  File "custom_assert.py", line 24, in custom_assert
    raise AssertionError(argument)
AssertionError: 2 != 1

The assertEqual or any other assertXxx() method expects the first argument to be an object reference. Generally we call the method as self.assertEqual(first, second, msg=None). Here self satisfies the first expected argument. To circumvent this situation we can do the following:

from unittest import TestCase as tc
def some_func():
    dummy_obj = tc()
    tc.assertEqual(dummy_obj, 123, 123, msg='Not Equal')

The reason for this behavior is Hangover from XUnit frameworks.

Related