Дразнящая конструкция класса - PullRequest
11 голосов
/ 02 апреля 2012

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

class holds_data(object):
    def __init__(self, path):
        """Pulls complicated data from a file, given by 'path'.

        Stores it in a dictionary. 
        """
        self.data = {}
        with open(path) as f:
            self.data.update(_parse(f))

    def _parse(self, file):
        # Some hairy parsing code here
        pass

    def x_coords(self):
        """The x coordinates from one part of the data
        """
        return [point[0] for point in self.data['points']]

Кодвыше упрощение того, что у меня есть.В действительности _parse является довольно значительным методом, для которого у меня есть тестовое покрытие на функциональном уровне.

Я хотел бы, однако, иметь возможность тестировать x_coords на уровне модульного тестирования.Если бы я создал экземпляр этого класса, указав ему путь, он нарушил бы правила юнит-тестов , потому что:

Тест не является юнит-тестом, если:

  • Это касается файловой системы

Итак, я хотел бы иметь возможность исправить метод __init__ для holds_data, а затем просто заполнить частьself.data необходимо для x_coords.Примерно так:

from mock import patch
with patch('__main__.holds_data.__init__') as init_mock:
    init_mock.return_value = None
    instance = holds_data()
    instance.data = {'points':[(1,1),(2,2),(3,4)]}
    assert(instance.x_coords == [1,2,3])

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

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

Мой вопрос о том, есть ли здесь запах кода, сводится к этой проблеме:

Я уверен, что было бы проще, если бы я рефакторил, чтобы x_coords была автономной функцией, которая принимает holds_data в качестве параметра.Если «легче тестировать == лучший дизайн», это будет путь.Тем не менее, потребуется x_coords функция, чтобы узнать больше о внутренностях holds_data, с которыми мне обычно было бы удобно.Где я должен сделать компромисс?Чистый код или чистые тесты?

Ответы [ 2 ]

6 голосов
/ 03 апреля 2012

Поскольку вас интересует только тестирование одного метода, почему бы вам не просто высмеять весь класс HoldsData и прикрепить к нему метод x_coords?

>>> mock = MagicMock(data={'points': [(0,1), (2,3), (4,5)]})
>>> mock.x_coords = HoldsData.__dict__['x_coords']
>>> mock.x_coords(mock)
[0, 2, 4]

Таким образом, вы 'Вы будете иметь полный контроль над входом и выходом x_coords (по побочному эффекту или возвращаемому значению).

Примечание: В py3k вы можете просто сделать mock.x_coords = HoldsData.x_coords, так как есть не более unbound methods.

Это также можно сделать в конструкторе фиктивного объекта:

MagicMock(data={'points': [(0,1), (2,3), (4,5)]}, x_coords=HoldsData.__dict__['x_coords'])
1 голос
/ 02 апреля 2012

Вы в основном сталкиваетесь с этой проблемой из-за этого:

Тест не является модульным тестом, если:

  • Он касается файловой системы

Если вы хотите следовать этому правилу, вам следует изменить метод _parse.В частности, он не должен принимать файл в качестве входных данных.Задача _parse состоит в том, чтобы проанализировать данные, но , откуда данные поступают, не относится к этому методу.

У вас может быть строка, содержащая те же данные, которые затемперешел на _parse.Точно так же данные могут поступать из базы данных или где-то еще целиком.Когда _parse принимает только данные в качестве входных данных, вы можете тестировать этот метод гораздо проще.

Это будет выглядеть примерно так:

class HoldsData(object):
    def __init__(self, path):
        self.data = {}
        file_data = self._read_data_from_file(path)
        self.data.update(self._parse(file_data))

    def _read_data_from_file(self, path):
        # read data from file
        return data

    def _parse(self, data):
        # do parsing

Чистый код приводит к чистомутесты конечно.В лучшем случае было бы смоделировать данные и предоставить _parse входные данные, а затем проверить x_coords позже.Если это невозможно, я бы оставил все как есть.Если ваши шесть строк фиктивного кода являются единственной частью тестовых случаев, о которых вы беспокоитесь, то все в порядке.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...