広告 プログラミング

pytestによるモジュール読み込みの例外処理のテスト方法を解説

※本ページには、プロモーション(広告)が含まれています。

pytestによるモジュール読み込みの例外処理のテスト方法を解説

皆さんは、pytestを用いてテストコードを書くことはありますか?

最近、私は以下のような実装結果に対して、テストコードを書く必要がありました。

# usecase1.py
try:
  # 想定:numpyがインストールされておらず、読み込めない
  import numpy
  g_can_load = True
except:
  # 例外が発生したら、代替モジュールを読み込む
  import math
  g_can_load = False

また、以下に示すように、逆の状況にも遭遇しています。

# usecase2.py
try:
  # 想定:インストール済みで問題なく読み込めるモジュール
  import math
  g_can_load = True
except:
  # 例外が発生したら、自作モジュールを読み込む
  import original_math
  g_can_load = False

今回は、上記のようなPython scriptに対し、pytestでテストを行う方法を解説します。

Pythonの仕様上、癖がある部分もあったので、備忘録として残しておきます。

ゆると
ゆると

GitHub上に実際に動かすことができるコード一式も用意したので、手元で動かしながら確認してみてください。

全体構成

ディレクトリ構成、動作確認環境、テスト対象ファイルの詳細について解説します。

ディレクトリ構成

今回想定するディレクトリ構成は、以下に示す通りです。

