Posted at

urllib2をMock化してunittestする

More than 5 years have passed since last update.

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()しても何も得られません。やっぱりポインタ周りはプログラマ的には必修だね。