Подтвердить что ты не робот

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

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

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

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

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

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

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

ОБНОВЛЕНИЕ

Я знаю Обсуждение Carl Meyer в PyCon 2012..

4b9b3361

Ответ 1

ОБНОВЛЕНИЕ 08-07-2012

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

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

2.- Используйте factory для создания ваших объектов, я лично люблю FactoryBoy (это происходит от FactoryGirl, который является рубиновой библиотекой). Я создаю отдельный файл под названием factories.py для каждого приложения, где я сохраняю все эти объекты. Таким образом, я сохраняю файлы тестов для всех объектов, которые мне нужны, что делает его более читаемым и простым в обслуживании. Самое интересное в этом подходе заключается в том, что вы создаете базовый объект, который можно изменить, если вы хотите проверить что-то другое на основе какого-либо объекта из factory. Кроме того, это не зависит от 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()

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

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

Ответ 2

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

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

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

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

Нет проверочных приборов

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

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

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

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

Широкое использование заводов

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

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

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

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

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

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

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