はじめに
pytestで単体テストを書いているときに以下のようなエラーに遭遇したことはないでしょうか。
ScopeMismatch: You tried to access the 'function' scoped fixture 'monkeypatch' with a 'module' scoped request object, involved factories
この記事では、このエラーについて解説し解決策を示します。
エラーの解説
エラーメッセージに書いてある通り、function
スコープのfixtureであるmonkeypatch
をより広いmodule
スコープで使おうとしたことによって、スコープがあっていないよ!と言われています。
pytestではfixtureの中で別のfixtureを入力として受け取り、使用することができます。この時気をつけなくてはいけないのは、fixtureの「スコープ」という概念です。スコープには以下の4つがあります1。
-
function
: テストケース毎に実行される(デフォルト) -
class
: テストケースをまとめたクラス毎に実行される -
module
: モジュール毎に実行される -
session
: すべてのテストケースで一回だけ実行される
もちろん、下に行くほどスコープが広いです。
**狭いスコープのfixtureをより広いスコープで使うことはできません。**つまり、function
スコープのfixtureはclass
以上のスコープを持つfixtureで使うことはできません。
私は、monkeypatch
を広いスコープで使おうとして、最初に示したエラーにたまに遭遇してしまいます。以下では、そういうシーンでの解決策を示していきます。
解決策の前に
まず、monkeypatch
を広いスコープで使う必要が本当にあるかを考えます。ただ、一回実行すればいいからという理由だけでclass
スコープやmodule
スコープにしていないでしょうか。monkeypatch
の「メソッドや属性を差し替える」という機能は意図しない箇所へ悪影響を及ぼす可能性があるので、できるだけその範囲(スコープ)を狭くする方が安全です。よって、毎回実行する必要がなくても、基本的にはmonkeypatch
はテストケース毎に実行するようにするべきです。
とはいえ、実行時間などの問題からそうもいかずmonkeypatch
を広いスコープで使わざるを得ない場合もあります。
解決策
以下のようなsession
スコープ版のmonkeypatch
を自作します。最も広いsession
スコープで作っておけば任意のスコープで使用することができます。
import pytest
from _pytest.monkeypatch import MonkeyPatch
@pytest.fixture(scope="session")
def monkeypatch_session():
mpatch = MonkeyPatch()
yield mpatch
mpatch.undo()
ソースコードを読むとわかりますが、monkeypatch_session
はmonkeypatch
と全く同じ処理をしているので、monkeypatch
でできることはすべてできます。あとは、monkeypatch
の代わりにmonkeypatch_session
を使うだけです。
# これはScopeMismatchになる
@pytest.fixture(scope="module")
def module_fixture(monkeypatch):
monkeypatch.setattr(...)
...
# monkeypatchをそのままmonkeypatch_sessionに置き換える
@pytest.fixture(scope="module")
def module_fixture(monkeypatch_session):
monkeypatch_session.setattr(...)
...
一応、MonkeyPatch
をそのまま使うこともできます。
@pytest.fixture(scope="module")
def module_fixture():
with MonkeyPatch.context() as mp:
mp.setattr(...)
...
yield
注意点
- 上にも書いたように、広いスコープで
monkeypatch
を使うのは安全ではない -
monkeypatch_session
で使用しているMonkeyPatch
はユーザが使うことを意図していない(_pytest
内にある)ものなので、予告なく変更される可能性がある
以上の注意点を認識して、用法用量を守って正しくお使いください。
-
実は
module
より広く、session
より狭いpackage
というスコープもあります(pytestドキュメント) ↩