Pythonのテストコードで、モジュールをモックに差し替える

Pythonにて、開発環境に無いモジュールをimportしているプロダクションコードに対して、テストコードを書く機会がありました。

ただ、テストコードにてモジュールをモックに差し替える方法で悩んだため、メモを残します。

 
目次

 

環境

 

対応

以下を参考に、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