tanihito’s blog

デジタル・新規事業開発・健康など、興味のあることについてつらつらと書いてきます。

moxの使い方

pythonのモックといえばminimockが有名ですが、私はmoxを利用しています。moxで検索してもあまり情報がないので、簡単に使い方を記載しておきます。
以下のような、URLを受け取ってソースを表示するprint_sourceという関数のテストを考えます。インターネットに接続されていない状況でもテストが通るように、urllib.urlopen()が返すsocket._fileobjectのモックを作成します。

my_urllib.py

import urllib

def print_source(url):
    f = urllib.urlopen(url)
    source = f.read()
    print "\n", source

test_my_urllib.py

import urllib
import mox
import my_urllib

class TestMyUrllib(object):
    def setUp(self):
        self.mox = mox.Mox()
    def tearDown(self):
        self.mox.UnsetStubs()

    def test_print_source(self):
        # モックの作成
        import socket
        fileobject_mock = self.mox.CreateMock(socket._fileobject)
        self.mox.StubOutWithMock(urllib, 'urlopen')

        # 処理の流れの記述
        urllib.urlopen('http://example.com').AndReturn(fileobject_mock)
        fileobject_mock.read().AndReturn("example source")

        # 関数の実行
        self.mox.ReplayAll()
        my_urllib.print_source('http://example.com')
        self.mox.VerifyAll()

このファイルを実行すると、正しくテストが実行されていることが分かります。

$ nosetests test_my_urllib.py
test_my_urllib.TestMyUrllib.test_print_source ... 
example source
ok

----------------------------------------------------------------------
Ran 1 test in 0.001s

OK

モックの作成

moxでは、モックの作成、処理の流れの記述、関数の実行というステップでテストを行います。最初に、socket._fileobjectに対応したモックを作成します。

import socket
fileobject_mock = self.mox.CreateMock(socket._fileobject)

オブジェクト名が分からなければ、CreateMockAnything()を利用することができます。

fileobject_mock = self.mox.CreateMockAnything()

また、urllib.urlopen()という関数をスタブで置き換えておきます。

self.mox.StubOutWithMock(urllib, 'urlopen')

処理の流れの記述

処理の流れは、まずurlopen()を実行し、モックfileobject_mockを返します。この際にAndReturn()を利用し、先ほど作成したモックfileobject_mockを引数として指定します。

urllib.urlopen('http://example.com').AndReturn(fileobject_mock)

引数の型だけ指定したい場合はmox.IsA(class)を使います。

urllib.urlopen(mox.IsA(str)).AndReturn(fileobject_mock)

次に、read()が呼び出された際に返すソースコードを記述しておきます。

fileobject_mock.read().AndReturn("example source")

関数の実行

mox.ReplayAll()とmox.VerifyAll()で囲まれた部分の関数を実行し、事前に記述した処理の流れと一致しているか確認します。

self.mox.ReplayAll()
my_urllib.print_source('http://example.com')
self.mox.VerifyAll()


基本的には、Mockingしたい関数があればStubOutWithMock()を使っておけば大丈夫です。より詳しく知りたい場合はSign in - Google Accountsを見てください。またmoxを使ってMySQLdbのテストを行う - tanihitoの技術メモにはmoxをMySQLdbのテストに利用する例が載っています。