Издевательство над open(file_name) в модульных тестах

У меня есть исходный код, который открывает CSV-файл и устанавливает заголовок для сопоставления значений. Исходный код приведен ниже:

def ParseCsvFile(source): 
  """Parse the csv file. 
  Args: 
    source: file to be parsed

  Returns: the list of dictionary entities; each dictionary contains
             attribute to value mapping or its equivalent. 
  """ 
  global rack_file 
  rack_type_file = None 
  try: 
    rack_file = source 
    rack_type_file = open(rack_file)  # Need to mock this line.
    headers = rack_type_file.readline().split(',') 
    length = len(headers) 
    reader = csv.reader(rack_type_file, delimiter=',') 
    attributes_list=[] # list of dictionaries. 
    for line in reader: 
      # More process to happeng. Converting the rack name to sequence. 
      attributes_list.append(dict((headers[i],
                                   line[i]) for i in range(length))) 
    return attributes_list 
  except IOError, (errno, strerror): 
    logging.error("I/O error(%s): %s" % (errno, strerror)) 
  except IndexError, (errno, strerror): 
    logging.error('Index Error(%s), %s' %(errno, strerror)) 
  finally: 
    rack_type_file.close() 

Я пытаюсь высмеять следующее утверждение

rack_type_file = open(rack_file) 

Как смоделировать функцию open(...)?


person Kartik    schedule 08.03.2011    source источник
comment
Я боюсь, что использование библиотеки Mock не запущено. Нам разрешено использовать только mox.   -  person Kartik    schedule 09.03.2011
comment
Я написал для вас пример, в котором используется mox.   -  person Mahmoud Abdelkader    schedule 09.03.2011


Ответы (7)


Это, по общему признанию, старый вопрос, поэтому некоторые ответы устарели.

В текущей версии библиотеки mock имеется удобная функция, предназначенная именно для этой цели. Вот как это работает:

>>> from mock import mock_open
>>> m = mock_open()
>>> with patch('__main__.open', m, create=True):
...     with open('foo', 'w') as h:
...         h.write('some stuff')
...
>>> m.mock_calls
[call('foo', 'w'),
 call().__enter__(),
 call().write('some stuff'),
 call().__exit__(None, None, None)]
>>> m.assert_called_once_with('foo', 'w')
>>> handle = m()
>>> handle.write.assert_called_once_with('some stuff')

Документация находится здесь.

person mac    schedule 29.10.2013
comment
Кстати, поскольку mock теперь является частью стандартной библиотеки, теперь вы можете импортировать его как: from unittest.mock import mock_open. - person Taylor Edmiston; 24.10.2016

Для имитации встроенной функции, открытой с помощью mox, используйте модуль __builtin__:

import __builtin__ # unlike __builtins__ this must be imported
m = mox.Mox()
m.StubOutWithMock(__builtin__, 'open')
open('ftphelp.yml', 'rb').AndReturn(StringIO("fake file content"))     
m.ReplayAll()
# call the code you want to test that calls `open`
m.VerifyAll()
m.UnsetStubs()

Обратите внимание, что __builtins__ не всегда является модулем, он может быть типа dict, используйте модуль __builtin__ (без "s") для ссылки на встроенные в систему методы.

Подробнее о модуле __builtin__: http://docs.python.org/library/встроенный.html

person Luke 10X    schedule 11.06.2011
comment
спасибо, это именно то, что я искал. +1 за использование мокс. - person jtolds; 02.03.2012

Есть два способа, которыми я предпочитаю это делать, в зависимости от ситуации.

Если ваш модульный тест будет вызывать ParseCsvFile напрямую, я бы добавил новый kwarg в ParseCsvFile:

def ParseCsvFile(source, open=open): 
    # ...
    rack_type_file = open(rack_file)  # Need to mock this line.

Затем ваш модульный тест может пройти другой open_func, чтобы выполнить насмешку.

Если ваш модульный тест вызывает какую-то другую функцию, которая, в свою очередь, вызывает ParseCsvFile, то передача open_func только для тестов будет уродливой. В этом случае я бы использовал фиктивный модуль. Это позволяет вам изменить функцию по имени и заменить ее фиктивным объектом.

