Модульное тестирование в Джанго

12

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

Вот моя проблема. Моя схема глубоко реляционная и в значительной степени ориентирована на время, что дает моему объекту модели высокую внутреннюю связь и множество состояний. Многие из моих методов модели запрашивают на основе временных интервалов, и я много работаю auto_now_addв полях с метками времени. Итак, возьмем метод, который выглядит следующим образом:

def summary(self, startTime=None, endTime=None):
    # ... logic to assign a proper start and end time 
    # if none was provided, probably using datetime.now()

    objects = self.related_model_set.manager_method.filter(...)

    return sum(object.key_method(startTime, endTime) for object in objects)

Как подходить к тестированию чего-то подобного?

Вот где я так далеко. Мне приходит в голову, что объекту модульного тестирования нужно дать какое-то насмешливое поведение by key_methodв своих аргументах, summaryправильно ли фильтрует / агрегирует для получения правильного результата?

Насмешка datetime.now () достаточно проста, но как я могу издеваться над остальным поведением?

  • Я мог бы использовать приборы, но я слышал о плюсах и минусах использования приборов для построения моих данных (плохая ремонтопригодность - это мошенничество, которое мне кажется удачным).
  • Я мог бы также настроить свои данные через ORM, но это может быть ограничением, потому что тогда я должен также создавать связанные объекты. И ORM не позволяет вам связываться с auto_now_addполями вручную.
  • Пересмешивание ORM - это еще один вариант, но не только непросто смоделировать глубоко вложенные методы ORM, но и логика в коде ORM исключается из теста, и, похоже, пересмотр делает тест действительно зависимым от внутренних компонентов и зависимостей функция-под-теста.

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

acjay
источник
Сначала вы должны написать модульные тесты, которые помогут вам выявить проблемы тестируемости в вашем проекте до того, как будет написан реальный производственный код.
Chedy2149
2
Это полезно, но на самом деле не решает вопрос о том, как лучше всего тестировать приложения с высоким состоянием и ORM-нагрузкой.
Acjay
Вы должны абстрагироваться от слоя персистентности
Chedy2149
1
Гипотетически звучит великолепно, но когда дело доходит до сопровождения проекта, я думаю, что вставка нестандартного слоя персистентности между бизнес-логикой и чрезвычайно хорошо документированным Django ORM не требует особых затрат. Внезапно классы заполняются кучей крошечных промежуточных методов, которые сами должны быть реорганизованы с течением времени. Но, возможно, это оправдано в тех местах, где тестируемость имеет решающее значение.
Acjay
Заказ это: vimeo.com/43612849 и это: vimeo.com/15007792
Chedy2149

Ответы:

6

Я собираюсь пойти дальше и зарегистрировать ответ на то, что я до сих пор придумал.

Моя гипотеза заключается в том, что для функции с глубокой связью и состоянием реальность такова, что для ее внешнего контекста просто потребуется много строк.

Вот как примерно выглядит мой контрольный пример, опираясь на стандартную библиотеку Mock:

  1. Я использую стандартный ORM для настройки последовательности событий
  2. Я создаю свое собственное начало datetimeи смещаю auto_now_addвремя, чтобы соответствовать фиксированному графику моего проекта. Я думал, что ORM этого не допустил, но работает нормально.
  3. Я проверяю, использует ли тестируемая функция, from datetime import datetimeчтобы я мог исправить datetime.now()только эту функцию (если я высмеиваю весь datetimeкласс, ORM подгоняет).
  4. Я создаю свою собственную замену object.key_method(), с простой, но четко определенной функциональностью, которая зависит от аргументов. Я хочу, чтобы это зависело от аргументов, потому что иначе я мог бы не знать, работает ли логика тестируемой функции. В моем случае он просто возвращает количество секунд между startTimeи endTime. Я исправляю это, оборачивая его в лямбду и исправляя непосредственно к object.key_method()использованию new_callablekwarg of patch.
  5. Наконец, я запускаю серию summaryутверждений по различным вызовам с разными аргументами, чтобы проверить равенство с ожидаемыми вычисленными вручную результатами с учетом заданного поведения макета.key_method

Излишне говорить, что это значительно дольше и сложнее, чем сама функция. Это зависит от БД и не похоже на юнит-тест. Но он также довольно не связан с внутренними элементами функции - только ее сигнатура и зависимости. Поэтому я думаю, что на самом деле это может быть модульное тестирование.

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

acjay
источник