2012-04-02 3 views
10

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

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 на единичном уровне тестирования. Если бы я был экземпляр этого класса, давая ему путь, он будет нарушать rules of unit tests, потому что:

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

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

Итак, я хотел бы иметь возможность исправить метод __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]) 

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

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

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

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

ответ

5

Поскольку вы заинтересованы только в тестировании одного метода, почему бы вам не просто издеваться над всем классом 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, так как есть no more unbound methods.

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

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

Да, это то, что я искал. Спасибо. – Wilduck

+1

3 года спустя, но могут быть полезны для кого-то: после того, как вы получили объект 'mock', вы можете просто сделать' hold_data.x_coords (mock) 'и сохранить беспорядок при назначении методам вещам. – Holloway

1

Вы в основном работает в этот вопрос в связи с этим:

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

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

Если вы должны следовать этому правилу, вы должны изменить метод _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 позже. Если это невозможно, я оставил бы его как есть. Если ваши шесть строк макет кода являются единственной частью тестовых случаев, о которых вы беспокоитесь, тогда вы в порядке.

+0

Это на самом деле, как работает мой реальный класс. Метод '__init__' считывает файл в список строк, и из него работает' _parse'. Проблема в том, что этот метод делает * много *, и я хочу измотать все, кроме небольшой части, которую использует 'x_coords'. Я не уверен, правильно ли я делаю эту часть. – Wilduck

+0

См. Мое редактирование. Моя проблема в том, что невозможно представить полную подделку моего ввода. Я просто хочу проверить поведение одного метода 'x_coords', а не поведение' _parse' (у меня есть функциональные тесты для этого). – Wilduck

+0

В этом случае вы могли бы реорганизовать '_parse' и разделить его на отдельные функции. Это возможно? Затем вы можете предоставить часть ввода, которая может быть проанализирована, и затем вы можете протестировать 'x_coords'. –

Смежные вопросы