web-dev-qa-db-ja.com

withステートメントで使用されるオープンをモックするにはどうすればよいですか(PythonのMockフレームワークを使用)。

モックで次のコードをテストするにはどうすればよいですか(モック、パッチデコレータ、および Michael FoordのMockフレームワーク が提供するセンチネルを使用):

def testme(filepath):
    with open(filepath, 'r') as f:
        return f.read()
152
Daryl Spitzer

これを行う方法は、特にMagicMockを使用したpythonプロトコルメソッド(マジックメソッド)のモックを最終的にサポートするmock 0.7.0で変更されました。

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')
123
fuzzyman

これらの答えには多くのノイズがあります。ほとんどすべてが正しいが、時代遅れでありきちんとされていません。 mock_openmock フレームワークの一部であり、非常に使いやすいです。 patch コンテキストとして使用すると、パッチが適用されたオブジェクトを置き換えるために使用されるオブジェクトが返されます。これを使用して、テストを簡単にすることができます。

Python 3.x

__builtin__の代わりにbuiltinsを使用します。

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")

Python 2.7

mockunittestの一部ではないため、__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_callablepatchの引数を使用し、patchが使用しない余分な引数はすべて patch documentation で説明されているようにnew_callable関数に渡されることに注意してください。

patch()は任意のキーワード引数を取ります。これらは構築時にMock(または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は、テスト関数の引数としてモックオブジェクトを追加することに注意してください。

178
Michele d'Amico

モックの最新バージョンでは、本当に便利な mock_open ヘルパーを使用できます:

mock_open(mock = None、read_data = None)

Openの使用を置き換えるモックを作成するヘルパー関数。直接呼び出されるか、コンテキストマネージャとして使用されるopenで機能します。

Mock引数は、構成するモックオブジェクトです。 None(デフォルト)の場合、MagicMockが作成され、APIは標準のファイルハンドルで使用可能なメソッドまたは属性に制限されます。

read_dataは、ファイルハンドルの読み取りメソッドが返す文字列です。これはデフォルトでは空の文字列です。

>>> 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')
66
David

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を使用

12
jlb83

ゲームに少し遅れているかもしれませんが、新しいファイルを作成せずに別のモジュールで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")

__builtin__モジュール内のopen関数をmock_open()にパッチすることで、ファイルを作成せずにファイルへの書き込みをモックできます。

注:cythonを使用するモジュールを使用している場合、またはプログラムが何らかの方法でcythonに依存している場合は、ファイルの先頭に__builtin__を含めて cythonのimport __builtin__モジュール をインポートする必要があります。 cythonを使用している場合、ユニバーサル__builtin__をモックすることはできません。

3
Leo C Han

一番上の答えは便利ですが、少し詳しく説明しました。

open()に渡された引数に基づいてファイルオブジェクトの値(as ff)を設定する場合は、次の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
3
theannouncer