0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

[pytest] monkeypatchを広いスコープで使う

Posted at

はじめに

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スコープで作っておけば任意のスコープで使用することができます。

monkeypatch_session.py
import pytest
from _pytest.monkeypatch import MonkeyPatch

@pytest.fixture(scope="session")
def monkeypatch_session():
    mpatch = MonkeyPatch()
    yield mpatch
    mpatch.undo()

ソースコードを読むとわかりますが、monkeypatch_sessionmonkeypatchと全く同じ処理をしているので、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内にある)ものなので、予告なく変更される可能性がある

以上の注意点を認識して、用法用量を守って正しくお使いください。

  1. 実はmoduleより広く、sessionより狭いpackageというスコープもあります(pytestドキュメント

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?