蛇ノ目の記

技術のことも。そうでないことも。

ユニットテストで躓いたところ - mock.patch()

単純なスクリプトユニットテストを書いていて躓くことがあったので、解決法をメモ。

例えばこんな、おみくじをするだけの簡単なスクリプトがあるとする。

import random


fortunes = {
    1: '凶',
    2: '吉',
    3: '大吉'
}

number = random.choice([1, 2, 3])

fortune = fortunes[number]

print(fortune)

変数numberに1〜3までのランダムな値が入って、対応する運勢が出力される 。random.choice()の部分をパッチして、期待通りの運勢が出力されることをテストする。

import unittest

from importlib import import_module
from unittest.mock import patch


class FortuneTestCase(unittest.TestCase):

    @patch('random.choice', lambda x: 1)
    def test_bad(self):
        module = import_module('fortune')
        expect = ('凶')
        self.assertEqual(module.fortune, expect)

    @patch('random.choice', lambda x: 3)
    def test_good(self):
        module = import_module('fortune')
        expect = ('大吉')
        self.assertEqual(module.fortune, expect)

さて、このテストを実行するとどうなるか。

(env) $ python3 -m unittest tests.test
凶
.F
======================================================================
FAIL: test_good (tests.test.FortuneTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/usr/local/Cellar/python/3.7.0/Frameworks/Python.framework/Versions/3.7/lib/python3.7/unittest/mock.py", line 1191, in patched
    return func(*args, **keywargs)
  File "/tests/test.py", line 21, in test_good
    self.assertEqual(module.fortune, expect)
AssertionError: '凶' != '大吉'
- 凶
+ 大吉


----------------------------------------------------------------------
Ran 2 tests in 0.029s

FAILED (failures=1)

test_goodメソッドのアサーションで例外が発生する。パッチした値が反映されていない。つまり初めに実行されたtest_badメソッドでパッチした値がそのまま使われている。

値がパッチされっぱなしなのではれば、モジュールを読み込み直してみる。importlib.reload()を使う。

Python3 公式ドキュメント importlib - reload

import unittest

from importlib import import_module, reload
from unittest.mock import patch


class FortuneTestCase(unittest.TestCase):

    @patch('random.choice', lambda x: 1)
    def test_bad(self):
        module = import_module('fortune')
        expect = ('凶')
        self.assertEqual(module.fortune, expect)

    @patch('random.choice', lambda x: 3)
    def test_good(self):
        module = import_module('fortune')
        reload(module)
        expect = ('大吉')
        self.assertEqual(module.fortune, expect)
(env) $ python3 -m unittest tests.test
凶
凶
.大吉
.
----------------------------------------------------------------------
Ran 2 tests in 0.025s

OK

無事テストが通った。テストメソッド実行ごとにパッチされると思っていただけにこの動きにだいぶ悩むこととなった。

公式ドキュメントに以下の注釈があるためtest_goodメソッドでだけreloadを使った。

注釈 :いろいろなテストが実行される順序は、文字列の組み込みの順序でテストメソッド名をソートすることで決まります。

https://docs.python.jp/3/library/unittest.html#organizing-test-code