[Sorry for accidentally cross-posting this to python-list] While working on a test suite for unittest these past few weeks, I've run across some behaviours that, while not obviously wrong, don't strike me as quite right, either. Submitted for your consideration: 1) TestCase.tearDown() is only run if TestCase.setUp() succeeded. It seems to me that tearDown() should always be run, regardless of any failures in setUp() or the test method itself. The case I'm considering is something like this, ie, a multi-part setUp():
def setUp(self) lock_file(testfile) # open_socket(), connect_to_database(), etc
something_that_raises_an_exception()
def tearDown(self): if file_is_locked(testfile): unlock_file(testfile)
In this pseudo-code example, the file won't be unlocked if some later operation in setUp() raises an exception. I propose that TestCase.run() be changed to always run tearDown(), even if setUp() raise an exception. I'm undecided if this is a new feature (so it should go in for 2.6) or a bug fix; I'm leaning toward the latter. 2) The TestLoader.testMethodPrefix attribute currently allows anything to be assigned to it, including invalid objects and the empty string. While the former will cause errors to be raised when one of TestLoader's loadTestsFrom*() methods is called, the empty string will raise no exception; rather, the loadTestsFrom*() methods will interpret every possible attribute as being a test method, e.g., meaning you get things like assertEqual(), failUnlessEqual(), etc, when TestLoader.loadTestsFromTestCase() is run. I propose protecting testMethodPrefix with a property that validates the assigned value, restricting input to non-empty instances of str. I see this as a bug fix that should go in before 2.5-final. 3) TestLoader.loadTestsFromTestCase() accepts objects that are not test cases and will happily look for appropriately-named methods on any object you give it. This flexibility should be documented, or proper input validation should be done (a bug fix for 2.5). 4) TestLoader.loadTestsFromName() (and by extension, loadTestsFromNames(), too) raises an AttributeError if the name is the empty string because -- as it correctly asserts -- the object does not contain an attribute named ''. I recommend that this be tested for and ValueError be raised (bug fix for 2.5). This of course leads into the question of how much input validation should be done on these names. Should loadTestsFrom{Name,Names}() make sure the names are valid attribute names, or is this overkill? 5) When TestLoader.loadTestsFrom{Name,Names}() are given a name that resolves to a classmethod on a TestCase subclass, the method is not invoked. From the docs:
The specifier name is a ``dotted name'' that may resolve either to a module, a test case class, a TestSuite instance, a test method within a test case class, or a callable object which returns a TestCase or TestSuite instance.
It is not documented which of these tests takes priority: is the classmethod "a test method within a test case class" or is it a callable? The same issue applies to staticmethods as well. Once I get answers to these questions, I can finish off the last few bits of the test suite and have it ready for 2.5-final. Thanks, Collin Winter
Collin Winter wrote:
[Sorry for accidentally cross-posting this to python-list]
While working on a test suite for unittest these past few weeks, I've run across some behaviours that, while not obviously wrong, don't strike me as quite right, either. Submitted for your consideration:
1) TestCase.tearDown() is only run if TestCase.setUp() succeeded. It seems to me that tearDown() should always be run, regardless of any failures in setUp() or the test method itself. The case I'm considering is something like this, ie, a multi-part setUp():
def setUp(self) lock_file(testfile) # open_socket(), connect_to_database(), etc
something_that_raises_an_exception()
def tearDown(self): if file_is_locked(testfile): unlock_file(testfile)
In this pseudo-code example, the file won't be unlocked if some later operation in setUp() raises an exception. I propose that TestCase.run() be changed to always run tearDown(), even if setUp() raise an exception.
I'm undecided if this is a new feature (so it should go in for 2.6) or a bug fix; I'm leaning toward the latter.
On this point, I believe the current behaviour is correct and should be kept. If setUp() fails internally (such that the test step isn't going to be run) it needs to cleanup after itself, just like any other function. That way, the tearDown() method is allowed to assume that setUp() succeeded completely, instead of having to guard against the possibility that setUp() may have died partway through. IOW, I consider the setUp() method in your example to be buggy. It should be written something like this: def setUp(self) lock_file(testfile) # open_socket(), connect_to_database(), etc try: something_that_may_raise_an_exception() except: unlock_file(testfile) raise def tearDown(self): unlock_file(testfile) Alternatively, someone who prefers your style (with a tearDown() method that can handle a partially executed call to the setUp() method), can just write it as: def setUp(self) try: lock_file(testfile) # open_socket(), connect_to_database(), etc something_that_may_raise_an_exception() except: self.tearDown() raise def tearDown(self): if file_is_locked(testfile): unlock_file(testfile) Consider the parallel to PEP 343's __enter__ and __exit__ methods - __exit__ is allowed to assume that it will only be called if __enter__ succeeded, because that is part of the semantics of the with statement. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia --------------------------------------------------------------- http://www.boredomandlaziness.org
On 8/19/06, Nick Coghlan <ncoghlan@gmail.com> wrote:
Alternatively, someone who prefers your style (with a tearDown() method that can handle a partially executed call to the setUp() method), can just write it as:
def setUp(self) try: lock_file(testfile) # open_socket(), connect_to_database(), etc something_that_may_raise_an_exception() except: self.tearDown() raise
def tearDown(self): if file_is_locked(testfile): unlock_file(testfile)
Consider the parallel to PEP 343's __enter__ and __exit__ methods - __exit__ is allowed to assume that it will only be called if __enter__ succeeded, because that is part of the semantics of the with statement.
I can accept that. Any thoughts on the other four items? Collin Winter
Collin Winter wrote:
Any thoughts on the other four items?
Generally speaking, I'm not sure it's worth the effort to do the input validation. All of the cases you suggest ruling out do indeed seem to be insane, but someone may have defined a subclass that does something based on the current behaviour. Given the timing, I suggest just documenting and testing the current behaviour for Python 2.5, and then creating a tracker item targeting 2.6 to cover the lack of sanity checks (and whether or not more should be added). Some of the problems have multiple possible solutions (e.g., using inspect.getargspec() to check for zero-argument callables when searching for test methods, instead of trying to call everything unittest can lay its hands on) so we need more time to think about them. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia --------------------------------------------------------------- http://www.boredomandlaziness.org
On 8/19/06, Collin Winter <collinw@gmail.com> wrote:
1) TestCase.tearDown() is only run if TestCase.setUp() succeeded. It seems to me that tearDown() should always be run, regardless of any failures in setUp() or the test method itself.
The case I'm considering is something like this, ie, a multi-part setUp():
def setUp(self) lock_file(testfile) # open_socket(), connect_to_database(), etc
something_that_raises_an_exception()
def tearDown(self): if file_is_locked(testfile): unlock_file(testfile)
I'm undecided if this is a new feature (so it should go in for 2.6) or a bug fix; I'm leaning toward the latter.
Most existing tearDown() code assumes that setUp() has been successfully called -- after all, that's what the docs say. It's also the behaviour of other xUnit frameworks. If this change is made, people will have to go through their tearDown() methods and add checks like the one in your example in order to make their tests correct. Further, it won't be obvious that their tearDown() methods are incorrect until something happens to make their setUp() methods incorrect. I don't think this change is a good one. However, if it does go in, it definitely shouldn't go in as a bug fix. cheers, jml
participants (3)
-
Collin Winter -
Jonathan Lange -
Nick Coghlan