2020-07-07

[python] まだmockで消耗してるの?mockを理解するための3つのポイント

隣の席の人がテスト強化週間とか抜かしていたので自分もちゃんと理解するために なるべくわかりやすく まとめてみようと思います。

この記事は 2015 tech-yuruyuru アドベントカレンダー - 15日目の記事です。 2015 tech-yuruyuru アドベントカレンダー (2015/12/01 00:00〜)# 2015 #tech-yuruyuru アドベントカレンダー #tech-yuruyuru のアドベントカレンダーです。 テーマは特に決まっていません。好きなことを書いてください。 * 参加したい日の参加枠に参加登録してください * 2 日以上参加したい場合は、フィードで宣言してください ## カレンダー 1. @pjxiao: VPC のプライベートサブネットについて解説 2. @pjxiao: mDNS を使いローカルマシン内の仮想環境に接続する 3. @pjxiao: Ansible の Iventory file についておさらい 4. @p...https://connpass.com/event/22759/

モックって何よ?

mockは特定のオブジェクトの代理をしてユニットテストを円滑に進めるためのモジュールです。

python3.3からはビルトインに入りましたが、それ未満のバージョンではインストールが必要です。

以下のようにインストールしてください。

$ pip install mock
  • インストールしたmockを使う場合は単に import mock とすればよいのですが ビルトインmockを使う場合は、 from unittest import mock のようにして使うのが一般的です。

  • (以降、この記事では無用な混乱を避けるため、mockのimport文を省略します。使い方は概ね同じはずです)

個人的にmockを理解する上で重要なポイントは以下の3点だと考えています。

  1. (大体)どんな振る舞いも表現できる Mockオブジェクト
  2. 任意の名前空間に自身の Mockオブジェクト をねじ込むことができる パッチ機能
  3. ねじ込んだ Mockオブジェクト がどのように使われたか(呼び出されたか)を記録する キャプチャ機能

これらが合わさるからこそmockは強力なわけですが、一遍には理解しづらい概念かもしれません。

一つ一つ理解していきましょう。

モックオブジェクト

Mockオブジェクトを一言で表現するなら(大体)どのようなオブジェクトの代わりにもなれる高機能粘土みたいなオブジェクトです。

具体的にはmock.Mockおよび派生クラス(のインスタンス)です。デフォルトではMockクラスを継承したMagicMockが使われます。 違いについては後述しますが、基本的には上位互換のMagicMockを使っていれば問題ありません。

単体で使うことはあまり多くはありませんが、理解するために触ってみましょう。

>>> m = mock.MagicMock(a=1, b=2) >>> # キーワード引数で指定した要素を作成できる >>> m.a 1 >>> m.b 2 >>> # 未知の属性にアクセスしてもAttributeErrorにならず、直接アクセスできる >>> m.c <MagicMock name='mock.c' id='4502806368'> >>> m.c.d <MagicMock name='mock.c.d' id='4504775928'> >>> m.e.f.g <MagicMock name='mock.e.f.g' id='4504837984'> >>> m.__a <MagicMock name='mock.__a' id='4504854824'> >>> # 特殊属性は自動生成できない >>> m.__a__ Traceback (most recent call last): File "", line 1, in File "/usr/lib/python3.4/unittest/mock.py", line 570, in __getattr__ raise AttributeError(name) AttributeError: __a__

定義しない属性を参照しても AttributeError は発生せず新たな Mock オブジェクトが得られているのがわかりますね。 この仕組みによって数珠つなぎに存在しない属性を作成できるというわけです。

また、初期化する段階で属性を辞書のキーとして指定できます。

これは shimizukawa 氏から教えてもらいました。

>>> # もちろんこのように書くことはできないけど >>> m = mock.MagicMock(a.b.c.d=1) File "", line 1 SyntaxError: keyword can't be an expression >>> # これはOK! >>> m = mock.MagicMock(**{'a.b.c.d': 100}) >>> m.a.b.c.d 100 >>> m.a.b.c <MagicMock name='mock.a.b.c' id='4504971528'> >>> # 初期化後であればconfigure_mockメソッドを使うこともできます >>> m.configure_mock(**{'e.f.g': 200, 'h.i': 300}) >>> m.e.f.g 200 >>> m.h.i 300

モックの実行

