Каковы наилучшие методы тестирования различных слоев в Django?

Я НЕ новичок в тестировании, но я действительно запутался в беспорядке рекомендаций по тестированию различных слоев в Django.

Некоторые рекомендуют (и они правы) избегать Doctests в модели, так как они не поддерживаются...

Другие советуют не использовать фикстуры, поскольку они менее гибкие, чем, например, вспомогательные функции.

Есть также две группы людей, которые борются за использование объектов Mock. Первая группа верит в использование Mock и изоляцию остальной системы, в то время как другая группа предпочитает прекратить использование Mock и начать тестирование.

Все, что я упомянул выше, в основном касалось тестовых моделей. Функциональное тестирование — это отдельная история (с использованием test.Client() VS webTest VS и т. д.)

Существует ли ЛЮБОЙ поддерживаемый, расширяемый и правильный способ тестирования различных уровней??

ОБНОВЛЕНИЕ

Мне известно о выступлении Карла Мейера на PyCon 2012..


person Community    schedule 18.07.2012    source источник


Ответы (2)


ОБНОВЛЕНИЕ ОТ 07.08.2012

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

1.- Используйте Fixtures только для информации, которая необходима для тестирования, но не будет меняться. , например, вам нужен пользователь для каждого теста, который вы делаете, поэтому используйте фикстуру base для создания пользователей.

2. Используйте фабрику для создания своих объектов, лично мне нравится FactoryBoy (это взято из FactoryGirl, которая представляет собой библиотеку ruby). Я создаю отдельный файл с именем factory.py для каждого приложения, в котором сохраняю все эти объекты. Таким образом, я удаляю из тестовых файлов все объекты, которые мне нужны, что делает его более читабельным и простым в обслуживании. Крутая вещь в этом подходе заключается в том, что вы создаете базовый объект, который можно изменить, если вы хотите протестировать что-то еще на основе какого-то объекта из фабрики. Также это не зависит от django, поэтому, когда я перенес эти объекты, когда начал использовать mongodb и мне нужно было их протестировать, все прошло гладко. Теперь, прочитав о фабриках, принято говорить: «Зачем мне тогда использовать фикстуры». Так как эти фикстуры никогда не должны меняться, все дополнительные возможности от фабрик бесполезны, а django очень хорошо поддерживает фикстуры из коробки.

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

4.- Я использую сервер интеграции (здесь я рекомендую jenkins), который запускает тесты каждый раз, когда я нажмите на мой промежуточный сервер, и если они не сработают, он отправит мне электронное письмо. Это просто здорово, так как со мной часто случается, что я сломал что-то еще в своем последнем изменении и забыл запустить тесты. Он также дает вам другие полезные функции, такие как отчет о покрытии, pylint/jslint/pep8, и существует множество плагинов, с помощью которых вы можете установить разную статистику.

Что касается вашего вопроса о тестировании внешнего интерфейса, django поставляется с помощником. функции, чтобы справиться с этим простым способом.

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

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

Я приведу пример приложения, в котором я могу применить эту практику, чтобы оно было более понятным. Давайте создадим очень простое приложение для блога:

структура

blogger/
    __init__.py
    models.py
    fixtures/base.json
    factories.py
    tests.py

models.py

 from django.db import models

 class Blog(models.Model):
     user = models.ForeignKey(User)
     text = models.TextField()
     created_on = models.DateTimeField(default=datetime.now())

фикстуры/base.json

[
{
    "pk": 1,
    "model": "auth.user",
    "fields": {
        "username": "fragilistic_test",
        "first_name": "demo",
        "last_name": "user",
        "is_active": true,
        "is_superuser": true,
        "is_staff": true,
        "last_login": "2011-08-16 15:59:56",
        "groups": [],
        "user_permissions": [],
        "password": "IAmCrypted!",
        "email": "[email protected]",
        "date_joined": "1923-08-16 13:26:03"
    }
}
]

factories.py

import factory
from blog.models import User, Blog

class BlogFactory(factory.Factory):
    FACTORY_FOR = Blog

    user__id = 1
    text = "My test text blog of fun"

tests.py

