Why do assertions in unittest use TestCase.assertEqual not the assert keyword?

Question:

Python’s built-in unittest module makes assertions with TestCase.assert* methods:

class FooTest(TestCase):
    def test_foo(self):
        self.assertEqual(1,1)
        self.assertNotEqual(1,2)
        self.assertTrue(True)

I have generally used a testrunner such as nose or py.test which allow use of the built-in assert keyword when making assertions:

assert 1 == 1
assert 1 != 2
assert True

What is the motivation for unittest’s TestCase.assert* approach and what are the strengths and weaknesses of this vs asserting with the built-in assert keyword? Are there reasons why unittest’s syntax should be favoured?

Asked By: Stephen Emslie

||

Answers:

The problem with the assert keyword is that it is optimized out, and thus ignored, when Python is run in ‘optimized’ mode (with the -O argument or with the PYTHONOPTIMIZE environment variable set.) If tests were to use assert then testing with -O would be impossible.

Additionally, the use of the assert methods makes it trivial to report about what the values involved actually were, without having to dig into the stack and the source and figure out what they were supposed to be (which, I believe, is the technique nose and py.test use for this.)

Answered By: Thomas Wouters

The main strength of this approach is providing several built in tests that are commonly performed so that one doesn’t have to write them over and over again. Additionally, assertRaises lets you customize the exact behavior of the assert by throwing an exception.

Answered By: jpm

I don’t see a concrete design decision for this. Looking at the unittest docs it states
that

the type specific equality function will be called in order to generate a more useful default error message

So I would say it is an implementation decision to help produce more meaningful errors etc.

Answered By: Nick Martin

The unittest syntax is a workaround for a weakness in how Python has implemented assert. Pytest has another workaround with assertion rewriting that largely fixes this problem in a much better way. It’s also BETTER at giving nice error messages for asserts than the assertFoo style asserts.

Use pytest and live a happier and more productive life 😛

Answered By: boxed

I did some research about the comparison between the two. From what I’ve read, including the others’ points, here are a few points of advocates of self.assertEqual :

  1. Tests will be run even with -O (optimization)
  2. Test messages are nicer looking, in older versions of python the test might halt and does not show the full stack message
  3. Can provide a log message

Advantages of assert are:

  1. flexible (for e.g., compare one dictionary is part of another dictionary: assert dict1 <= dict2 )
  2. simple & shorter to read

In my opinion,

  1. For optimization, I doubt we run it with -O, so self.assertEqual
  2. I did a small test about the two, it looks like: assert is able to terminate properly, and I am able to see the real values going into the statement (probably it’s gotten better after years of evolution, till Python 3.8). But it prints out more traceback messages, which may/may not be useful.

So I think it’s ok to use assert in unittest, but it’s definitely worth considering self.assertEqual as well

Answered By: Ruotong Jia

Usage of specific test methods like TestCase.assertEqual(x,y) instead of assert x == y can result in more clear reporting of what is wrong.

For example, consider:

from unittest import TestCase

class AssertApproachesTests(TestCase):
    def test_plain_assert(self):
        assert {"key": "value"} == {"key": "valuesss"}

    def test_rich_assert(self):
        self.assertEqual({"key": "value"}, {"key": "valuesss"})

Running the tests (in this case, using pytest) gives:

$ pytest api -k "_assert"

======================== FAILURES =========================
_________ AssertApproachesTests.test_plain_assert _________

self = <api.tests.test_asserts.AssertApproachesTests testMethod=test_plain_assert>

    def test_plain_assert(self):
>       assert {"key": "value"} == {"key": "valuesss"}
E       AssertionError: assert {'key': 'value'} == {'key': 'valuesss'}
E         Differing items:
E         {'key': 'value'} != {'key': 'valuesss'}
E         Use -v to get more diff

api/tests/test_asserts.py:5: AssertionError
_________ AssertApproachesTests.test_rich_assert __________

self = <api.tests.test_asserts.AssertApproachesTests testMethod=test_rich_assert>

    def test_rich_assert(self):
>       self.assertEqual({"key": "value"}, {"key": "valuesss"})
E       AssertionError: {'key': 'value'} != {'key': 'valuesss'}
E       - {'key': 'value'}
E       + {'key': 'valuesss'}
E       ?               +++

api/tests/test_asserts.py:8: AssertionError

Notice the "+++" in the latter output, showing what actually differs.

Answered By: Rabarberski
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.