最近 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)
わかりにくいなぁ。もっと時間かけて丁寧に書かないと、知らない人には伝えられない気がしてきた。