class BlogTest(TestCase):
    fixtures = ['base']  # loads fixture

    def setUp(self):
        self.blog = BlogFactory()
        self.blog2 = BlogFactory(text="Another test based on the last one")

    def test_blog_text(self):
        self.assertEqual(Blog.objects.filter(user__id=1).count(), 2)

    def test_post_blog(self):
        # Lets suppose we did some views
        self.client.login(username='user', password='IAmCrypted!')
        response = self.client.post('/blogs', {'text': "test text", user='1'})

        self.assertEqual(response.status, 200)
        self.assertEqual(Blog.objects.filter(text='test text').count(), 1)

    def test_mocker(self):
        # We will mock the datetime so the blog post was created on the date
        # we want it to
        mocker = Mock()
        co = mocker.replace('datetime.datetime')
        co.now()
        mocker.result(datetime.datetime(2012, 6, 12))

        with mocker:
            res = Blog.objects.create(user__id=1, text='test')

        self.assertEqual(res.created_on, datetime.datetime(2012, 6, 12))

    def tearDown(self):
        # Django takes care of this but to be strict I'll add it
        Blog.objects.all().delete()

Обратите внимание, что я использую некоторую конкретную технологию для примера (которая, кстати, не тестировалась).

Я должен настаивать на том, что это может быть не лучшая стандартная практика (в которой я сомневаюсь, что она существует), но она работает очень хорошо для меня.

person Hassek    schedule 18.07.2012
comment
+1 за хорошие советы, особенно 2 и 3. Размещение файлов factory.py в каждом приложении кажется интересным, но что, если у меня есть отдельная папка для тестов в корневом каталоге моего проекта? Обычно я отделяю тесты от кода и передаю их в Nose. - person Soask; 18.07.2012
comment
вы можете разделить тесты приложений для каждого приложения и создать там файл фабрик или создать разные файлы фабрик для каждого приложения, например dashboard_factory. Но создание одного файла для всех ваших фабрик станет огромным, если вы работаете над средним проектом и серьезно тестируете - person Hassek; 18.07.2012
comment
Итак, вы имеете в виду, что я могу поместить каждый factory.py в соответствующее тестовое приложение. Следовательно, если у меня есть приложение под названием курсы, там будет ROOT/tests/courses/factories.py .. Это очень хорошая идея .. спасибо :) - person Soask; 18.07.2012
comment
надеюсь, это вас заведет, идите тестируйте! ха - person Hassek; 18.07.2012
comment
не отделяйте свои тесты от своего кода :) - person ashwoods; 18.07.2012
comment
вы можете задать другой вопрос, потому что он довольно длинный, но вкратце: вы хотите, чтобы архитектура проекта была максимально модульной. Как вы хотите сделать это в django/python, чтобы все было в виде портативных автономных приложений. В идеале ваш проект должен иметь не более чем settings.py и urls.py. Мои приложения имеют 3 этапа: ядро, библиотеки и вклад (сторонние, включая самодельные библиотеки с открытым исходным кодом). Либы идентичны contrib по структуре, они живут вне проекта и имеют свой setup.py и репозиторий git. Вы хотите, чтобы ваш код перемещался слева направо. И объединение тестов... - person ashwoods; 19.07.2012
comment
... это всего лишь часть создания переносимых, многоразовых, модульных приложений и библиотек. - person ashwoods; 19.07.2012
comment
никогда не думал об этом раньше .. спасибо, что поделились своей идеей - person Soask; 19.07.2012
comment
Как вы на самом деле реализуете № 3? Это отличная идея, но есть ли способ лучше, чем if 'test' in sys.argv? Я не хочу разбрасывать фиктивные данные по моему проекту... - person supervacuo; 06.08.2012
comment
Я использую библиотеку mocker. - person Hassek; 06.08.2012
comment
Спасибо за это. Любопытно: как выглядит ваш tearDown? - person Ghopper21; 08.08.2012
comment
В этом случае django позаботится об этом, но, чтобы быть более строгим, вы можете сами поместить объекты Blog в tearDown. Я только что обновил ответ с ним - person Hassek; 08.08.2012

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

