サブクラスを作ったときに、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 かなぁ…。