サブクラスを作ったときに、TestBedTestCase の setUp と tearDown を明示的に呼び出さないといけないのがちょっと面倒です。
class MyTestCase(TestBedTestCase):
def setUp(self):
super(MyTestCase, class).setUp()
# … 準備 ...
def tearDown(self):
# … 片付け …
super(MyTestCase, class).tearDown()
Pythonic なので、これはこれで別に問題ないんだろうけど、これ結構めんどくさい。あと、nose 使えば、モジュールごとに setUp を定義しておくと、こういうのもやってくれるはずだけど、老害じじいは nose とか分かんないので、とりあえずスルー。
という訳で、ベースクラスの setUp と tearDown を自動的に呼び出すようにして使っています。setUp の呼び出し順は、基底クラスの setUp の後で、派生クラスの setUp。tearDown は逆です。それぞれ、C++ なんかのコンストラクタとデストラクタと同じにしました。
class CascadingTestCaseMeta(type):
"""Metaclass for TestCase class
This metaclass make setUp method calls all setUp methods
in base classes and then calls defined setUp method.
Likewise tearDown but in opposite order.
"""
def __init__(cls, name, bases, ns):
for method_name, reverse in [('setUp',False), ('tearDown', True)]:
setattr(cls, method_name,
cls._create_method(method_name, bases, ns, reverse=False))
@classmethod
def _create_method(self, method_name, bases, ns, reverse=True):
"""return a method that calls all methods with given
name in class hierarchy
"""
# create method sequence in parent and current classes
methods = [getattr(base, method_name, lambda self: None)
for base in bases]
methods.append(ns.get(method_name, lambda self: None))
# reverse order if necessary
if reverse:
methods.reverse()
# define method to call all methods
def call_methods(self):
for method in methods:
method(self)
# return the caller method
return call_methods
class CascadingTestCase(unittest.TestCase):
__metaclass__ = CascadingTestCaseMeta
class TestBedTestCase(CascadingTestCase):
# 以下、@tokibito さんのコード
TestBedTestCase のメタクラスを直接指定していないのは、testbed の setUp よりも「前に」何かしたいときと、「後に」何かしたいときがあると思ったからです(でも今のところない…)。
class MyTestCase(TestBedTestCase, MyBaseTestCase):
def setUp(self):
...
と定義すると、TestBedTestCase.setUp, MyBase.setUp, MyTestCase.setUp の順に呼び出されます。
TestBedTestCase のsetUp と tearDown が呼び出されるのが明示的じゃない気がしますが、そこの明示性は諦めました。
やっぱ nose かなぁ…。