Mockオブジェクトはcallableなので、 ( callableじゃないMock もありますが) 関数(メソッド)として呼び出すことができます。

返却値を指定したい場合 return_value を使います。

>>> # 返却値の指定 >>> m = mock.MagicMock(return_value=3) >>> # なにも指定せずに呼び出し >>> m() 3 >>> # 通常の引数を指定して呼び出しても結果は同じ >>> m(4, 5) 3 >>> # キーワード引数も指定して呼び出しても結果は同じ >>> m(6, spam=7, ham=8, egg=9) 3 >>> # あとから変更することもできる >>> m.return_value = 4 >>> m() 4

複雑なふるまいを表現したい

Mockオブジェクトはほかにも side_effect という引数(属性)を持っています。直訳すると「副作用」です。 一般的に副作用を持つ関数とは、状態を持ち同じ引数に対する実行結果の同一性が保証されないことを指します。

モックに関して言うと、通常は常に同じ返却値を返しますが side_effect を指定したモックは実行ごとに返却値を変えることができるのです。

さらに、例外を起こすことも可能です。

>>> # 毎回返却値を変えたい場合 >>> m = mock.MagicMock(side_effect=[10, 11, 12]) >>> # side_effectはイテレータ形式で保存されている >>> m.side_effect <list_iterator object at 0x10c84ba58> >>> m() 10 >>> m() 11 >>> m() 12 >>> m() Traceback (most recent call last): File "", line 1, in File "/usr/lib/python3.4/unittest/mock.py", line 896, in __call__ return _mock_self._mock_call(*args, **kwargs) File "/usr/lib/python3.4/unittest/mock.py", line 955, in _mock_call result = next(effect) StopIteration >>> # これも後から変更できる >>> m.side_effect = [4, 5] >>> m() 4 >>> m() 5 >>> # 関数を渡すとそのまま実行される >>> def echo(*args, **kwargs): ... print(args, kwargs) ... return 500 >>> m = mock.MagicMock(side_effect=echo) >>> m(1, 2, a=3, *[4, 5], **{'b': 7, 'c': 8}) ((1, 2, 4, 5), {'a': 3, 'c': 8, 'b': 7}) 500 >>> # 例外を発生させたい >>> m = mock.MagicMock(side_effect=IndexError) >>> m() Traceback (most recent call last): File "", line 1, in File "/usr/lib/python3.4/unittest/mock.py", line 896, in __call__ return _mock_self._mock_call(*args, **kwargs) File "/usr/lib/python3.4/unittest/mock.py", line 952, in _mock_call raise effect IndexError >>> m = mock.MagicMock(side_effect=IndexError(u'インデックスエラー')) >>> m() Traceback (most recent call last): File "", line 1, in File "/usr/lib/python3.4/unittest/mock.py", line 896, in __call__ return _mock_self._mock_call(*args, **kwargs) File "/usr/lib/python3.4/unittest/mock.py", line 952, in _mock_call raise effect IndexError: インデックスエラー >>> m.side_effect IndexError('インデックスエラー',)
info
  • return_valueside_effect を同時に指定した場合 side_effect が優先されます。
  • もし、あとから side_effect を無効化する場合は None を代入するという手もあります。 (推奨はしません)

呼び出しをキャプチャ

少々順番が前後しますが、タイミング的にここがベストな気がするので説明します。

Mockオブジェクトは自身が呼び出されたときの引数などをすべて記録しており、あとから参照することができます。

