When you write unit tests in Python you can use these widgets:


self.assertEqual(var1, var2, msg=None)
self.assertNotEqual(var1, var2, msg=None)
self.assertTrue(expr, msg=None)
self.assertRaises(exception, func, para, meters, ...)

That's fine but is it "pythonic" enough? The alternative is to do with with "pure python". Eg:


assert var1 == var2, msg
assert var1 != var2, msg
assert expr, msg
try:
   func(para, meter)
   raise Exception
except exception:
   pass

I'm sure there are several benefits with using the unittest methods that I don't understand but I understand the benefits of brevity and readability. The more tests you write the more tedious it becomes to write self.assertEquals(..., ...) every time. In my own code I prefer to use simple assert statements rather than the verbose unittest alternative. Partially because I'm lazy and partially because they read better and the word assert is highlit in red in my editor so it just looks nicer from a distance.

Perhaps some much more clever people than me can explain what a cardinal sin it is to not use the unittest methods over the lazy more pythonic ones.

Incidentally, during the course of jotting down this blog I reviewed some old inherited code and changed this:


self.assertEqual(len(errors),0)

into this:


assert not errors

Isn't that just nicer to use/read/write?

Comments

Post your own comment
Victor Noagbodji

I think one of the benefit of using the module methods are the analysis you can get at the end of tests. How many passed, failed, etc...

Peter Bengtsson

No, the summary is the same me thinks. Take this example code:
http://docs.python.org/library/unittest.html#basic-example

Fiddle it so that the assertEquals on line 13 fails (e.g. change range(10) to range(11)) and run it and you'll get this result:
----------------------------------------------------------------------
Ran 3 tests in 0.001s

FAILED (failures=1)

Now, edit it again and replace the self.assertEquals with a normal assert and run it again and you'll get this result:
----------------------------------------------------------------------
Ran 3 tests in 0.001s

FAILED (failures=1)

Chris McDonough

"assert" statements aren't run when Python is run in "-O" (optimize) mode. That's about the only reason to not do this.

Adriaan Beiertz

This is the real reason why, good point.

Michal Bartoszkiewicz

assertEquals generates nicer error message when it fails – AssertionError: 'foo' != 'bar' instead of just AssertionError.

Marius Gedminas

What Chris said, with the additional argument that the tests should be run with the same Python executable and options as real code, so if you use -O to run your app, you should do it to run your tests.

(Personally, I'm not convinced, and I've never used -O to run production applications.)

Also, when you do

assert actual_result == expected_result

you get no information about what the actual result was, while self.assertEqual() prints both arguments.

For completeness' sake I have to mention that there are test runners (py.test and, I think, nose with some option turned on) that do magic to display this even when you use an assert statement.

Floris Bruynooghe

Both nose and py.test encourage just using "assert" because it is more pythonic. However the nice thing of self.assertEqual() and friends is that you don't have to write the message yourself to get a nice message in case the test fails. That's why both nose and py.test do try to show the values of the objects involved in the assert statment, relieving you in most cases of having to write the message yourself.

Peter Bengtsson

People behind nose and py.test are clever cookies but unittest (which ZopeTestCase and Django TestClient is based on) is not nose or py.test.

Besides instead of::

assert actual_result == expected_result

I think it's more sensible to write::

assert actual_result == expected_result, expected_result

but already that's excessive typing.

In general I get the feeling that assertEquals() and its companions ARE more useful in terms of output but I still stand by my opinion that writing them is disadvantageous in favor of assert.

Alex

If that test fails, you have no idea what the actual_result was, since you're only shown what it wasn't.

assertEquals( x, y ) will tell you "Foo" != "Bar", your test will simply say, "failed: Bar". Why did it fail? Being told why it failed usually saves me a minute or two, so I can squeeze more productive work into the time I have available.

Doug Latornell

As others have mentioned, the convenience of self.assertEqual() generating a nice, useful message is a strong reason for using self.assertEqual() in my mind. However, the real deal-breaker for pure Python asserts is what happens if you use parentheses in an assert statement:

Python 2.5.1 (r251:54863, Jul 23 2008, 11:00:16)
[GCC 4.0.1 (Apple Inc. build 5465)] on darwin
Type "help", "copyright", "credits" or "license" for more information.

>>> assert False, "a short message"
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AssertionError: a short message

>>> assert (False,
... "a long message that needs more than 1 line")
>>>

Peter Bengtsson

I think a short message is very pythonic. If you need to write something long you should reconsider the construct all together. Besides, it's just a string so you can write it on multiple lines with the \ backslash.

Dan Lepage

You've misplaced your parentheses:

>>> assert False, ('This is '
... 'a long message that requires me to use '
... 'several lines to write it down.')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AssertionError: This is a long message that requires me to use several lines to write it down.

Gary Godfrey

Just curious - do any packages do something like:

import sys

def myass(s):
f0 = sys._getframe(0)
assert eval(s, f0.f_globals, f0.f_locals), "myass failed: '%s'" % s

a = 10
myass('a==10')
myass('a==20')

?
Gary Godfrey
Austin, TX, USA

Peter Bengtsson

I've never seen it used in any of the big ones. eval() is very rarely used.

Brian Beck

I like the more helpful message output by assertEquals, but prefer using plain assert mostly because it's a keyword and stands out. So for a few cases I use helpers like the following (this is simplified, pretend assertEquals is unittest's):

def equals(a, b):
    assertEquals(a, b)
    return True

def test_foo():
    assert equals(x.foo, "bar")

Peter Bengtsson

You say it's simplified. Fine. If you didn't simplify wouldn't that then become:

def equals(self, a, b):
self.assertEquals(a, b)
return True
def test_foo(self):
assert self.equals(x.foo, "bar")

That looks pretty verbose to me. It doesn't make it any neater or simpler.

We have to recognize a benefit with using standard self.assertEquals() that it's kind of a "standard" in that every programmer uses the same. If you then have to debug someone elses code and that uses the non-standard equals() it becomes harder to read.

Brian Beck

No, equals is a global test helper, not a method. The tests don't need to be unittest.TestCase instances; it merely reuses unittest's assertEquals because it already exists.

assertEquals is only "standard" if you're using unittest; beyond that, you're going to have to become familiar with the codebase's test helpers anyway -- try navigating through Django's or SQLAlchemy's tests. :)

Your email will never ever be published.

Related posts

Go to top of the page