urllib2.urlopen()しているやつをMock化してテストを書いたときに若干はまったので備忘録として。
まずはやってみる
テスト対象コード
実際はいろいろやっているコードですが、シンプルにするためにとりあえず以下のような感じに。
target.py
import urllib2
def get(url):
return urllib2.urlopen().read()
悪い例
安易に最初に書いたコードがこれです。一件すると問題なさそうですが、これを実行すると1回目のアサーションは通りますが、2回目のアサーションでAssertionError: 'hoge' != ''となりエラーになります。
bad_test_sample.py
from nose.tools import *
from mock import Mock
from StringIO import StringIO
import urllib2
import target
def test_get():
urllib2.urlopen = Mock(return_value=StringIO('hoge'))
eq_('hoge', target.get("http://dummy-url"))
eq_('hoge', target.get("http://dummy-url"))
1回目ならまだしも、2回目以降でエラーになりますので???となります。実際のテストはこのように二度実行はしないとでしょうから、明らかにおかしいタイミングでエラーになって見えます。少なくとも自分の時はそうでした。
正しい例
結論から言うと**「readが必要->StringIOで作る」が間違っている**ので、それをしないようにして、そこもMockで済ませます。
good_test_sample.py
from nose.tools import *
from mock import Mock
import urllib2
import target
def test_get():
urllib2.urlopen = Mock()
urllib2.urlopen.return_value.read.return_value = 'hoge'
eq_('hoge', target.get("http://dummy-url"))
eq_('hoge', target.get("http://dummy-url"))
解説
StringIOで渡した場合、確かにちゃんとしたread()メソッドがついてきますが、逆にちゃんとしているが故に二度目以降の実行でも同一ストリームを参照し、さらにポインタが既に最後尾にあります。なので、二度目以降はread()しても何も得られません。やっぱりポインタ周りはプログラマ的には必修だね。