>>> m = mock.MagicMock(return_value=3) >>> m() 3 >>> m(4, 5) 3 >>> m(6, spam=7, ham=8, egg=9) 3 >>> # 呼び出された回数を記憶している >>> m.call_count 3 >>> # どのように呼び出されたかも覚えている >>> m.call_args_list [call(), call(4, 5), call(6, egg=9, spam=7, ham=8)] >>> # call_args_listの各要素にはcallというlistオブジェクトが格納されている >>> m.call_args_list[0] call() >>> # 0番目に通常引数(*args)が格納されていて >>> m.call_args_list[0][0] () >>> # 1番目にキーワード引数(**kwargs)が格納されている >>> m.call_args_list[0][1] {} >>> m.call_args_list[1] call(4, 5) >>> m.call_args_list[1][0] (4, 5) >>> m.call_args_list[1][1] {} >>> m.call_args_list[2] call(6, egg=9, spam=7, ham=8) >>> m.call_args_list[2][0] (6,) >>> m.call_args_list[2][1] {'egg': 9, 'spam': 7, 'ham': 8} >>> # 直前だけを参照する場合はcall_argsを見る >>> m.call_args call(6, egg=9, ham=8, spam=7) >>> # 一度でも呼ばれている場合はcalled属性がTrue >>> m.called True >>> n = mock.MagicMock() >>> # やっぱり呼ばれていないとFalse >>> n.called False >>> n() <MagicMock name='mock()' id='4505008952'> >>> n.called True >>> # 一度だけ呼び出されたかを確認するときにはassert_called_once_withを使う >>> n.assert_called_once_with() >>> n() <MagicMock name='mock()' id='4505008952'> >>> n.assert_called_once_with() Traceback (most recent call last): File "", line 1, in File "/usr/lib/python3.4/unittest/mock.py", line 802, in assert_called_once_with raise AssertionError(msg) AssertionError: Expected 'mock' to be called once. Called 2 times. >>> # 履歴をリセットする >>> m.reset_mock() >>> m.called False

他にも呼び出されたときの引数をテストするためのメソッドがいくつか用意されています。

>>> o = mock.MagicMock() >>> o(1, b=2) >>> o(3, c=4) >>> # 直前に呼び出されたかどうかを確認する >>> o.assert_called_with(1, b=2) Traceback (most recent call last): File "", line 1, in File "/usr/lib/python3.4/unittest/mock.py", line 792, in assert_called_with raise AssertionError(_error_message()) from cause AssertionError: Expected call: mock(1, b=2) Actual call: mock(3, c=4) >>> o.assert_called_with(3, c=4) >>> # 一度でも呼ばれていればOKなことを検査したい場合 >>> o.assert_any_call(1, b=2) >>> o.assert_any_call(1, b=2, c=3) Traceback (most recent call last): File "", line 1, in File "/usr/lib/python3.4/unittest/mock.py", line 854, in assert_any_call ) from cause AssertionError: mock(1, b=2, c=3) call not found

大抵はユニットテストのテストケースを後述する パッチ機能 で書き換え、期待通りに呼び出されているかの検証に利用されます。

MagicMock と Mock の違いについて

MagicMockの MagicMagicMethodMagic です。

通常の Mock ではサポートされていない四則演算等も MagicMock なら実現できます。

>>> m = mock.Mock() >>> m + 1 Traceback (most recent call last): File "", line 1, in TypeError: unsupported operand type(s) for +: 'Mock' and 'int' >>> n = mock.MagicMock() >>> n + 1 <MagicMock name='mock.__add__()' id='4312098744'> >>> n + 2 <MagicMock name='mock.__add__()' id='4312098744'> >>> m[1] Traceback (most recent call last): File "", line 1, in TypeError: 'Mock' object does not support indexing >>> n[1] <MagicMock name='mock.__getitem__()' id='4312197552'> >>> m['a'] Traceback (most recent call last): File "", line 1, in TypeError: 'Mock' object is not subscriptable >>> n['a'] <MagicMock name='mock.__getitem__()' id='4312197552'> >>> for i in m: i ... Traceback (most recent call last): File "", line 1, in TypeError: 'Mock' object is not iterable >>> for i in n: i ...

当たり前ですが、通常のMockには一つもマジックメソッドが存在しないというわけではありません。

明示的に実装しなければならないマジックメソッドがあらかじめ実装されているのがMagicMockというわけです。

Pythonの万能モック MagicMockと戯れる## 導入 Pytho...http://kimihiro-n.appspot.com/show/5837449502654464

パッチ機能

パッチ機能によってオブジェクトの要素を差し替えるわけですが、パッチは決して魔法ではありません。

使いこなすには多少のモジュールの知識が必要です。

まずはmockを使わずに関数のふるまいを変えてみましょう。

  • b.py
  • console
  • # coding: utf-8 import os def dummy(*args, **kwargs): return 'dummy' os.path.join = dummy
  • >>> import os >>> os.path.join('/a/b/c', 'd/e') '/a/b/c/d/e' >>> import b >>> os.path.join('/a/b/c', 'd/e') 'dummy'

上記の例を解説すると、「consoleで参照している os 」も「b.pyで参照している os 」 も実体が同じ為、変更されれば参照している処理すべてが影響を受けます。