./
|-- docker-compose.yml
|-- LICENSE
|-- README.md
|-- env.sample
|-- .env
|-- docker/
|   |-- bashrc
|   |-- Dockerfile
|   |-- pyproject.toml
|   `-- waiting.sh
`-- app/
    |-- src/
    |   |-- __init__.py
    |   |-- main.py
    |   |-- newton.py
    |   |-- private.py           ← テスト対象が参照するファイル
    |   |-- target_module.py        ← テスト対象が参照するファイル
    |   `-- sample_with_having_errors.py  ← 今回のテスト対象
    `-- tests/
        |-- __init__.py
        |-- conftest.py
        |-- test_main.py
        |-- test_newton.py
        `-- test_target_module.py ← 今回のメインコード

GitHub上の構成をそのまま反映しているため、適宜コメントもあわせてご覧ください。

ゆると
ゆると

動作確認環境

今回は、以下の環境で動作確認を行いました。

項目詳細確認方法
デバイスRaspberry Pi 4 Model B Rev 1.4cat /proc/cpuinfo | sed -e "s/\s\s*/ /g" | grep -oP "(?<=Model : )(.*)"
アーキテクチャaarch64 (64bit)uname -m
OSDebian GNU/Linux 11 (bullseye)cat /etc/os-release | grep -oP '(?<=PRETTY_NAME=")(.*)(?=")'
動作環境

さらに、Dockerを用いて検証を行っているため、Docker環境さえあれば、ほとんどの方が実行結果を再現できるようになっています。

Poetryというpythonの仮想環境も利用しているので、pythonが動く環境があればそれでもOKです。

ゆると
ゆると

テスト対象ファイル

今回のテスト対象のコードは、以下に示す通りです。

# sample_with_having_errors.py
# 下記の2つのモジュールは存在しない
import disabled_module
from cannot_use_package.cannot_use_module import disabled_function

def execute(x):
  y1 = disabled_module.undefined_function(x + 1)
  y2 = disabled_function(x + 2)
  y = y1 + y2

  return y
# private.py
# Assumption
#  - This file is private.
#  - This file is ignored for git's configuration management.
# target_module.py
# ==============
# Test pattern 1
# ==============
try:
  import src.sample_with_having_errors
  g_sample = True
except:
  g_sample = False

# ==============
# Test pattern 2
# ==============
try:
  import src.private
  g_private = True
except:
  g_private = False

実際に確認したいことなどは、次の章で解説したいと思います。

今回やりたいこと

コードベースになりますが、今回やりたいことを説明します。

順に解説します。

テスト時にImportErrorの例外を発生させない

テスト対象コードのうち、前半部分を再掲します。

# target_module.py
# ==============
# Test pattern 1
# ==============
try:
  import src.sample_with_having_errors
  g_sample = True
except:
  g_sample = False

上記のimport文ですが、src.sample_with_having_errorsは、以下のようになっているため、例外が発生します。

# sample_with_having_errors.py
# 下記の2つのモジュールは存在しない
import disabled_module
from cannot_use_package.cannot_use_module import disabled_function

# 以下、省略 

このため、グローバル変数g_sampleは必ずFalseになります。

今回は、テストコードを工夫して、src.sample_with_having_errorsをimportする際に例外が発生しないようにテストを行うことが目的になります。

テスト時に意図的にImportErrorの例外を発生させる

続いて、テスト対象コードのうち、後半部分を紹介します。

# target_module.py
# 中略

# ==============
# Test pattern 2
# ==============
try:
  import src.private
  g_private = True
except:
  g_private = False

上記は先ほどと逆の動きをし、src.privateをimportする際に例外は発生しないため、g_privateは必ずTrueになります。

今回は、テストコードを工夫して、src.privateをimportする際に例外が発生するようにテストを行うことが目的になります。

以上のことをまとめると、以下のように整理できますね。

テスト観点やりたいこと注目する変数通常実行時の値テスト時の出力
import時に例外が発生するimport時に発生する例外を抑制したいg_sampleFalseTrue
import時に例外が発生しないimport時に意図的に例外を発生させたいg_privateTrueFalse

ImportError関連の例外処理テストを行う際の考え方

今回のテストを行う際は、import時にpythonの内部で何が行われるかを知る必要があります。

pythonのimport処理はimportlibにて実現されているため、該当するコードの一部を記載します。

import importlib.util
import sys

def import_module(name, package=None):
    """An approximate implementation of import."""
    absolute_name = importlib.util.resolve_name(name, package)
    try:
        return sys.modules[absolute_name]
    except KeyError:
        pass

    path = None
    if '.' in absolute_name:
        parent_name, _, child_name = absolute_name.rpartition('.')
        parent_module = import_module(parent_name)
        path = parent_module.__spec__.submodule_search_locations
    for finder in sys.meta_path:
        spec = finder.find_spec(absolute_name, path)
        if spec is not None:
            break
    else:
        msg = f'No module named {absolute_name!r}'
        raise ModuleNotFoundError(msg, name=absolute_name)
    module = importlib.util.module_from_spec(spec)
    sys.modules[absolute_name] = module
    spec.loader.exec_module(module)
    if path is not None:
        setattr(parent_module, child_name, module)
    return module

こちらのページにあるコードを参照しました。

ゆると
ゆると

上記から読み取れる3つのことは、以下に示す通りです。

参照したコードから分かること3つ

  1. 一度importしたモジュールは、sys,modulesにキャッシュされる。
  2. モジュールロード時は、探索時のモジュール名を変更しながら再帰的に処理される。(15行目)
  3. モジュールが見つからない場合は、ModuleNotFoundErrorの例外が投げられる。

以上のことを踏まえると、以下のようにテストコードを作成すれば、テストが実施できると考えられます。

テストコードの作成方針

  1. ImportErrorが発生する場合
    sys.modulesにある該当モジュールをモックに差し替える。
  2. 正常に読み込める場合
    sys.modulesにある該当モジュールを削除した上で、モジュール探索時に例外を投げる。

文字だけではイメージしづらいと思うので、テストコードの実装結果もあわせて説明します。

テストコードの実装例

今回、実装したテストコードは以下のようになります。

import pytest

@pytest.fixture
def get_resetter_of_sys_modules(mocker):
  def module_setter(remove_cached_modules=None, mocker_modules=None):
    # 初期化
    remove_cached_modules = remove_cached_modules or []
    mocker_modules = mocker_modules or []
    # キャッシュ済みのモジュールの削除
    import sys
    _fake_modules = {key: val for key, val in sys.modules.items() if key not in remove_cached_modules}
    # 指定したモジュールをモック化
    for key in mocker_modules:
      _fake_modules[key] = mocker.Mock()
    # sys.moduleにpatchを当てる
    mocker.patch.dict('sys.modules', _fake_modules, clear=True)

  return module_setter

@pytest.mark.abnormal
def test_cannot_load_specific_files(get_resetter_of_sys_modules):
  module_setter = get_resetter_of_sys_modules
  # キャッシュ済みのモジュールを削除
  module_setter(remove_cached_modules=[
    'src.target_module',
    'src.sample_with_having_errors',
    'disabled_module',
    'cannot_use_package.cannot_use_module',
  ])

  # ==================
  # Import test module
  # ==================
  # [注意] `from src import target_module as tm` とすると、pythonでは独立してロードされるため、今回のケースでは適さない
  import src.target_module as tm

  assert not tm.g_sample

@pytest.mark.normal
def test_avoid_raising_import_exception(get_resetter_of_sys_modules):
  module_setter = get_resetter_of_sys_modules
  # キャッシュ済みのモジュールを削除し、対象のモジュールをモック化
  module_setter(
    remove_cached_modules=['src.target_module', 'src.sample_with_having_errors'],
    mocker_modules=['disabled_module', 'cannot_use_package.cannot_use_module'],
  )

  # ==================
  # Import test module
  # ==================
  # [注意] `from src import target_module as tm` とすると、pythonでは独立してロードされるため、今回のケースでは適さない
  import src.target_module as tm

  assert tm.g_sample

@pytest.mark.normal
def test_can_load_private_file(get_resetter_of_sys_modules):
  module_setter = get_resetter_of_sys_modules
  # キャッシュ済みのモジュールを削除
  module_setter(remove_cached_modules=[
    'src.target_module',
    'src.private',
  ])

  # ==================
  # Import test module
  # ==================
  import src.target_module as tm

  assert tm.g_private

@pytest.mark.abnormal
def test_raise_import_exception_with_existing_file(get_resetter_of_sys_modules, mocker):
  module_setter = get_resetter_of_sys_modules
  # キャッシュ済みのモジュールを削除
  module_setter(remove_cached_modules=[
    'src.target_module',
    'src.private',
  ])
  # モジュール探索用の関数を定義
  import importlib
  original_finder = importlib._bootstrap._find_and_load

  def _fake_finder(name, *args, **kwargs):
    # 特定のモジュールに対して、例外を投げる
    if name == 'src.private':
      raise Exception()
  
    return original_finder(name, *args, **kwargs)
  # モジュール探索用の関数を参照するよう、オリジナルの実装にpatchを当てる
  mocker.patch('importlib._bootstrap._find_and_load', side_effect=_fake_finder)

  # ==================
  # Import test module
  # ==================
  import src.target_module as tm

  assert not tm.g_private

@pytest.mark.normal
def test_execute_func(get_resetter_of_sys_modules, mocker):
  module_setter = get_resetter_of_sys_modules
  # キャッシュ済みのモジュールを削除し、対象のモジュールをモック化
  module_setter(
    remove_cached_modules=['src.target_module', 'src.sample_with_having_errors'],
    mocker_modules=['disabled_module', 'cannot_use_package.cannot_use_module'],
  )
  # 期待値を設定
  ret_y1 = 4
  ret_y2 = 5
  y_sum  = ret_y1 + ret_y2
  # 該当モジュールをモック化
  import sys
  disabled_module_mocker = sys.modules['disabled_module']
  mocker.patch.object(disabled_module_mocker, 'undefined_function', return_value=ret_y1)
  mocker.patch('src.sample_with_having_errors.disabled_function', return_value=ret_y2)

  import src.sample_with_having_errors as swhe
  estimate_y = swhe.execute(1)

  assert estimate_y == y_sum

Github上のファイルは、こちらになります。

以降では、コアになる部分(fixture部分)とテストコードの書き方の例について、紹介します。

モジュールをモックする処理は、こちらの記事も参考にしました。

ゆると
ゆると

コア部分

コア部分では、以下の3つを実現しています。

コア部分で実現していること3つ

  1. sys.modulesから指定したモジュールを削除する。
  2. 指定したモジュールのモックを作成し、sys.modulesに設定する。
  3. 書き換えたsys.modulesが呼び出されるように、patchを当てる。

実装結果の該当箇所は、以下に示す通りです。

@pytest.fixture
def get_resetter_of_sys_modules(mocker):
  def module_setter(remove_cached_modules=None, mocker_modules=None):
    # 初期化
    remove_cached_modules = remove_cached_modules or []
    mocker_modules = mocker_modules or []
    # キャッシュ済みのモジュールの削除
    import sys
    _fake_modules = {key: val for key, val in sys.modules.items() if key not in remove_cached_modules}
    # 指定したモジュールをモック化
    for key in mocker_modules:
      _fake_modules[key] = mocker.Mock()
    # sys.moduleにpatchを当てる
    mocker.patch.dict('sys.modules', _fake_modules, clear=True)

  return module_setter

上記のうち、以下の処理により「sys.modulesから指定したモジュールを削除する」という処理を実現しています。

_fake_modules = {key: val for key, val in sys.modules.items() if key not in remove_cached_modules}

また、以下の処理にて、「指定したモジュールのモックを作成し、sys.modulesに設定する」という処理を実現しています。

for key in mocker_modules:
  _fake_modules[key] = mocker.Mock()

最後に、下記を実行することで、「書き換えたsys.modulesが呼び出される」ことになりますよ。

mocker.patch.dict('sys.modules', _fake_modules, clear=True)

以上がコア部分の実装になります。

以降では、実際に関連機能をテストしながら、動作確認を行っていきましょう。

ImportErrorを発生させない場合のテストコードの例

まず、従来通り、ImportErrorが発生するケースを示します。

@pytest.mark.abnormal
def test_cannot_load_specific_files(get_resetter_of_sys_modules):
  module_setter = get_resetter_of_sys_modules
  # キャッシュ済みのモジュールを削除
  module_setter(remove_cached_modules=[
    'src.target_module',
    'src.sample_with_having_errors',
    'disabled_module',
    'cannot_use_package.cannot_use_module',
  ])

  # ==================
  # Import test module
  # ==================
  # [注意] `from src import target_module as tm` とすると、pythonでは独立してロードされるため、今回のケースでは適さない
  import src.target_module as tm

  assert not tm.g_sample

こちらは、普通に呼び出しているだけなので、g_sampleの値は必ずFalseになります。

pytest tests/test_target_module.py::test_cannot_load_specific_files
(out)==================================================================================== test session starts ====================================================================================
(out)platform linux -- Python 3.12.10, pytest-8.3.5, pluggy-1.6.0 -- /usr/local/bin/python3.12
(out)cachedir: home/.cache
(out)Using --randomly-seed=1702629328
(out)rootdir: /opt
(out)configfile: pyproject.toml
(out)plugins: env-1.1.5, randomly-3.16.0, mock-3.14.1, cov-6.1.1, anyio-4.9.0, asyncio-0.26.0
(out)asyncio: mode=Mode.STRICT, asyncio_default_fixture_loop_scope=session, asyncio_default_test_loop_scope=session
(out)collected 1 item
(out) 
(out)tests/test_target_module.py::test_cannot_load_specific_files PASSED                                                                                                                   [100%]
(out) 
(out)====================================================================================== tests coverage =======================================================================================
(out)_____________________________________________________________________ coverage: platform linux, python 3.12.10-final-0 ______________________________________________________________________
(out) 
(out)Coverage HTML written to dir htmlcov
(out)Coverage XML written to file coverage.xml
(out)===================================================================================== 1 passed in 0.49s ====================================================================================

続いて、読み込めないモジュールをモック化した場合のケースです。

@pytest.mark.normal
def test_avoid_raising_import_exception(get_resetter_of_sys_modules):
  module_setter = get_resetter_of_sys_modules
  # キャッシュ済みのモジュールを削除し、対象のモジュールをモック化
  module_setter(
    remove_cached_modules=['src.target_module', 'src.sample_with_having_errors'],
    mocker_modules=['disabled_module', 'cannot_use_package.cannot_use_module'],
  )

  # ==================
  # Import test module
  # ==================
  # [注意] `from src import target_module as tm` とすると、pythonでは独立してロードされるため、今回のケースでは適さない
  import src.target_module as tm

  assert tm.g_sample

この場合、読み込めないモジュールをモック化しているので、正常に読み込むことができたことになり、テストもパスします。

pytest tests/test_target_module.py::test_avoid_raising_import_exception
(out)==================================================================================== test session starts ====================================================================================
(out)platform linux -- Python 3.12.10, pytest-8.3.5, pluggy-1.6.0 -- /usr/local/bin/python3.12
(out)cachedir: home/.cache
(out)Using --randomly-seed=2230336766
(out)rootdir: /opt
(out)configfile: pyproject.toml
(out)plugins: env-1.1.5, randomly-3.16.0, mock-3.14.1, cov-6.1.1, anyio-4.9.0, asyncio-0.26.0
(out)asyncio: mode=Mode.STRICT, asyncio_default_fixture_loop_scope=session, asyncio_default_test_loop_scope=session
(out)collected 1 item
(out)
(out)tests/test_target_module.py::test_avoid_raising_import_exception PASSED                                                                                                               [100%]
(out)
(out)====================================================================================== tests coverage =======================================================================================
(out)_____________________________________________________________________ coverage: platform linux, python 3.12.10-final-0 ______________________________________________________________________
(out)
(out)Coverage HTML written to dir htmlcov
(out)Coverage XML written to file coverage.xml
(out)===================================================================================== 1 passed in 0.53s =====================================================================================

意図的にImportErrorを発生させる場合のテストコードの例

まず、従来通り、ImportErrorが発生しないケースを示します。

@pytest.mark.normal
def test_can_load_private_file(get_resetter_of_sys_modules):
  module_setter = get_resetter_of_sys_modules
  # キャッシュ済みのモジュールを削除
  module_setter(remove_cached_modules=[
    'src.target_module',
    'src.private',
  ])

  # ==================
  # Import test module
  # ==================
  import src.target_module as tm

  assert tm.g_private

こちらは、src/private.pyが存在するため、g_privateは必ずTrueになります。

pytest tests/test_target_module.py::test_raise_import_exception_with_existing_file
==================================================================================== test session starts ====================================================================================
platform linux -- Python 3.12.10, pytest-8.3.5, pluggy-1.6.0 -- /usr/local/bin/python3.12
cachedir: home/.cache
Using --randomly-seed=4106986825
rootdir: /opt
configfile: pyproject.toml
plugins: env-1.1.5, randomly-3.16.0, mock-3.14.1, cov-6.1.1, anyio-4.9.0, asyncio-0.26.0
asyncio: mode=Mode.STRICT, asyncio_default_fixture_loop_scope=session, asyncio_default_test_loop_scope=session
collected 1 item

tests/test_target_module.py::test_raise_import_exception_with_existing_file PASSED                                                                                                    [100%]

====================================================================================== tests coverage =======================================================================================
_____________________________________________________________________ coverage: platform linux, python 3.12.10-final-0 ______________________________________________________________________

Coverage HTML written to dir htmlcov
Coverage XML written to file coverage.xml
===================================================================================== 1 passed in 0.51s ====================================================================================

続いて、意図的に例外を呼び出す場合の実装例です。

@pytest.mark.abnormal
def test_raise_import_exception_with_existing_file(get_resetter_of_sys_modules, mocker):
  module_setter = get_resetter_of_sys_modules
  # キャッシュ済みのモジュールを削除
  module_setter(remove_cached_modules=[
    'src.target_module',
    'src.private',
  ])
  # モジュール探索用の関数を定義
  import importlib
  original_finder = importlib._bootstrap._find_and_load

  def _fake_finder(name, *args, **kwargs):
    # 特定のモジュールに対して、例外を投げる
    if name == 'src.private':
      raise Exception()
  
    return original_finder(name, *args, **kwargs)
  # モジュール探索用の関数を参照するよう、オリジナルの実装にpatchを当てる
  mocker.patch('importlib._bootstrap._find_and_load', side_effect=_fake_finder)

  # ==================
  # Import test module
  # ==================
  import src.target_module as tm

  assert not tm.g_private

この時のポイントとして、モジュール探索用の関数を自前で定義していることが挙げられます。

今回の場合、読み込むモジュールに応じて、以下のように場合分けできます。

読み込めないモジュールによる場合分け2パターン

  1. src.privateを読み込む
    例外を投げる
  2. src.private以外を読み込む
    そのまま読み込む

上記を実現したいことを踏まえると、モジュール探索用の関数は、以下のようにすれば良いことが分かりますね。

import importlib
original_finder = importlib._bootstrap._find_and_load

def _fake_finder(name, *args, **kwargs):
  # 特定のモジュールに対して、例外を投げる
  if name == 'src.private':
    raise Exception()
  
  return original_finder(name, *args, **kwargs)

このような処理を組み込んだ上でテストを行うと、g_privateFalseになり、テストをパスしていることが分かります。

pytest tests/test_target_module.py::test_raise_import_exception_with_existing_file
==================================================================================== test session starts ====================================================================================
platform linux -- Python 3.12.10, pytest-8.3.5, pluggy-1.6.0 -- /usr/local/bin/python3.12
cachedir: home/.cache
Using --randomly-seed=2832467546
rootdir: /opt
configfile: pyproject.toml
plugins: env-1.1.5, randomly-3.16.0, mock-3.14.1, cov-6.1.1, anyio-4.9.0, asyncio-0.26.0
asyncio: mode=Mode.STRICT, asyncio_default_fixture_loop_scope=session, asyncio_default_test_loop_scope=session
collected 1 item

tests/test_target_module.py::test_raise_import_exception_with_existing_file PASSED                                                                                                    [100%]

====================================================================================== tests coverage =======================================================================================
_____________________________________________________________________ coverage: platform linux, python 3.12.10-final-0 ______________________________________________________________________

Coverage HTML written to dir htmlcov
Coverage XML written to file coverage.xml
===================================================================================== 1 passed in 0.55s ====================================================================================

【おまけ】モック化した後のモジュールの利用例

モック化するのは良いですが、実際にはモック化した上でテストを行うケースが多いと思います。

モック化するのは、スタブを用意していることと同義ですね。

ゆると
ゆると

このため、モック化したものの活用方法を実装例(下記)と共に説明します。

@pytest.mark.normal
def test_execute_func(get_resetter_of_sys_modules, mocker):
  module_setter = get_resetter_of_sys_modules
  # キャッシュ済みのモジュールを削除し、対象のモジュールをモック化
  module_setter(
    remove_cached_modules=['src.target_module', 'src.sample_with_having_errors'],
    mocker_modules=['disabled_module', 'cannot_use_package.cannot_use_module'],
  )
  # 期待値を設定
  ret_y1 = 4
  ret_y2 = 5
  y_sum  = ret_y1 + ret_y2
  # 該当モジュールをモック化
  import sys
  disabled_module_mocker = sys.modules['disabled_module']
  mocker.patch.object(disabled_module_mocker, 'undefined_function', return_value=ret_y1)
  mocker.patch('src.sample_with_having_errors.disabled_function', return_value=ret_y2)

  import src.sample_with_having_errors as swhe
  estimate_y = swhe.execute(1)

  assert estimate_y == y_sum

上記のうち、以下に示す部分が重要です。

  # 期待値を設定
  ret_y1 = 4
  ret_y2 = 5
  y_sum  = ret_y1 + ret_y2
  # 該当モジュールをモック化
  import sys
  disabled_module_mocker = sys.modules['disabled_module']
  mocker.patch.object(disabled_module_mocker, 'undefined_function', return_value=ret_y1)
  mocker.patch('src.sample_with_having_errors.disabled_function', return_value=ret_y2)

これらの処理を整理すると、以下のように大別できます。

モック化した後の処理パターン2つ

  1. import文によりモジュールを読み込む場合
    sys.modulesにキャッシュされているモックインスタンスを取得する
  2. from文によりモジュールを読み込む場合
    従来通り、mocker.patchを用いてpatchを当てる

上記に示したパターンに対し、それぞれの具体的な対応関係は、下記の図に示す通りです。

上記の処理を行うことで、execute関数はエラーなく実行できるようになります。

pytest tests/test_target_module.py::test_execute_func
==================================================================================== test session starts ====================================================================================
platform linux -- Python 3.12.10, pytest-8.3.5, pluggy-1.6.0 -- /usr/local/bin/python3.12
cachedir: home/.cache
Using --randomly-seed=824814770
rootdir: /opt
configfile: pyproject.toml
plugins: env-1.1.5, randomly-3.16.0, mock-3.14.1, cov-6.1.1, anyio-4.9.0, asyncio-0.26.0
asyncio: mode=Mode.STRICT, asyncio_default_fixture_loop_scope=session, asyncio_default_test_loop_scope=session
collected 1 item

tests/test_target_module.py::test_execute_func PASSED                                                                                                                                 [100%]

====================================================================================== tests coverage =======================================================================================
_____________________________________________________________________ coverage: platform linux, python 3.12.10-final-0 ______________________________________________________________________

Coverage HTML written to dir htmlcov
Coverage XML written to file coverage.xml
===================================================================================== 1 passed in 0.54s ====================================================================================

さらに、これらは同時にテストをしても、問題なく動作することを確認済みです。

pytest tests/test_target_module.py
==================================================================================== test session starts ====================================================================================
platform linux -- Python 3.12.10, pytest-8.3.5, pluggy-1.6.0 -- /usr/local/bin/python3.12
cachedir: home/.cache
Using --randomly-seed=3843431221
rootdir: /opt
configfile: pyproject.toml
plugins: env-1.1.5, randomly-3.16.0, mock-3.14.1, cov-6.1.1, anyio-4.9.0, asyncio-0.26.0
asyncio: mode=Mode.STRICT, asyncio_default_fixture_loop_scope=session, asyncio_default_test_loop_scope=session
collected 5 items

tests/test_target_module.py::test_cannot_load_specific_files PASSED                                                                                                                   [ 20%]
tests/test_target_module.py::test_avoid_raising_import_exception PASSED                                                                                                               [ 40%]
tests/test_target_module.py::test_raise_import_exception_with_existing_file PASSED                                                                                                    [ 60%]
tests/test_target_module.py::test_execute_func PASSED                                                                                                                                 [ 80%]
tests/test_target_module.py::test_can_load_private_file PASSED                                                                                                                        [100%]

====================================================================================== tests coverage =======================================================================================
_____________________________________________________________________ coverage: platform linux, python 3.12.10-final-0 ______________________________________________________________________

Coverage HTML written to dir htmlcov
Coverage XML written to file coverage.xml
===================================================================================== 5 passed in 0.58s ====================================================================================

今回のまとめ

今回は、以下について解説しました。

かなり特殊なケースの事例かもしれませんが、ライブラリの有無やユーザ定義関数の有無で処理を分ける場合は、今回紹介したようなテスト方法が有効に機能すると考えています。

pytestでテストコードを実装する際に、上記のような困りごとがあれば参考にしてみてください。

スポンサードリンク

-プログラミング
-,