# code.py
def open_func(name):
    return open(name)

def ParseCsvFile(source):
    # ...
    rack_type_file = open_func(rack_file)  # Need to mock this line.

# test.py
import unittest
import mock
from StringIO import StringIO

@mock.patch('code.open_func')
class ParseCsvTest(unittest.TestCase):
    def test_parse(self, open_mock):
        open_mock.return_value = StringIO("my,example,input")
        # ...
person Spike Gronim    schedule 08.03.2011
comment
Нет необходимости звонить заменяющему вас open open_func. Вы можете просто использовать open=open в сигнатуре своей функции и не нужно вносить какие-либо (потенциально запутывающие) изменения в коде. - person kindall; 21.06.2011

Просто с декоратором (Python3):

def my_method():
    with open(file="/1.txt", mode='r', encoding='utf-8') as file:
        return file.read().strip()


@mock.patch("builtins.open", create=True)
def test_my_method(mock_open):
    mock_open.side_effect = [
        mock.mock_open(read_data="A").return_value
    ]

    resA = my_method()
    assert resA == "A"

    mock_open.mock_calls ==  [mock.call(file="/1.txt", mode='r', encoding='utf-8')]
person wierzbiks    schedule 21.06.2017

Я взял на себя смелость переписать вашу примерную функцию:

Предположим, что ваша функция находится в файле с именем code.py.

# code.py
import csv

import logging


def ParseCsvFile(source):
    """Parse the csv file.
    Args:
      source: file to be parsed

    Returns: the list of dictionary entities; each dictionary contains
               attribute to value mapping or its equivalent.
    """
    global rack_file
    rack_file = source
    attributes_list = []

    try:
        rack_type_file = open(rack_file)
    except IOError, (errno, strerror):
        logging.error("I/O error(%s): %s", errno, strerror)
    else:
        reader = csv.DictReader(rack_type_file, delimiter=',')
        attributes_list = [line for line in reader]   # list of dictionaries
        rack_type_file.close()

    return attributes_list

Простым тестовым случаем будет:

# your test file
import __builtin__
import unittest
import contextlib
from StringIO import StringIO

import mox

import code


@contextlib.contextmanager
def mox_replayer(mox_instance):
    mox_instance.ReplayAll()
    yield
    mox_instance.VerifyAll()


class TestParseCSVFile(unittest.TestCase):

    def setUp(self):
        self.mox = mox.Mox()

    def tearDown(self):
        self.mox.UnsetStubs()

    def test_parse_csv_file_returns_list_of_dicts(self):
        TEST_FILE_NAME = 'foo.csv'

        self.mox.StubOutWithMock(__builtin__, 'open')
        open(TEST_FILE_NAME).AndReturn(StringIO("name,age\nfoo,13"))

        with mox_replayer(self.mox):
            result = code.ParseCsvFile(TEST_FILE_NAME)

        self.assertEqual(result, [{'age': '13', 'name': 'foo'}])  # works!


if __name__ == '__main__':
    unittest.main()

РЕДАКТИРОВАТЬ:

% /usr/bin/python2.6
Python 2.6.1 (r261:67515, Jun 24 2010, 21:47:49) 
[GCC 4.2.1 (Apple Inc. build 5646)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import __builtin__
>>> import mox
>>> mock = mox.Mox()
>>> mock.StubOutWithMock(__builtin__, 'open')
>>> mock.UnsetStubs()

Отлично работает на 2.6 с использованием mox 0.53

person Mahmoud Abdelkader    schedule 08.03.2011
comment
Привет, Махмуд, решение не сработало. В вашем решении происходит сбой в этой строке self.mox.StubOutWithMock(встроенные, 'open') - person Kartik; 12.03.2011
comment
Привет, Махмуд, решение не сработало. В этой строке происходит сбой self.mox.StubOutWithMock(встроенные модули, 'open'). встроенные — это диктофоны. Я пробовал self.mox.StubOutWithMox(встроенные.get('open'), 'call' и приведенные ниже методы: dir(встроенные .get('open')) ['вызов', 'класс', 'cmp', 'delattr', 'doc', 'eq', 'формат', 'ge', 'getattribute', 'gt', 'хэш', 'инициализация', 'файл ', 'lt', 'модуль', 'имя', 'ne', ' новый', 'reduce', 'reduce_ex', 'repr', 'self' , 'setattr', 'sizeof', 'str', 'subclasshook'] - person Kartik; 12.03.2011
comment
Я не смог вместить все в свой предыдущий комментарий. Сообщение об ошибке: File /usr/lib/pymodules/python2.6/mox.py, строка 226, в StubOutWithMock self.stubs.Set(obj, attr_name, stub) File /usr/lib/pymodules/python2.6/stubout .py, строка 120, в Set old_attribute = parent.__dict__.get(child_name) AttributeError: объект 'builtin_function_or_method' не имеет атрибута 'dict' - person Kartik; 12.03.2011
comment
встроенные — это не словарь, это модуль. вставьте весь имеющийся у вас код сюда: paste.pocoo.org и ответьте, указав URL-адрес. - person Mahmoud Abdelkader; 12.03.2011
comment
какую версию мокс используете? - person Mahmoud Abdelkader; 12.03.2011
comment
paste.pocoo.org/show/352285 Я установил pymox 0.5.3 из google-кода Веб-сайт. - person Kartik; 12.03.2011
comment
Почему вы делаете self.mox.StubOutWithMock(встроенные модули.get('open'), 'модуль', use_mock_anything=True)? Можете ли вы выполнить код, который я вставил выше? - person Mahmoud Abdelkader; 12.03.2011
comment
@Kartik, ты можешь выполнить эти строки кода и сказать мне, что они делают на твоей машине? Я обновил свой ответ. - person Mahmoud Abdelkader; 12.03.2011
comment
На самом деле вам нужен встроенный модуль. Бывает так, что встроенные разрешаются в встроенные модули в вашем конкретном примере, но в целом это не так. См. stackoverflow.com/questions/1184016/< /а>. Я соответствующим образом изменил код, хотя вижу, что Лукас Нормантас также дал правильный ответ ниже. - person Lorin Hochstein; 20.06.2011

Привет, у меня была похожая проблема, и я рвал на себе волосы, переключаясь между разными насмешливыми библиотеками. Наконец-то я нашел решение, которым я доволен, и, возможно, оно поможет вам? В конце концов я выбрал библиотеку Mocker http://labix.org/mocker, и вот код для имитации открытым:

from mocker import Mocker
from StringIO import StringIO
import __builtin__
mocker = Mocker()
sourceFile = 'myTestFile.txt'
__builtin__.open = mocker.mock()
__builtin__.open(sourceFile)
mocker.result(StringIO('this,is,a,test,file'))

<the rest of your test setup goes here>

mocker.replay()

ParseCsvFile(sourceFile)

mocker.restore()
mocker.verify()

Кстати, причина, по которой я выбрал Mocker, заключается в том, что я тестировал функцию, которая использовала open для чтения файла, а затем снова использовала open для перезаписи того же файла новыми данными. Что мне нужно было сделать, так это проверить случай, когда исходный файл не существует, поэтому настроил макет, который в первый раз выдавал IOError, а затем работал во второй раз. Настройка для которого выглядела так:

from mocker import Mocker
import __builtin__

mocker = Mocker()

mockFileObject = mocker.mock()
__builtin__.open = mocker.mock()

__builtin__.open('previousState.pkl', 'r') 
mocker.throw(IOError('Boom'))

__builtin__.open('previousState.pkl','w') 
mocker.result(mockFileObject)

<rest of test setup >

mocker.replay()

<test>

mocker.restore() #required to restore the open method
mocker.verify()

Надеюсь это поможет!

person Ctrlspc    schedule 11.01.2012

декоратор @mock.patch (пример 2.7)

Теперь это намного проще:

import your_script.py
import __builtin__
import mock


@mock.patch("__builtin__.open")
def test_example(self, mock_open):
    your_script.your_method()
    self.assertEqual(mock_open.call_count, 1)
person Jeremy Swagger    schedule 23.02.2018