os.path.join は dummy関数 によって書き換えられてしまったため、同一プロセス内では常に dummy が返却されます。

mockによるパッチは特別なことを行っているわけではありません。

上記でいうdummyの代わりにMockオブジェクトを差し込み、適用範囲を抜けたら元に戻すことでほかの処理に影響しないようにしてくれています。

パッチ機能は mock.patch, mock.patch.object, mock.patch.dict の3種類があります。

mock.patch

mock.patchは指定したモジュールパス(文字列)が指すオブジェクトをMockオブジェクトに差し替えます。

デコレータかコンテキストマネージャによって適用することができます。

先程の例を mock.patch を使って表現してみましょう。

>>> import os >>> os.path.join('/a/b/c', 'd/e') '/a/b/c/d/e' >>> with mock.patch('os.path.join', return_value='dummy'): ... os.path.join('/a/b/c', 'd/e') 'dummy' >>> os.path.join('/a/b/c', 'd/e') '/a/b/c/d/e'

上記のようにした場合os.path.joinがMockオブジェクトに差し替わり、 return_valueで dummy を指定したため常に dummy を返却します。

mock.patch は書き換え後の値が関数以外であっても親オブジェクトの関係を損なわずに書き換えてくれます。(2018/2/5追記)

>>> with mock.patch('sys.copyright', 'おれ') as dummyright: ... import sys ... print(sys, sys.copyright, dummyright) <module 'sys' (built-in)> おれ おれ

with ステートメントで書き換えた後の値は as で受け取れるので比較用に二重に定義する必要はありません。

info
  • 当該記事では 狭い範囲に適用しやすいコンテキストマネージャを多用していますが、 実際のコードではテストケースのメソッドや関数にデコレータを設定するほうが多いと思います。

  • 具体的には次のように書きます。

    >>> import sys >>> sys.copyright 'Copyright (c) 2001-2018 Python Software Foundation.\nAll Rights Reserved.\n\nCopyright (c) 2000 BeOpen.com.\nAll Rights Reserved.\n\nCopyright (c) 1995-2001 Corporation for National Research Initiatives.\nAll Rights Reserved.\n\nCopyright (c) 1991-1995 Stichting Mathematisch Centrum, Amsterdam.\nAll Rights Reserved.' >>> @mock.patch('sys.copyright', '俺やで!') ... def test(): ... import sys ... print(sys.copyright) ... # 実際に呼び出すのはテストランナーがやるので本来は考えなくて良い >>> test() 俺やで! >>> sys.copyright 'Copyright (c) 2001-2018 Python Software Foundation.\nAll Rights Reserved.\n\nCopyright (c) 2000 BeOpen.com.\nAll Rights Reserved.\n\nCopyright (c) 1995-2001 Corporation for National Research Initiatives.\nAll Rights Reserved.\n\nCopyright (c) 1991-1995 Stichting Mathematisch Centrum, Amsterdam.\nAll Rights Reserved.'

from importを使ったオブジェクトを Patch する

モジュールの理解があれば特筆するようなことでもないのですが、ハマりポイントなような気がするので一応解説しておきます。

from import によって利用可能になったモジュール変数はもとのモジュールから切り離されるため、 もとのモジュールをパッチしても書き換えられません。

例の如く os.path.join を書き換える例です。

>>> from os.path import join >>> # この時点でjoinオブジェクトはos.pathに属していないため書き換えても動かない >>> with mock.patch('os.path.join', return_value='test') as m: ... join('a/b', 'c/d') ... m is join 'a/b/c/d' False >>> # joinオブジェクトが属するモジュールを指定する必要がある(この場合はコンソールなので__main__) >>> with mock.patch('__main__.join', return_value='test'): ... join('a/b', 'c/d') ... m is join 'test' True >>> # もしくはモジュールパス部分から指定する など >>> import os.path >>> with mock.patch('os.path.join', return_value='test') as m: ... os.path.join('a/b', 'c/d') ... m is os.path.join 'test' True

mock.patch.object

mock.patchがモジュールパスに該当するオブジェクトを差し替えるのに対し、 patch.object は任意のオブジェクトの属性を差し替えます。

