恐怖のMocking

Python と Mock の話。

import されているモジュールをモックに差し替えたいときに

from minimock import Mock

import hoge

hoge.fuga = Mock('hoge.fuga')

と安易にやってはいけない。 これだと restore しても元の hoge.fuga には戻らない。

例えば

turtle.py というのを考える:

# turtle.py

def soup():
    return 'turtle'

tests.py も考える。

import unittest

from minimock import Mock, restore

import turtle


class TurtleTest(unittest.TestCase):
    def setUp(self):
        turtle.soup = Mock('turtle.soup',
                           returns=u'mock turtle')

    def tearDown(self):
        restore()

        print turtle.soup()

    def test_soup(self):
        self.assertEqual(turtle.soup(), 'mock turtle')


if __name__ == '__main__':
    unittest.main()

と restore までしてやるわけだけど、悲しいことに(あたいまえなことに) turtle.soup はモックのままである。

 % python -m tests
Called turtle.soup()
Called turtle.soup()
mock turtle
.
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK

よくやるのがテスト対象のモジュール target に対して、そこで import されている turtle.soup を差し替えるというもの。 target.turtle.soup をモックに差し替えるんだろうけど、その場合も元に戻っていないと厄介。

mock 使おう

Mock 使わずに mock 使えばいい。

from minimock import mock, restore

...

class TurtleTest(unittest.TestCase):
    def setUp(self):
        mock('turtle.soup',
             returns=u'mock turtle')

...

これだけ。ほんと素直に mock 使おう。restore すれば元に戻ってくれる。 呼び出してみても restore されてるのが分かる。

 % python -m tests
Called turtle.soup()
turtle
.
----------------------------------------------------------------------
Ran 1 test in 0.018s

OK

修正したいが名前変えたくない場合

TraceTracker などで呼び出しを追ってる場合、モックするときの名前も 重要になる。

既存テストが副作用起こしてて修正したい!けど名前変えたくない!ってときは mock_obj 引数使ってこうしよう

# turtle.soup = Mock('turtle_soup')

mock('turtle.soup'
     mock_obj=Mock('turtle_soup'))

わあい

これで万事解決。

小ネタ

モック呼びだされるたびに標準出力されると、テストの結果がみにくくなる:

................F.......F..........Called turtle.soup
.......Called turtle.soup
Called turtle.soup
Called turtle.__add__
Called turtle.__add__
Called turtle.soup
...
Called turtle.soup
Called turtle.soup
.......E...s...

iMac を窓の外に放り投げる前に、 tracker=None にして落ち着こう。

Mock('turtle.__add__',
     returns='Ambition',
     tracker=None)

これで黙る。

まとめ

  • minimock.Mock はすぐ消えるオブジェクトに適応
  • モックで頑張るな