Знущання над двома функціями за допомогою патча для модульного тесту


78

У мене є функція, яку я хочу, щоб модульний тест містив виклики двох інших функцій. Я не впевнений, як я можу нормально знущатись над обома функціями, використовуючи патч. Я навів приклад того, що я маю на увазі нижче. Коли я запускаю носові тести, тести проходять, але я відчуваю, що це повинен бути більш чистий спосіб, і я насправді не розумію частину щодо f.close () ...

Структура каталогів виглядає так:

program/
  program/
    data.py
  tests/
    data_test.py

data.py:

import cPickle

def write_out(file_path, data):
    f = open(file_path, 'wb')
    cPickle.dump(data, f)
    f.close()

data_test.py:

from mock import MagicMock, patch

def test_write_out():
    path = '~/collection'
    mock_open = MagicMock()
    mock_pickle = MagicMock()
    f_mock = MagicMock()
    with patch('__builtin__.open', mock_open):
        f = mock_open.return_value
        f.method.return_value = path
        with patch('cPickle.dump', mock_pickle):
            write_out(path, 'data')
            mock_open.assert_called_once_with('~/collection', 'wb')
            f.close.assert_any_call()
            mock_pickle.assert_called_once_with('data', f)

Результати:

$ nosetests
.
----------------------------------------------------------------------
Ran 1 test in 0.008s
OK

1
Я думаю, що моє початкове запитання було незрозумілим, тому я його очистив. Сподіваюсь, це показує, що я точніше шукаю!
cnodell

Відповіді:


113

Ви можете спростити тест, використовуючи декоратор виправлень і вкладаючи їх так ( MagicMockза замовчуванням вони є об’єктами):

@patch('cPickle.dump')
@patch('__builtin__.open')
def test_write_out(mock_open, mock_pickle):
    path = '~/collection'
    f = mock_open.return_value
    f.method.return_value = path

    write_out(path, 'data')

    mock_open.assert_called_once_with('~/collection', 'wb')
    mock_pickle.assert_called_once_with('data', f)
    f.close.assert_any_call()

Виклики до MagicMockекземпляра повертають новий MagicMockекземпляр, тому ви можете перевірити, чи повертається значення викликалося так само, як і будь-який інший знущаний об’єкт. У цьому випадку fце MagicMockіменований 'open()'(спробуйте роздрукувати f).


9
У своїй пропозиції ви вводите два параметри, по одному для кожного макета. Як Python знає, що є що? Мені не вдалося знайти відповіді на це в документації.
кроки

54
Декоратори застосовуються знизу вгору, і порядок параметрів повинен відповідати цьому. Дивіться тут: voidspace.org.uk/python/mock/…
Матті Джон

1
Так, я прочитав це, але виявив недостатньо зрозуміле. Дякую!
кроки

5
Зверніть увагу, що параметри тут розташовані в протилежному порядку, в якому застосовувались виправлення.
Шубхем Чаудхарі,

Відмінно! Дякую за це.
R Claven

56

На додаток до відповіді @Matti John ви також можете використовувати patchвнутрішню функцію test_write_out:

from mock import MagicMock, patch

def test_write_out():
    path = '~/collection'
    with patch('__builtin__.open') as mock_open, \
            patch('cPickle.dump') as mock_pickle:

        f = mock_open.return_value
        ...

3

Ось простий приклад того, як перевірити підвищення ConflictErrorв create_collectionфункції за допомогою mock:

import os
from unittest import TestCase
from mock import patch
from ..program.data import ConflictError, create_collection


class TestCreateCollection(TestCase):
    def test_path_exists(self):
        with patch.object(os.path, 'exists') as mock_method:
            mock_method.return_value = True

            self.assertRaises(ConflictError, create_collection, 'test')

Будь ласка, також ознайомтесь з макетними документами та приголомшливим вступом Майкла Фоорда до макету .


Дякую, що ви навіть намагаєтесь мені допомогти. Це мені допомагає, але я більше зосереджувався на тому, як знущатись над кількома функціями за допомогою патча. На жаль, моє запитання не дало зрозуміти цього. Зараз я прибрав питання.
cnodell
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.