>>> class Test(object): ... a = 100 >>> # Dummy.aを200で書き換える >>> with mock.patch.object(Test, 'a', 200) as dummy: ... print(Test.a) ... print(dummy) 200 200 >>> # 第3引数()を指定しない場合、Mockオブジェクトが渡される。 >>> with mock.patch.object(Test, 'a') as Dummy: ... print(Test, Test.a) ... Test.a = 200 ... print(Test.a) <class '__main__.Test'> <MagicMock name='a' id='4312206192'> 200 >>> Test.a 100

さて、mock.patchではなくmock.patch.objectを使うべきケースはどのようなものがあるのでしょうか。

Globalスコープに属さない変数を差し替える場合

mock.patchが差し替えられるのは globalスコープ に属する変数に限られます。

>>> class Test(object): ... a = 1 >>> # 関数内で定義されている場合は対応できない >>> with mock.patch('__main__.test1', a=2): ... print(test1.a) ... print(test2.a) 2 1 >>> def test(): ... test1 = Test() ... test2 = Test() ... with mock.patch(__name__ + '.test1', a=2): ... print test1.a ... print test2.a >>> test() Traceback (most recent call last): File "", line 1, in File "", line 4, in test File "/home/righ/Tests/libtest2/local/lib/python2.7/site-packages/mock/mock.py", line 1369, in __enter__ original, local = self.get_original() File "/home/righ/Tests/libtest2/local/lib/python2.7/site-packages/mock/mock.py", line 1343, in get_original "%s does not have the attribute %r" % (target, name) AttributeError: does not have the attribute 'test1' >>> test1 = Test() >>> test2 = Test() >>> # globalスコープに変数が定義されている場合は以下のようにできる >>> with mock.patch('__main__.test1', a=2): ... print(test1.a) ... print(test2.a) 2 1

mock.patch.objectでやる場合は以下のようにします。

>>> class Test(object): ... a = 1 >>> def test(): ... test1 = Test() ... test2 = Test() ... with mock.patch.object(test1, 'a', 2): ... print test1.a ... print test2.a >>> # ローカルスコープの変数も差し替えられる >>> test() 2 1

mock.patch.dict

patch.dictは他のパッチとは異なり、Mockオブジェクトを差し込むのではなく対象ブロックにて対象dictの要素を書き換えます。

>>> d = {'a': 1} >>> print(d, id(d)) {'a': 1} 140638128425792 >>> with mock.patch.dict(d, {'b': 2, 'c': 3}): ... print d, id(d) {'a': 1, 'c': 3, 'b': 2} 140638128425792 >>> print(d, id(d)) {'a': 1} 140638128425792 >>> with mock.patch.dict(d, {'b': 2, 'c': 3}, clear=True): ... print(d, id(d)) {'c': 3, 'b': 2} 140638128425792

アドレス(オブジェクト)が変わらないというのが重要なポイントです。

次のセクションからは少し特殊な利用ケースを紹介します。

イミュータブルなオブジェクトをパッチしたい

Pythonには書き換え不可能なオブジェクトが存在します。 テスト対象として一番よく遭遇するのはdatetime(またはdate)でしょう。

今回は datetime.now() が返却する日時を固定化させたいというシナリオです。

>>> from datetime import datetime >>> # datetimeオブジェクトは変更できない >>> with mock.patch('__main__.datetime.now', return_value=datetime(2015, 1, 1)): ... datetime.now() Traceback (most recent call last): File "", line 1, in File "/usr/lib/python2.7/site-packages/mock/mock.py", line 1460, in __enter__ setattr(self.target, self.attribute, new_attr) TypeError: can't set attributes of built-in/extension type 'datetime.datetime' >>> # datetimeオブジェクト自体をMockオブジェクトと差し替えた上で、それぞれのオブジェクトを入れていく >>> with mock.patch('__main__.datetime', **{'now.return_value': datetime(2015, 1, 1)}): ... datetime.now() datetime.datetime(2015, 1, 1, 0, 0)

さて、お気づきの通りこのやり方には少々欠点があります。ほかの属性が巻き込まれるのです。

datetime.now 以外に datetime.strptime も同じ関数内で使っているなんてよくあることですよね。

datetime を差し替えると datetime.stftime もMockオブジェクトを返却するようになってしまいます。

現実的な解決方法は使われている属性をすべて定義してしまうことです。