Тем не менее, большинство из нас по-прежнему стремятся к одним и тем же целям при тестировании наших приложений, в основном:

  • Аккуратная организация наших тестовых модулей
  • Создание повторно используемых утверждений и вспомогательных методов, вспомогательных функций, которые уменьшают LOC для тестовых методов, чтобы сделать их более компактными и читабельными.
  • Демонстрация очевидного систематического подхода к тестированию компонентов приложения.

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

Нет приспособлений для тестовых случаев

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

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

Кроме того, вы, скорее всего, будете использовать множество видов фикстур в своих тестах, а не только для моделей: вы хотели бы хранить тело ответа от запросов API, создавать фикстуры, предназначенные для серверных частей базы данных NoSQL, писать имеющиеся фикстуры, которые используются для заполнения данных формы и т. д.

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

Широко использовать фабрики

Фабричные функции и методы предпочтительнее, чем вытаптывание ваших тестовых данных. Вы можете создавать вспомогательные фабричные функции уровня модуля или методы тестового примера, которые вы можете повторно использовать в тестах приложения или во всем проекте. В частности, factory_boy, о котором упоминает @Hassek, предоставляет вам возможность наследовать/расширять данные приборов и выполнять автоматическую последовательность, которая может выглядеть немного неуклюже, если бы вы сделали это вручную в противном случае.

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

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

Мой личный подход к этой проблеме заключается в том, чтобы начать с модуля myproject.factory, который создает легкодоступные ссылки на методы QuerySet.create для моих моделей, а также для любых объектов, которые я мог бы регулярно использовать в большинстве своих тестов приложений:

from django.contrib.auth.models import User, AnonymousUser
from django.test import RequestFactory

from myproject.cars.models import Manufacturer, Car
from myproject.stores.models import Store


create_user = User.objects.create_user
    create_manufacturer = Manufacturer.objects.create
create_car = Car.objects.create
create_store = Store.objects.create

_factory = RequestFactory()


def get(path='/', data={}, user=AnonymousUser(), **extra):
    request = _factory.get(path, data, **extra)
    request.user = user

    return request


def post(path='/', data={}, user=AnonymousUser(), **extra):
    request = _factory.post(path, data, **extra)
    request.user = user

    return request

Это, в свою очередь, позволяет мне сделать что-то вроде этого:

from myproject import factory as f  # Terse alias

# A verbose, albeit readable approach to creating instances
manufacturer = f.create_manufacturer(name='Foomobiles')
car1 = f.create_car(manufacturer=manufacturer, name='Foo')
car2 = f.create_car(manufacturer=manufacturer, name='Bar')

# Reduce the crud for creating some common objects
manufacturer = f.create_manufacturer(name='Foomobiles')
data = {name: 'Foo', manufacturer: manufacturer.id)
request = f.post(data=data)
view = CarCreateView()

response = view.post(request)

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

Используйте насмешки, но используйте их с умом

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

# Creating mocks to simplify tests
factory = RequestFactory()
request = factory.get()
request.user = Mock(is_authenticated=lamda: True)  # A mock of an authenticated user
view = DispatchForAuthenticatedOnlyView().as_view()

response = view(request)


# Patching objects to return expected data
@patch.object(CurrencyApi, 'get_currency_list', return_value="{'foo': 1.00, 'bar': 15.00}")
def test_converts_between_two_currencies(self, currency_list_mock):
    converter = Converter()  # Uses CurrencyApi under the hood

    result = converter.convert(from='bar', to='foo', ammount=45)
    self.assertEqual(4, result)

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

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

Прежде всего, будьте последовательны. Очень очень последовательно

Это самый важный момент, который нужно сделать. Будьте последовательны абсолютно во всем:

  • как вы организуете код в каждом из ваших тестовых модулей
  • как вы вводите тестовые примеры для компонентов вашего приложения
  • как вы вводите методы тестирования для подтверждения поведения этих компонентов
  • как вы структурируете методы тестирования
  • как вы подходите к тестированию общих компонентов (представлений на основе классов, моделей, форм и т. д.)
  • как вы применяете повторное использование

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

person Filip Dupanović    schedule 07.08.2012