I've noticed some really nice things, and some really horrible things.
Nice thing: Python doesn't let you hash mutable collections:
>>> hash(set()) # mutable
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'set'
>>> hash(frozenset()) # immutable
133156838395276
>>> hash([1,2,3]) # mutable
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'
>>> hash((1,2,3)) # immutable
2528502973977326415
This is great, because if you hash something, it tends to be in order to store it in a set or dict or something, and if you change the thing after hashing it, you've broken the contract of the set/dict.
Awesome. Guiding you toward doing the right thing.
Horrible thing: unittest1 has a method: assertRaises. Great - a simple, concise way of asserting that a single call raises an exception.
Except I wanted to assert that something is raised, without specifying what, so I tried skipping the exception type from the call to assertRaises.
>>> import unittest
>>>
>>> def does_raise():
... raise Exception()
...
>>> def does_not_raise():
... return 1
...
>>> class TC(unittest.TestCase):
... def test_raises(self):
... self.assertRaises(Exception, does_raise)
... def test_does_not_raise(self):
... self.assertRaises(does_not_raise)
...
>>> unittest.main()
..
----------------------------------------------------------------------
Ran 2 tests in 0.002s
OK
Except I wanted to assert that something is raised, without specifying what, so I tried skipping the exception type from the call to assertRaises.
>>> import unittest
>>>
>>> def does_raise():
... raise Exception()
...
>>> def does_not_raise():
... return 1
...
>>> class TC(unittest.TestCase):
... def test_raises(self):
... self.assertRaises(Exception, does_raise)
... def test_does_not_raise(self):
... self.assertRaises(does_not_raise)
...
>>> unittest.main()
..
----------------------------------------------------------------------
Ran 2 tests in 0.002s
OK
WAIT! I called self.assertRaises with a method which *does not raise*! It's in the name and everything! It feels like if the first arg to assertRaises is a callable, and not a type, unittest could perhaps at least warn, if not throw.
If I hadn't run this test before implementing the backing code to see it fail, and seen it passed, I would have blindly been assuming that my code correctly raised an exception (as my test showed!) when it didn't!
1: unittest from Python 2.7, backported as unittest2 before
Checking for being callable wouldn't help - types are callable, thus Exception is too: classes are their own constructors.
ReplyDeleteIt could, however, check that does_not_raise is a subclass of Exception, or a tuple of such. In fact it probably should, IMO. Ask Guido if you should submit a patch!
FWIW, the single-argument version of assertRaises is for use with the 'with' context construct, which is a really much nicer way of expressing it:
with self.assertRaises(SomeException):
do_something()
Alex, some fine thoughts indeed :) I've put together a patch, and in doing (by running python's own tests), have found that some of python itself's tests make the mistake of omitting the exception type parameter!
ReplyDelete