web-dev-qa-db-ja.com

反復のためのunittest.mock.mock_openのカスタマイズ

このコードを処理するには、unittest.mock.mock_openをどのようにカスタマイズする必要がありますか?

file: impexpdemo.py
def import_register(register_fn):
    with open(register_fn) as f:
        return [line for line in f]

私の最初の試みはread_dataを試しました。

class TestByteOrderMark1(unittest.TestCase):
    REGISTER_FN = 'test_dummy_path'
    TEST_TEXT = ['test text 1\n', 'test text 2\n']

    def test_byte_order_mark_absent(self):
        m = unittest.mock.mock_open(read_data=self.TEST_TEXT)
        with unittest.mock.patch('builtins.open', m):
            result = impexpdemo.import_register(self.REGISTER_FN)
            self.assertEqual(result, self.TEST_TEXT)

これは失敗しました。おそらく、コードがread、readline、またはreadlinesを使用していないためです。 unittest.mock.mock_openの documentation は、「read_dataは、ファイルハンドルのread()、readline()、およびreadlines()メソッドが返す文字列です。これらのメソッドの呼び出しには時間がかかります。 read_dataから枯渇するまでのデータ。これらのメソッドのモックはかなり単純です。テストされたコードにフィードするデータをさらに制御する必要がある場合は、このモックを自分でカスタマイズする必要があります。read_dataはデフォルトで空の文字列です。 。」

ドキュメントにはどのようなカスタマイズが必要かについてのヒントがないので、私はreturn_valueside_effectを試しました。どちらも機能しませんでした。

class TestByteOrderMark2(unittest.TestCase):
    REGISTER_FN = 'test_dummy_path'
    TEST_TEXT = ['test text 1\n', 'test text 2\n']

    def test_byte_order_mark_absent(self):
        m = unittest.mock.mock_open()
        m().side_effect = self.TEST_TEXT
        with unittest.mock.patch('builtins.open', m):
            result = impexpdemo.import_register(self.REGISTER_FN)
            self.assertEqual(result, self.TEST_TEXT)
24
lemi57ssss

mock_open()オブジェクトは実際には反復を実装していません。

ファイルオブジェクトをコンテキストマネージャーとして使用していない場合は、次を使用できます。

_m = unittest.mock.MagicMock(name='open', spec=open)
m.return_value = iter(self.TEST_TEXT)

with unittest.mock.patch('builtins.open', m):
_

これで、open()はイテレータを返します。これは、ファイルオブジェクトと同じように直接反復できるものであり、next()でも機能します。ただし、コンテキストマネージャーとして使用することはできません。

これをmock_open()と組み合わせて、戻り値に___iter___および___next___メソッドを提供できます。さらに、mock_open()が使用の前提条件を追加するという利点もあります。コンテキストマネージャーとして:

_# Note: read_data must be a string!
m = unittest.mock.mock_open(read_data=''.join(self.TEST_TEXT))
m.return_value.__iter__ = lambda self: self
m.return_value.__next__ = lambda self: next(iter(self.readline, ''))
_

ここでの戻り値は、MagicMockオブジェクト(Python 2)または メモリ内ファイルオブジェクト (Python 3)から指定されたfileオブジェクトですが、readwrite、および___enter___メソッドのみがあります。スタブアウトされました。

上記はPython 2では機能しません。a)Python 2は___next___ではなくnextが存在することを期待し、b)nextは処理されないためです。 Mockの特別なメソッドとして(当然のことながら)、上記の例で___next___の名前をnextに変更した場合でも、戻り値のtype値にはnextメソッドはありません。 mostの場合、ファイルオブジェクトがiterableを生成するようにするだけで十分です。 )イテレータではなく:

_# Python 2!
m = mock.mock_open(read_data=''.join(self.TEST_TEXT))
m.return_value.__iter__ = lambda self: iter(self.readline, '')
_

iter(fileobj)を使用するコードはすべて機能します(forループを含む)。

このギャップを修正することを目的とした Pythonトラッカー の未解決の問題があります。

34
Martijn Pieters

Python 3.6の時点で、unittest.mock_openメソッドによって返されるモックファイルのようなオブジェクト 反復をサポートしていません 。このバグは2014年に報告され、 2017年現在も営業しています。

したがって、このようなコードはサイレントにゼロ反復を生成します。

f_open = unittest.mock.mock_open(read_data='foo\nbar\n')
f = f_open('blah')
for line in f:
  print(line)

適切な行イテレータを返すモックオブジェクトにメソッドを追加することで、この制限を回避できます。

def mock_open(*args, **kargs):
  f_open = unittest.mock.mock_open(*args, **kargs)
  f_open.return_value.__iter__ = lambda self : iter(self.readline, '')
  return f_open
8
maxschlepzig

私は次の解決策を見つけました:

text_file_data = '\n'.join(["a line here", "the second line", "another 
line in the file"])
with patch('__builtin__.open', mock_open(read_data=text_file_data), 
create=True) as m:
    # mock_open doesn't properly handle iterating over the open file with for line in file:
    # but if we set the return value like this, it works.
    m.return_value.__iter__.return_value = text_file_data.splitlines()
    with open('filename', 'rU') as f:
        for line in f:
            print line
2
Luis Meraz