Pythonにて、開発環境に無いモジュールをimportしているプロダクションコードに対して、テストコードを書く機会がありました。
ただ、テストコードにてモジュールをモックに差し替える方法で悩んだため、メモを残します。
目次
環境
- Python 3.5.2
- Python3.3以降なので、unittest.mock.Mockを使用
- pytest 3.0.5
- テストランナーとして使用
対応
以下を参考に、sys.modules
辞書の該当モジュールをモックに差し替えます。
python - How to mock an import - Stack Overflow
例えば、
# 開発環境で使えないモジュールたち from disabled_package import disabled_module from disabled_package.disabled_module import disabled_submodule class Target(object): def square(self, value): # モジュールに含まれる関数(call_function())や定数(CONST)を使用 disabled_module.call_function(disabled_submodule.CONST) return value ** 2
というコードがあり、モジュール
- disabled_package
- disabled_package.disabled_module
が開発環境で使えないとします。
この状態で
import pytest from target import Target class Test_Target(object): def test_square(self): # Arrange sut = Target() # Act actual = sut.square(2) # Assert assert actual == 4
というテストコードを作成・実行しても、
(env) >pytest ============================= test session starts ============================= platform win32 -- Python 3.5.2, pytest-3.0.5, py-1.4.32, pluggy-0.4.0 rootdir: path\to\python_mock_sample, inifile: pytest.ini collected 0 items / 1 errors =================================== ERRORS ==================================== _______________ ERROR collecting mocking_module/test_target.py ________________ ImportError while importing test module 'path\to\python_mock_sample\mocking_module\test_target.py'. Hint: make sure your test modules/packages have valid Python names. Traceback: test_target.py:11: in <module> from target import Target target.py:1: in <module> from disabled_package import disabled_module E ImportError: No module named 'disabled_package' !!!!!!!!!!!!!!!!!!! Interrupted: 1 errors during collection !!!!!!!!!!!!!!!!!!! =========================== 1 error in 0.33 seconds ===========================
失敗します。
そのため、テストコードを修正し、
import pytest from unittest.mock import Mock # モジュールをモックへ差し替えるように追加 import sys sys.modules['disabled_package'] = Mock() sys.modules['disabled_package.disabled_module'] = Mock() from target import Target class Test_Target(object): def test_square(self): # Arrange sut = Target() # Act actual = sut.square(2) # Assert assert actual == 4
テスト対象コードのimport前に、開発環境で使えないモジュールをモックへと差し替えます。
その後、テストを実行したところ、
(env) >pytest ============================= test session starts ============================= platform win32 -- Python 3.5.2, pytest-3.0.5, py-1.4.32, pluggy-0.4.0 rootdir: D:\Sandbox\python_mock_sample, inifile: pytest.ini collected 1 items test_target.py . ========================== 1 passed in 0.09 seconds ===========================
成功しました。
ちなみに、sys.modules
は
ロード済みモジュールのモジュール名とモジュールオブジェクトの辞書。
sys.modules | 28.1. sys — システムパラメータと関数 — Python 2.7.x ドキュメント
とのことです。
確認のため
import sys import pytest def main(): print(sys.modules['pytest'])
を作成・実行してみたところ、
<module 'pytest' from 'path\\to\\python_mock_sample\\env\\lib\\site-packages\\pytest.py'>
と、モジュールオブジェクトが入っていました。
これにより、モジュールオブジェクトをモックに差し替えられることが分かりました。
ソースコード
GitHubに上げました。e.g_mocking_module
ディレクトリの中が今回のコードです。
thinkAmi-sandbox/python_mock-sample