モックを使用して次のコードをテストするにはどうすればよいですか(モック、パッチデコレーター、およびMichael Foordのモックフレームワークによって提供されるセンチネルを使用)。
def testme(filepath):
with open(filepath, 'r') as f:
return f.read()
モックを使用して次のコードをテストするにはどうすればよいですか(モック、パッチデコレーター、およびMichael Foordのモックフレームワークによって提供されるセンチネルを使用)。
def testme(filepath):
with open(filepath, 'r') as f:
return f.read()
回答:
これを行う方法はモック0.7.0で変更され、特にMagicMockを使用して、Pythonプロトコルメソッド(マジックメソッド)のモックを最終的にサポートします。
http://www.voidspace.org.uk/python/mock/magicmock.html
コンテキストマネージャーとして開いたモックの例(モックドキュメントのサンプルページから):
>>> open_name = '%s.open' % __name__
>>> with patch(open_name, create=True) as mock_open:
... mock_open.return_value = MagicMock(spec=file)
...
... with open('/some/path', 'w') as f:
... f.write('something')
...
<mock.Mock object at 0x...>
>>> file_handle = mock_open.return_value.__enter__.return_value
>>> file_handle.write.assert_called_with('something')
__enter__
し__exit__
てモックするコンテキストマネージャーの例よりもはるかに単純に見えます。後者の方法は時代遅れですか、それともまだ役に立ちますか?
file
はなくなりました!
mock_open
mock
フレームワークの一部であり、使用方法は非常に簡単です。patch
コンテキストとして使用すると、パッチが適用されたオブジェクトを置き換えるために使用されるオブジェクトが返されます。これを使用して、テストを簡単にすることができます。
のbuiltins
代わりに使用します__builtin__
。
from unittest.mock import patch, mock_open
with patch("builtins.open", mock_open(read_data="data")) as mock_file:
assert open("path/to/open").read() == "data"
mock_file.assert_called_with("path/to/open")
mock
の一部ではなく、unittest
パッチを適用する必要があります__builtin__
from mock import patch, mock_open
with patch("__builtin__.open", mock_open(read_data="data")) as mock_file:
assert open("path/to/open").read() == "data"
mock_file.assert_called_with("path/to/open")
の結果patch
を使用してデコレータとして使用する場合mock_open()
、new
patch
の引数は少し奇妙な場合があります。
この場合、new_callable
patch
の引数をpatch
使用することnew_callable
をお勧めしpatch
ます。ドキュメントで説明されているように、使用しない追加の引数はすべて関数に渡されることに注意してください。
patch()は任意のキーワード引数を取ります。これらは構築時にモック(またはnew_callable)に渡されます。
たとえば、Python 3.xの装飾バージョンは次のとおりです。
@patch("builtins.open", new_callable=mock_open, read_data="data")
def test_patch(mock_file):
assert open("path/to/open").read() == "data"
mock_file.assert_called_with("path/to/open")
この場合patch
、テスト関数の引数としてモックオブジェクトが追加されることに注意してください。
with patch("builtins.open", mock_open(read_data="data")) as mock_file:
デコレータ構文に変換できますか?試しましたが、@patch("builtins.open", ...)
2番目の引数として何を渡す必要があるのかわかりません。
return_value
のをmock_open
別のモックオブジェクトに第二モックのASSERT return_value
)が、それは追加することによって、働いていたmock_open
ようnew_callable
。
six
モジュールを調べて、一貫性のあるmock
モジュールを作成しています。しかし、それbuiltins
が共通モジュールにもマッピングされているかどうかはわかりません。
mockの最新バージョンでは、本当に便利なmock_openヘルパーを使用できます。
mock_open(mock = None、read_data = None)
openの使用を置き換えるモックを作成するヘルパー関数。直接呼び出された、またはコンテキストマネージャーとして使用されたopenに対して機能します。
モック引数は、構成するモックオブジェクトです。None(デフォルト)の場合、MagicMockが作成され、APIは標準のファイルハンドルで使用できるメソッドまたは属性に制限されます。
read_dataは、返されるファイルハンドルのreadメソッドの文字列です。これはデフォルトでは空の文字列です。
>>> from mock import mock_open, patch
>>> m = mock_open()
>>> with patch('{}.open'.format(__name__), m, create=True):
... with open('foo', 'w') as h:
... h.write('some stuff')
>>> m.assert_called_once_with('foo', 'w')
>>> handle = m()
>>> handle.write.assert_called_once_with('some stuff')
.write
通話があるかどうかをどのように確認しますか?
handle.write.assert_any_call()
です。handle.write.call_args_list
順序が重要な場合は、を使用して各呼び出しを取得することもできます。
m.return_value.write.assert_called_once_with('some stuff')
イモがいいです。通話の登録を回避します。
Mock.call_args_list
がMock.assert_xxx
、メソッドを呼び出すよりも安全です。Mockの属性である後者のスペルを間違えた場合、それらは常に黙ってパスします。
単純なファイルにmock_openを使用するにはread()
(このページですでに提供されている元のmock_openスニペットは、書き込み用に設計されています):
my_text = "some text to return when read() is called on the file object"
mocked_open_function = mock.mock_open(read_data=my_text)
with mock.patch("__builtin__.open", mocked_open_function):
with open("any_string") as f:
print f.read()
mock_openのドキュメントによると、これはのためread()
に特別に作成されているため、for line in f
たとえばのような一般的なパターンでは機能しません。
Python 2.6.6 /モック1.0.1を使用
for line in opened_file:
、コードの種類を処理できません。の__iter__
代わりにそれを実装して使用する反復可能なStringIOを試してみましたmy_text
が、うまくいきませんでした。
read()
ため、あなたのfor line in opened_file
ケースでは機能しません。私は明確にするために投稿を編集しました
with open("any_string") as f: print f.read()
上の答えは役に立ちますが、少し拡張しました。
ここに渡された引数に基づいてファイルオブジェクト(f
in as f
)の値を設定する場合は、次open()
の1つの方法があります。
def save_arg_return_data(*args, **kwargs):
mm = MagicMock(spec=file)
mm.__enter__.return_value = do_something_with_data(*args, **kwargs)
return mm
m = MagicMock()
m.side_effect = save_arg_return_array_of_data
# if your open() call is in the file mymodule.animals
# use mymodule.animals as name_of_called_file
open_name = '%s.open' % name_of_called_file
with patch(open_name, m, create=True):
#do testing here
基本的にopen()
は、オブジェクトを返し、そのオブジェクトwith
を呼び出し__enter__()
ます。
適切にモックするにはopen()
、モックオブジェクトを返すためにモックする必要があります。次に、そのモックオブジェクトは、__enter__()
呼び出しをモックして(MagicMock
これを実行します)、必要なモックデータ/ファイルオブジェクト(したがってmm.__enter__.return_value
)を返します。上記のように2つのモックでこれを行うと、渡された引数をキャプチャopen()
してdo_something_with_data
メソッドに渡すことができます。
私はに文字列として全体のモックファイルを渡されopen()
て、私はdo_something_with_data
このように見えました:
def do_something_with_data(*args, **kwargs):
return args[0].split("\n")
これは文字列をリストに変換するので、通常のファイルと同じように次のことができます。
for line in file:
#do action
__enter__
ないようにする方法はありますか?それは確かに推奨される方法よりもハックのように見えます。
ゲームには少し遅れるかもしれませんがopen
、新しいファイルを作成せずに別のモジュールを呼び出すと、これでうまくいきました。
test.py
import unittest
from mock import Mock, patch, mock_open
from MyObj import MyObj
class TestObj(unittest.TestCase):
open_ = mock_open()
with patch.object(__builtin__, "open", open_):
ref = MyObj()
ref.save("myfile.txt")
assert open_.call_args_list == [call("myfile.txt", "wb")]
MyObj.py
class MyObj(object):
def save(self, filename):
with open(filename, "wb") as f:
f.write("sample text")
モジュールopen
内の関数__builtin__
にmyをパッチするmock_open()
ことで、ファイルを作成せずにファイルへの書き込みを模擬できます。
注:用途はcythonというモジュールを使用している、またはあなたのプログラムがどのような方法でcythonに依存している場合は、インポートする必要がありますcythonの__builtin__
モジュールを含むことによってimport __builtin__
、ファイルの先頭に。__builtin__
cythonを使用している場合、ユニバーサルをモックすることはできません。
import __builtin__
テストモジュールに確実に追加する必要がありました。この記事は、この手法がうまく機能する理由を明らかにするのに役立ちました:ichimonji10.name/blog/6
これは、json設定を読み取るパッチで機能しました。
class ObjectUnderTest:
def __init__(self, filename: str):
with open(filename, 'r') as f:
dict_content = json.load(f)
モックされたオブジェクトは、open()関数によって返されるio.TextIOWrapperオブジェクトです
@patch("<src.where.object.is.used>.open",
return_value=io.TextIOWrapper(io.BufferedReader(io.BytesIO(b'{"test_key": "test_value"}'))))
def test_object_function_under_test(self, mocker):
これ以上ファイルが必要ない場合は、テストメソッドを装飾できます。
@patch('builtins.open', mock_open(read_data="data"))
def test_testme():
result = testeme()
assert result == "data"