Как предоставить условные аргументы для фиктивной функции в Python? - PullRequest
2 голосов
/ 30 апреля 2011

Мой проект выполняет различные вызовы внешних API, используя Python urllib2.urlopen. Я использую NoseTests для моего модульного тестирования и MiniMock для макетирования вызовов, сделанных на urllib2.urlopen.

Код издевательства:

from hashlib import md5
from os.path import dirname, join
from urllib2 import Request, urlopen

from minimock import mock, restore

def urlopen_stub(url, data=None, timeout=30):
    """
    Mock urllib2.urlopen and return a local file handle or create file if
    not existent and then return it.
    """

    if isinstance(url, Request):
        key = md5(url.get_full_url()).hexdigest()
    else:
        key = md5(url).hexdigest()
    data_file = join(dirname(__file__), 'cache', '%s.xml' % key)
    try:
        f = open(data_file)
    except IOError:
        restore() # restore normal function
        data = urlopen(url, data).read()
        mock('urlopen', returns_func=urlopen_stub, tracker=None) # re-mock it.
        with open(data_file, 'w') as f:
            f.write(data)
        f = open(data_file, 'r')
    return f

mock('urlopen', returns_func=urlopen_stub, tracker=None)

Я провожу свои тесты так:

from os.path import isfile, join
from shutil import copytree, rmtree

from nose.tools import assert_raises, assert_true

import urlopenmock

class TestMain(object):
    working = 'testing/working'

    def setUp(self):
        files = 'testing/files'
        copytree(files, self.working)

    def tearDown(self):
        rmtree(self.working)

    def test_foo(self):
        func_a_calling_urlopen()
        assert_true(isfile(join(self.working, 'file_b.txt')))

    def test_bar(self):
        func_b_calling_urlopen()
        assert_true(isfile(join(self.working, 'file_b.txt')))

    def test_bar_exception(self):
        assert_raises(AnException, func_c_calling_urlopen)

Изначально у меня была тестовая проверка на исключение в отдельном модуле, который импортировал другой фиктивный файл, который возвращал поврежденный XML-файл при вызове urlopen. Однако при импорте этого ложного класса был отменен тот, что показан выше, и все тесты были нарушены, поскольку каждый раз использовался сломанный XML.

Я предполагаю, что это произошло потому, что модуль тестирования исключений был загружен после других, поэтому его импорт был вызван последним, а смоделированная функция, возвращающая сломанный XML-файл, перекрыла исходную макетированную функцию.

Мне бы хотелось, чтобы при использовании test_bar_exception код проверки использовался для использования поврежденного файла XML, чтобы он вызывал исключение. Как бы я поступил так?

Ответы [ 2 ]

3 голосов
/ 01 мая 2011

Мне кажется, что вам нужно будет настраивать и отключать макет urlopen в каждом тесте, который должен его использовать, и иметь другой макет urlopen, чтобы возвращать сломанный xml-файл для тех тестов, которые имеют дело с этой ошибкой. Что-то вроде:

class TestMain(object):
    # ...

    def test_foo(self):
        mock('urlopen', returns_func=urlopen_stub, tracker=None)
        func_a_calling_urlopen()
        assert_true(isfile(join(self.working, 'file_b.txt')))
        restore()

    # ...

    def test_bar_exception(self):
        mock('urlopen', 
                returns_func=urlopen_stub_which_returns_broken_xml_file, 
                tracker=None)
        assert_raises(AnException, func_c_calling_urlopen)
        restore()

Однако существует проблема с вышеперечисленным, если тест вызывает исключение и вызов restore() не достигается. Вы можете обернуть try: ... finally:, однако, это много бюрократизма для каждого теста.

Возможно, вы захотите взглянуть на библиотеку mock Майкла Фурда.

Pycon презентация: http://blip.tv/file/4881513

Взгляните на patch , который предоставляет вам возможности декоратора и менеджера контекста для замены и восстановления ваших звонков на urlopen. Это очень изящно!

0 голосов
/ 03 апреля 2013

Предположим, что ваш ввод для запроса - это "url", а вывод - "aresponse", а для ввода "burl" - "bresponse", поэтому используйте

@mock.patch('requests.get', mock.Mock(side_effect = lambda k:{'aurl': 'a response', 'burl' : 'b response'}.get(k, 'unhandled request %s'%k)))
...