最近 mock ライブラリを使うようになりました。
能書き
(ここは単体テストとモックの意義が分かってる人には、価値ゼロです。)
単体テストというのは、本来、あるコンポーネントの依存先に影響されないように、対象をテストします。が、これまでは比較的てきとーで、依存先のテストが通っていれば、依存先を完全に分離せずにやってきました。
これには2つ問題があって、(1) そもそもそれは単体テストではない、(2) 依存先が外部だったらどうするんだ、と。例えば、Twitter からタイムラインをとってくる、とかですね。
想定するテスト対象
def get_user_timeline(user) """タイムラインをとってきて、辞書で返す""" twitter = Twitter() response = twitter.get_timeline(user.id, user.access_token) timeline = [{'text': tweet.text} for tweet in response['tweets']] return timeline
辞書にレンダリングを分離すべきとかありますが、いまは Twitter から取ってくる箇所だけ考えます。
テストの度に本当に Twitter にアクセスしていては単体テストになりません。ってなわけで、 mock ライブラリを使います。
インストール
easy_install mock
で、おk。
Mock クラス
Mock のインスタンスのプロパティは適当にやってくれます。事前に定義する必要はありません(定義することはできます)。しかも foo.bar にアクセスするとき、いつも同じオブジェクトが返されます。
>>> import mock >>> x = mock.Mock() >>> x.foo>>> x.foo >>> x.foo == x.foo True
Mock インスタンスは常に呼び出し可能です。
>>> y = mock.Mock() >>> y()>>> y.hoge()
テストを書いてみる
get_user_timeline 関数の仕事は、(1) 引数 user を twitter.get_timeline() に渡すこと、(2) その戻り値から辞書を作成すること、です。user オブジェクトの作成や get_timeline() で何が起こっているかは、別の単体テストでやることです。なので、 user と get_timeline はモックにしちゃいましょう。
任意のモジュール内の、任意のクラスのメソッドを入れ替えるときは、patch 関数を使います。
class GetUserTimelineTest(unittest.TestCase): def test(self): # モックのコンテクストで実行 # 動作を変えたいメソッドを文字列で指定する。 with mock.patch('mymodule.Twitter.get_timeline') as m: # レスポンスで使うダミーの tweet を作る tweet0 = mock.Mock() tweet1 = mock.Mock() # get_timelineメソッドを呼び出したときの戻り値をモック m.return_value = {"tweets":[tweet0, tweet1]} # 引数で渡すオブジェクトもモックにする user = mock.Mock() # 単体テスト対象関数呼び出し result = get_user_timeline(user) # モックが1度呼ばれていることを確認 self.assertEqual(m.call_count, 1) # モックが呼ばれたときの引数を確認 self.assertEqual(m.call_args, ((user.id, user.access_token),{})) # 戻り値の確認 self.assertEqual(len(result), 2) self.assertEqual(result[0]['text'], tweet0.text)
わかりにくいなぁ。もっと時間かけて丁寧に書かないと、知らない人には伝えられない気がしてきた。