Насмешка над внутренней сущностью Python

Я хотел бы проверить метод, вызывает ли он определенный метод временного внутреннего объекта или нет. (ConfigParser.read)

Таким образом, объект создается внутри и недоступен снаружи после выхода из метода.

Использование питона 2.7

В foobar.py

 import ConfigParser

 class FooBar:
     def method(self, filename):
         config=ConfigParser.ConfigParser()
         config.read(filename)
         do_some_stuff()

Я хотел бы проверить, был ли вызван config.read.

Как я понимаю, патч-декоратор был сделан для этого, но, к сожалению, объект MagicMock, который получает тесткейс, не тот, который создается внутри, и я не могу подобраться к объекту, который живет внутри метода.

Я пробовал так:

class TestFooBar(TestCase):

    def setUp(self):
         self.myfoobar = FooBar()

    @mock.patch('foobar.ConfigParser')
    def test_read(self,mock_foobar):
        self.myfoobar.method("configuration.ini")
        assert mock_foobar.called # THIS IS OKAY
        assert mock_foobar.read.called # THIS FAILS
        mock_foobar.read.assert_called_with("configuration.ini") # FAILS TOO

Проблема в следующем: - mock_foobar создается до того, как self.myfoobar.method создает внутри ConfigReader. - при отладке mock_foobar имеет внутренние данные о предыдущих вызовах, но не имеет свойства "read" (внутренний MagicMock для имитации метода чтения)

Конечно, один из выходов - это рефакторинг и предоставление .read() или init() объекта ConfigReader, но не всегда возможно изменить код, и я хотел бы понять внутренние объекты. метода, не касаясь тестируемого модуля.


person PetrosHu    schedule 26.08.2015    source источник


Ответы (1)


Ты так близко! Проблема в том, что вы издеваетесь над классом, но затем ваш тест проверяет, что read() вызывается в этом фиктивном классе, но на самом деле вы ожидаете, что read() будет вызываться для экземпляра, который возвращается при вызове класса. Следующие работы - я нахожу второй тест более читаемым, чем первый, но они оба работают:

import ConfigParser
from unittest import TestCase

from mock import create_autospec, patch, Mock 


class FooBar(object):
    def method(self, filename):
        config=ConfigParser.ConfigParser()
        config.read(filename)


class TestFooBar(TestCase):

    def setUp(self):
         self.myfoobar = FooBar()

    @patch('ConfigParser.ConfigParser')
    def test_method(self, config_parser_class_mock):
        config_parser_mock = config_parser_class_mock.return_value

        self.myfoobar.method("configuration.ini")

        config_parser_class_mock.assert_called_once_with()
        config_parser_mock.read.assert_called_once_with("configuration.ini")

    def test_method_better(self):
        config_parser_mock = create_autospec(ConfigParser.ConfigParser, instance=True)
        config_parser_class_mock = Mock(return_value=config_parser_mock)

        with patch('ConfigParser.ConfigParser', config_parser_class_mock):
            self.myfoobar.method("configuration.ini")

        config_parser_class_mock.assert_called_once_with()
        config_parser_mock.read.assert_called_once_with("configuration.ini")
person Tom Dalton    schedule 26.08.2015
comment
Благодарю вас! К сожалению, первый просто не работает (assert AssertionError: Ожидается, что 'read' будет вызван один раз. Вызывается 0 раз.), у второго есть синтаксическая ошибка в строке with @mock (тоже нет как-то: в конце) . Я не упомянул, что это python 2.7, я обновлю вопрос этой информацией. - person PetrosHu; 27.08.2015
comment
О, извините, я вижу, вы импортируете модуль ConfigParser, а не класс. Будет обновляться соответственно. - person Tom Dalton; 27.08.2015