with 문에서 사용되는 open을 어떻게 조롱합니까 (Python에서 Mock 프레임 워크 사용)?


188

mocks를 사용하여 다음 코드를 테스트하는 방법 ( Michael Foord의 Mock 프레임 워크에서 제공하는 mock , 패치 데코레이터 및 센티넬 사용 ) :

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

@Daryl Spitzer : 메타 질문을 피할 수 있습니까 ( "답을 알고 있습니다 ...") 혼란 스럽습니다.
S.Lott

과거에 내가 그 일을 그만두었을 때 사람들은 내가 내 자신의 질문에 대답하고 있다고 불평했습니다. 나는 그것을 내 대답으로 옮길 것입니다.
Daryl Spitzer

1
@Daryl : 일반적으로 "카르마 창녀"에 대한 걱정에서 비롯된 자신의 질문에 대한 불만을 피하는 가장 좋은 방법은 질문을 표시하거나 "커뮤니티 위키"로 답변하는 것입니다.
John Millikin

3
자신의 질문에 대답하는 것이 Karma Whoring으로 간주되면 그 시점에서 FAQ를 명확히해야합니다.
EBGreen

답변:


132

이를 수행하는 방법은 mock 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')

와! 이것은 현재 voidspace.org.uk/python/mock/magicmock.html에 있는 컨텍스트 관리자 예제보다 훨씬 단순 해 보입니다. 객체 를 명시 적으로 설정 __enter__하고 __exit__조롱하는 것도 후자의 접근 방식이 오래되었거나 여전히 유용합니까?
Brandon Rhodes

6
" 최신 접근 방식"은 MagicMock 사용 하지 않고 수행하는 방법을 보여줍니다 (즉, Mock이 마법 방법을 지원하는 방법의 예일뿐입니다). MagicMock을 사용하는 경우 (위와 같이) enterexit 가 미리 구성되어 있습니다.
fuzzyman 2016

5
왜 / 어떻게 작동하는지 자세히 설명하는 블로그 게시물을 가리킬 수 있습니다
Rodrigue

9
파이썬 3에서는 'file'이 정의되어 있지 않으므로 (MagicMock 사양에 사용됨) 대신 io.IOBase를 사용하고 있습니다.
Jonathan Hartley

1
참고 : Python3에서는 내장 기능 file이 사라졌습니다!
exhuma

239

mock_openmock프레임 워크의 일부이며 사용이 매우 간단합니다. patch컨텍스트로 사용하면 패치 된 오브젝트를 대체하는 데 사용되는 오브젝트가 리턴됩니다.이를 사용하여 테스트를 단순화 할 수 있습니다.

파이썬 3.x

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

파이썬 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_callable patch'의 인수 를 사용하는 것이 좋습니다. 사용 patch하지 않는 모든 추가 인수 new_callablepatch설명서에 설명 된대로 기능에 전달됩니다 .

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모의 객체를 테스트 함수의 인수로 추가합니다.


질문해서 죄송합니다. with patch("builtins.open", mock_open(read_data="data")) as mock_file:데코레이터 구문으로 변환 할 수 있습니까? 시도했지만 @patch("builtins.open", ...) 두 번째 인수 로 무엇을 전달해야하는지 잘 모르겠습니다 .
imrek

1
@DrunkenMaster 업데이트했습니다. 지적 해 주셔서 감사합니다. 이 경우 데코레이터를 사용하는 것이 쉽지 않습니다.
Michele d' Amico

그 라지! 내 문제는 (I 채널했다 조금 더 복잡 return_value의를 mock_open다른 모의 개체로 두 번째 모의의 주장 return_value)하지만 추가 근무 mock_opennew_callable.
imrek

1
@ArthurZopellaro six는 일관된 mock모듈 을 갖기 위해 모듈을 살펴 봅니다 . 그러나 그것이 builtins공통 모듈로 매핑되는지 모르겠습니다 .
Michele d' Amico

1
패치 할 올바른 이름을 어떻게 찾습니까? 즉, 임의 함수에 대해 @patch (이 경우 'builtins.open')의 ​​첫 번째 인수를 어떻게 찾습니까?
zenperttu

73

최신 버전의 mock을 사용하면 매우 유용한 mock_open 도우미를 사용할 수 있습니다 .

mock_open (mock = 없음, read_data = 없음)

오픈 사용을 대체하기 위해 모의 객체를 만드는 헬퍼 함수. 직접 호출되거나 컨텍스트 관리자로 사용되는 열기에서 작동합니다.

mock 인수는 구성 할 mock 객체입니다. 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통화 가 있는지 어떻게 확인 합니까?
n611x007

1
@naxa 한 가지 방법은 각 예상 매개 변수를에 전달하는 것 handle.write.assert_any_call()입니다. handle.write.call_args_list주문이 중요한 경우 각 전화를 받기 위해 사용할 수도 있습니다 .
Rob Cutmore

m.return_value.write.assert_called_once_with('some stuff')더 나은 imo입니다. 통화 등록을 피하십시오.
익명

2
에 대해 수동으로 주장하는 Mock.call_args_list것은 Mock.assert_xxx, 메소드를 호출하는 것보다 안전 합니다. Mock의 속성 인 후자를 잘못 입력하면 항상 자동으로 전달됩니다.
Jonathan Hartley

12

간단한 파일에 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.

파이썬 2.6.6 / 모의 1.0.1 사용


좋아 보이지만 for line in opened_file:코드 유형으로는 작동하지 않습니다 . 나는 __iter__대신 반복 하여 구현 하고 사용하는 반복 가능한 StringIO로 실험을 시도 my_text했지만 운이 없습니다.
Evgen

@EvgeniiPuchkaryov 이것은 특별히 작동 read()하므로 귀하의 for line in opened_file경우에는 작동하지 않습니다 . 내가 명확하게 게시물을 편집했습니다
jlb83

1
@EvgeniiPuchkaryov의 for line in f:지원의 반환 값 조롱 의해 달성 될 수있는 open()바와 같이 StringIO 대신 개체 .
Iskar Jarak

1
명확히하기 위해이 예에서 테스트 대상 시스템 (SUT)은 다음과 같습니다. with open("any_string") as f: print f.read()
Brad M

4

가장 좋은 대답은 유용하지만 조금 확장했습니다.

여기에 전달 된 인수를 기반으로 파일 객체의 값 ( fin as f)을 설정하려면 다음 open()중 하나를 수행하십시오.

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

테스트중인 코드가 예를 들어 "readline"함수를 호출하여 다른 방식으로 파일을 처리하는 경우 원하는 속성을 사용하여 "do_something_with_data"함수에서 원하는 모의 객체를 반환 할 수 있습니다.
user3289695

만지지 않는 방법이 __enter__있습니까? 그것은 권장 방법보다 확실히 해킹처럼 보입니다.
imrek

enter 는 open ()과 같은 conext 관리자가 작성되는 방법입니다. 모의 객체는 자주 액세스 모의에 "개인"물건을 필요에 비트 해키 될 것입니다,하지만이 밤은 ingerintly 해키 IMO 여기에 입력
theannouncer

3

게임에 약간 늦었을 수도 있지만 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
killthrush

0

unittest로 내장 open () 함수를 패치하려면 :

이것은 패치가 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):

0

더 이상 파일이 필요하지 않으면 테스트 방법을 꾸밀 수 있습니다.

@patch('builtins.open', mock_open(read_data="data"))
def test_testme():
    result = testeme()
    assert result == "data"
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.