>>> with mock.patch('__main__.datetime', **{ ... 'now.return_value': datetime(1, 1, 1), ... 'strptime.return_value': datetime(1, 2, 3), ... }): ... datetime.now() ... datetime.strptime(2000, '%Y') datetime.datetime(1, 1, 1, 0, 0) datetime.datetime(1, 2, 3, 0, 0)

現在日時を固定するのであれば、freezegun や testfixtures など専用のライブラリを使うのが望ましいです。(今回はやりません) GitHub - spulec/freezegun: Let your Python tests travel through timeLet your Python tests travel through time. Contribute to spulec/freezegun development by creating an account on GitHub.https://github.com/spulec/freezegun GitHub - simplistix/testfixtures: testfixtures is a collection of helpers and mock objects that are useful when writing automated tests in Python.testfixtures is a collection of helpers and mock objects that are useful when writing automated tests in Python. - GitHub - simplistix/testfixtures: testfixtures is a collection of helpers and mock...https://github.com/simplistix/testfixtures

複数のオブジェクトをパッチしたい

単純にパッチしたいオブジェクトの分だけ繰り返せばよいのですが、記述方法については多少コツのようなものがあります。

withをネストする

date.todaydatetime.now() を同時に書き換えてみましょう。

>>> from contextlib import nested >>> from datetime import date, datetime >>> >>> with nested( ... mock.patch('__main__.datetime'), ... mock.patch('__main__.date') ... ) as (m, n): ... print(m, n) (<MagicMock name='datetime' id='4346916112'>, <MagicMock name='date' id='4353610192'>)
warning
  • contextlib.nestedは現在推奨されていないらしいです。
  • thanks tell-k

Python3ではこのように書く。

>>> from unittest import mock >>> from datetime import date, datetime >>> with mock.patch('__main__.datetime') as m, mock.patch('__main__.date') as n: ... print(m, n) (<MagicMock name='datetime' id='4346915536'>, <MagicMock name='date' id='4353660944'>) >>> # そもそもnestedがない! >>> from contextlib import nested Traceback (most recent call last): File "", line 1, in from contextlib import nested ImportError: cannot import name 'nested'

その他、3.3からは contextlib.ExitStack なるものがあるらしいので気になる方は触ってみるといいのではないでしょうか

デコレータをネストする

patchを関数に対してかけたい場合は、デコレータとして利用します。

パッチされたMockオブジェクトが引数として渡されるのですが、重要なのはその順番です。

>>> from datetime import date, datetime >>> @mock.patch('__main__.date') ... @mock.patch('__main__.datetime') ... def test_patch(mock_datetime, mock_date): ... print(mock_datetime, mock_date) >>> test_patch() (<MagicMock name='datetime' id='4357884816'>, <MagicMock name='date' id='4353612240'>)

withが書いた順に渡されるのに対し、デコレータでは内側(深いほう)が先に渡されます。 これは内側のデコレータから順に対象関数を包んでいくからです。

with ...() as xに Mock オブジェクトを差し込みたい

with open('test.txt') as f としている箇所で test.txt の内容を書き換えたいことがあるかもしれません。

その場合は以下のように対処できます。

>>> with mock.patch.object(__builtins__, 'open') as m: ... m.return_value.__enter__.return_value.read.return_value = 'abc' ... with open('test.txt') as f: ... print(f.read()) abc

ポイントは __enter__ メソッドの動作を書き換えることです。

withコンテキストでは __enter__ の返却値が as にわたるため、そのあとは使いそうな属性を書き換えていけばよいのです。

contextlibを使うと以下のようにも書けます。どちらがわかりやすいかは個人差がありそう。

>>> @contextlib.contextmanager ... def dummy_open(path): ... yield mock.MagicMock(**{'read.return_value': 'abc'}) ... >>> with mock.patch.object(__builtins__, 'open', side_effect=dummy_open): ... with open('test.txt') as f: ... print(f.read()) abc

openに関しては mock_open というのがあり、fileオブジェクトとして扱われるようです。(よくしらない)

おまけ

>>> # name属性はmockの名前となる(ほとんど使わないけど) >>> m = mock.MagicMock(name='test') >>> m <MagicMock name='test' id='4505025616'>

くぅ~、疲れましたwこれにて完結です

あとはリファレンス読んでください!

参考

unittest.mock --- 入門 — Python 3.10.0b2 ドキュメントhttp://docs.python.jp/3/library/unittest.mock-examples.html

おかしいところがあったら優しくツッコみください