views:

39

answers:

2

Hi all, I have a group of test cases that all should have exactly the same test done, along the lines of "Does method x return the name of an existing file?"

I thought that the best way to do it would be a base class deriving from TestCase that they all share, and simply add the test to that class. Unfortunately, the testing framework still tries to run the test for the base class, where it doesn't make sense.

class SharedTest(TestCase):
    def x(self):
        ...do test...

class OneTestCase(SharedTest):
    ...my tests are performed, and 'SharedTest.x()'...

I tried to hack in a check to simply skip the test if it's called on an object of the base class rather than a derived class like this:

    class SharedTest(TestCase):
        def x(self):
            if type(self) != type(SharedTest()):
                ...do test...
            else:
                pass

but got this error:

ValueError: no such test method in <class 'tests.SharedTest'>: runTest

First, I'd like any elegant suggestions for doing this. Second, though I don't really want to use the type() hack, I would like to understand why it's not working.

Cheers, Jivan

+1  A: 

I faced a similar problem. I couldn't prevent the test method in the base class being executed but I ensured that it did not exercise any actual code. I did this by checking for an attribute and returning immediately if it was set. This attribute was only set for the Base class and hence the tests ran everywhere else but the base class.

class SharedTest(TestCase):
    def setUp(self):
        self.do_not_run = True

    def test_foo(self):
        if getattr(self, 'do_not_run', False):
            return
        # Rest of the test body.

class OneTestCase(SharedTest):
    def setUp(self):
        super(OneTestCase, self).setUp()
        self.do_not_run = False

This is a bit of a hack. There is probably a better way to do this but I am not sure how.

Update

As sdolan says a mixin is the right way. Why didn't I see that before?

Update 2

(After reading comments) It would be nice if (1) the superclass method could avoid the hackish if getattr(self, 'do_not_run', False): check; (2) if the number of tests were counted accurately.

There is a possible way to do this. Django picks up and executes all test classes in tests, be it tests.py or a package with that name. If the test superclass is declared outside the tests module then this won't happen. It can still be inherited by test classes. For instance SharedTest can be located in app.utils and then used by the test cases. This would be a cleaner version of the above solution.

# module app.utils.test
class SharedTest(TestCase):
    def test_foo(self):
        # Rest of the test body.

# module app.tests
from app.utils import test
class OneTestCase(test.SharedTest):
    ...
Manoj Govindan
@Manoj Govindan: Because you didn't ask about it on SO :)
sdolan
This is what I coded up after asking the question. It's a bit of a hack, but pylint likes it better and I don't see that it's significantly worse than the mixin. The only down side is that it adds a non-existent test to the test count...not something that will keep me up at night.
JivanAmara
Since I'm not very familiar with the philosophy of mixins, I bounced this off a friend and he confirmed my hesitance; saying that mixins are supposed to be self-contained. I'm going with this solution since the mixin solution makes the mixin dependent on the classes which will use it.
JivanAmara
+2  A: 

You could use a mixin by taking advantage that the test runner only runs tests inheriting from unittest.TestCase (which Django's TestCase inherits from.) For example:

class SharedTestMixin(object):
    # This class will not be executed by the test runner (it inherits from object, not unittest.TestCase.
    # If it did, assertEquals would fail , as it is not a method that exists in `object`
    def test_common(self):
         self.assertEquals(1, 1)


class TestOne(TestCase, SharedTestMixin):
    def test_something(self):
         pass

    # test_common is also run

class TestTwo(TestCase, SharedTestMixin):
    def test_another_thing(self):
        pass

    # test_common is also run

For more information on why this works do a search for python method resolution order and multiple inheritance.

sdolan
+1. This is the clean way to go about it.
Manoj Govindan
Thanks for your reply,I started out this way, and didn't like that I had a class that called methods which it didn't have. Pylint also barks over this, and I use pylint heavily.
JivanAmara
@jivanamara: You don't need to call `test_common` within your `TestOne` and `TestTwo` test classes. It's "mixed in" as it were part of the class through multiple inheritence.
sdolan
@Manoj, I agree, nice solution. +1
rebus
@sdolan: I understand that, what bugs me is SharedTestMixin calling self.assertEquals when it neither declares it nor inherits it.
JivanAmara