不少測試都須要在啓動的時候作一些事情,而後在結束的時候再把作的事情給清理了。通常的作法是把這些動做寫在setUp和tearDown的兩個方法裏,單元測試框架會負責在開始和結束的時候調用這兩個方法。app
class SomeTest(unittest.case.TestCase): def setUp(self): super(SomeTest, self).setUp() setup_db() def tearDown(self): clean_db() super(SomeTest, self).tearDown()
這種寫法有好幾個煩人的地方。首先是Logic Locality很差的問題:setup_db()和clean_db()是分在兩處的,中間可能隔着很長一段代碼。從視覺上沒法直觀的指導setup_db()原來和clean_db()是一對的。
其次是很難重用的問題(上綱上線的話就是複雜度很差管理的問題),爲了不重複寫公共的setUp和tearDown通常會抽取出一個UsingDbTest這樣的基類。這樣全部的子類必須記得super(xxx, self).setUp(),不然就會覆蓋掉基類的setUp。其次在須要有多個維度的東西須要複用的時候,好比有一個UsingDbTest的基類,有一個UsingNetworkTest的基類,難道讓子類繼承兩個基類麼(mixin是否是有點過於複雜了?)。
使用generator能夠很好的解決這個問題。首先咱們寫一個方法來作setUp和tearDown:框架
@contextlib.contextmanager def using_db(): setup_db() yield clean_db()
這樣能夠很是清晰地知道setup_db和clean_db是一對的。而後再把這個小的上下文附着到主測試邏輯上:單元測試
def apply_context(test, contextmanager): contextmanager.__enter__() test.addCleanup(lambda: contextmanager.__exit__(None, None, None)) class SomeTest(unittest.case.TestCase): def setUp(self): apply_context(self, using_db())
這裏利用了單元測試的addCleanup的特性,把tearDown轉化爲回調在setUpd的時候就設置好。利用這種方式,咱們能夠用組合的方式而不是繼承的方式來複用公共的setUp和tearDown的邏輯了。測試