Deriving a class from TestCase throws two errors
Question:
I have some basic setup/teardown code that I want to reuse in a whole bunch of unit tests. So I got the bright idea of creating some derived classes to avoid repeating code in every test class.
In so doing, I received two strange errors. One, I cannot solve. Here is the unsolvable one:
AttributeError: 'TestDesktopRootController' object has no attribute '_testMethodName'
Here is my base class:
import unittest
import twill
import cherrypy
from cherrypy._cpwsgi import CPWSGIApp
class BaseControllerTest(unittest.TestCase):
def __init__(self):
self.controller = None
def setUp(self):
app = cherrypy.Application(self.controller)
wsgi = CPWSGIApp(app)
twill.add_wsgi_intercept('localhost', 8080, lambda : wsgi)
def tearDown(self):
twill.remove_wsgi_intercept('localhost', 8080)
And here is my derived class:
import twill
from base_controller_test import BaseControllerTest
class TestMyController(BaseControllerTest):
def __init__(self, args):
self.controller = MyController()
BaseControllerTest.__init__(self)
def test_root(self):
script = "find 'Contacts'"
twill.execute_string(script, initial_url='http://localhost:8080/')
The other strange error is:
TypeError: __init__() takes exactly 1 argument (2 given)
The “solution” to that was to add the word “args” to my __init__
function in the derived class. Is there any way to avoid that?
Remember, I have two errors in this one.
Answers:
In BaseController
‘s __init__
you need to call unittest.TestCase
‘s __init__
just like you did in TestMyController
.
The call to construct a TestCase from the framework may be passing an argument. The best way to handle this for deriving classes is:
class my_subclass(parentclass):
def __init__(self, *args, **kw):
parentclass.__init__(self, *args, **kw)
...
It’s because you’re overriding __init__()
incorrectly. Almost certainly, you don’t want to override __init__()
at all; you should do everything in setUp()
. I’ve been using unittest
for >10 years and I don’t think I’ve ever overridden __init__()
.
However, if you really do need to override __init__()
, remember that you don’t control where your constructor is called — the framework calls it for you. So you have to provide a signature that it can call. From the source code (unittest/case.py
), that signature is:
def __init__(self, methodName='runTest'):
The safe way to do this is to accept any arguments and just pass ’em up to the base class. Here is a working implementation:
class BaseTest(unittest.TestCase):
def __init__(self, *args, **kwargs):
unittest.TestCase.__init__(self, *args, **kwargs)
def setUp(self):
print "Base.setUp()"
def tearDown(self):
print "Base.tearDown()"
class TestSomething(BaseTest):
def __init__(self, *args, **kwargs):
BaseTest.__init__(self, *args, **kwargs)
self.controller = object()
def test_silly(self):
self.assertTrue(1+1 == 2)
I have some basic setup/teardown code that I want to reuse in a whole bunch of unit tests. So I got the bright idea of creating some derived classes to avoid repeating code in every test class.
In so doing, I received two strange errors. One, I cannot solve. Here is the unsolvable one:
AttributeError: 'TestDesktopRootController' object has no attribute '_testMethodName'
Here is my base class:
import unittest
import twill
import cherrypy
from cherrypy._cpwsgi import CPWSGIApp
class BaseControllerTest(unittest.TestCase):
def __init__(self):
self.controller = None
def setUp(self):
app = cherrypy.Application(self.controller)
wsgi = CPWSGIApp(app)
twill.add_wsgi_intercept('localhost', 8080, lambda : wsgi)
def tearDown(self):
twill.remove_wsgi_intercept('localhost', 8080)
And here is my derived class:
import twill
from base_controller_test import BaseControllerTest
class TestMyController(BaseControllerTest):
def __init__(self, args):
self.controller = MyController()
BaseControllerTest.__init__(self)
def test_root(self):
script = "find 'Contacts'"
twill.execute_string(script, initial_url='http://localhost:8080/')
The other strange error is:
TypeError: __init__() takes exactly 1 argument (2 given)
The “solution” to that was to add the word “args” to my __init__
function in the derived class. Is there any way to avoid that?
Remember, I have two errors in this one.
In BaseController
‘s __init__
you need to call unittest.TestCase
‘s __init__
just like you did in TestMyController
.
The call to construct a TestCase from the framework may be passing an argument. The best way to handle this for deriving classes is:
class my_subclass(parentclass):
def __init__(self, *args, **kw):
parentclass.__init__(self, *args, **kw)
...
It’s because you’re overriding __init__()
incorrectly. Almost certainly, you don’t want to override __init__()
at all; you should do everything in setUp()
. I’ve been using unittest
for >10 years and I don’t think I’ve ever overridden __init__()
.
However, if you really do need to override __init__()
, remember that you don’t control where your constructor is called — the framework calls it for you. So you have to provide a signature that it can call. From the source code (unittest/case.py
), that signature is:
def __init__(self, methodName='runTest'):
The safe way to do this is to accept any arguments and just pass ’em up to the base class. Here is a working implementation:
class BaseTest(unittest.TestCase):
def __init__(self, *args, **kwargs):
unittest.TestCase.__init__(self, *args, **kwargs)
def setUp(self):
print "Base.setUp()"
def tearDown(self):
print "Base.tearDown()"
class TestSomething(BaseTest):
def __init__(self, *args, **kwargs):
BaseTest.__init__(self, *args, **kwargs)
self.controller = object()
def test_silly(self):
self.assertTrue(1+1 == 2)