Тестирование в Django
При разработке веб-приложений большинство программистов часто избегают тестирования. Я основном говорю о начинающих программистах. Да и многие , кто уже довольно долго в этой профессии и которые разрабатывают коммерческие приложения довольно часто избегают тестирования , а многие незнакомы с ней
В этой статье мы будем рассматривать тестирования для веб-приложений , которые создаются с использованием фреймворка Django
Так для чего вам нужно тестирование и зачем тратить на это время и ресурсы?
Как забавно не звучало бы на первый взгляд , но тестирование позволяет вам экономить ваше время. Как обычно происходит проверка работоспособности написанного кода, если вы не используете тестирование ? Мы обычно это делаем вручную , вводя определенные данные и смотря как работает наш код. Но со временем проект разрастается и у вас десятки компонентов , которые взаимодействуют друг с другом и измненения в одном компоненте может влиять на другие компоненты. И это будет занимать большое время при ручном тестировании системы, а некоторые ошибки вы не сможете отследить до того , пока это не окажется в продакшене. А автоматизированные тесты помогут вам отследить многие ошибки до выкладки на продакшен.
Когда вы пишите новый код, то написанные вами правильно тесты позволяют убедиться в том , что ваш код работает правильно.
Также , при измении вами старого кода или кто-то из ваших коллег изменит какую-то часть нашего приложения , то тесты покажут, поломался ли ваш функционал или нет.
Тестирование веб-приложений — очень сложная задача. Так как нам нужно протестировать обработку HTTP запросов , валидацию форм , отрисовку шаблонов. Но во фреймворке Django есть средства которые упрощают нам тестирование и в этой статье рассмотрим конкретно тестирование использованием этих средств
python manage.py startapp blog
Давайте вначале опишем наши модели для блога
from django.db import models from django.utils import timezone class Category(models.Model): name = models.CharField(max_length=255) slug = models.SlugField(max_length=255) def __str__(self): return self.name class Meta: verbose_name = 'Категория' verbose_name_plural = 'Категории' def get_absolute_url(self): return f'/category//' class Post(models.Model): class Statuses(models.TextChoices): DRAFT = 'DRAFT', 'Черновик' PUBLISHED = 'PUBLISHED', 'Опубликован' title = models.CharField(max_length=255) slug = models.SlugField(max_length=1024) content = models.TextField() created_at = models.DateTimeField(default=timezone.now) updated_at = models.DateTimeField() status = models.CharField(max_length=10, choices = Statuses.choices, default=Statuses.DRAFT) category = models.ForeignKey(Category, related_name='posts', on_delete=models.CASCADE) def __str__(self): return self.title class Meta: ordering = ('-created_at',) verbose_name = 'Пост' verbose_name_plural = 'Посты' @property def is_published(self): return self.status == Post.Statuses.DRAFT
Создадим миграции и применим их следующими командами:
python manage.py makemigrations python manage.py migrate
Как вы можете заметить , внутри app есть файл tests.py, где мы можем написать наши тесты для этого приложения. Для начала это приемлемый путь.
У модели Category есть метод get_absolute_url , который возвращает url с использованием поля slug. В нашем первом тесте мы проверим правильно ли работает этот метод для вновь созданного объекта модели Category. Создадим наш первый тестовый класс CategoryModelTests который наследуется от TestCase и напишем первый тест методом test_absolute_url_is_correct. Методы должны начинаться с «test_»
from django.test import TestCase from blog.models import Category class CategoryModelTests(TestCase): def test_absolute_url_is_correct(self): new_category = Category(name='Первый пост', slug='first-post') new_category.save() self.assertEqual(new_category.get_absolute_url(), '/category/first-post/')
python manage.py test
Наш тест запустился и отработал успешно

Для модели Post у нас есть метод is_published , который возвращает опубликован ли пост или нет. Этот метод неправильно работает и это сделано специально , чтобы найти неправильное поведение при тестировании. Давайте напишем новый тест для модели Post, который проверяет правильно ли работает метод is_published
from django.test import TestCase from blog.models import Category, Post class CategoryModelTests(TestCase): def test_absolute_url_is_correct(self): new_category = Category.objects.create(name='Первый пост', slug='first-post') self.assertEqual(new_category.get_absolute_url(), '/category/first-post/') class PostModelTests(TestCase): def test_is_published_post(self): category = Category.objects.create(name='Первый пост', slug='first-post') new_post = Post.objects.create(title='Первый опубликованный пост', slug='first-published-post', category=category, status=Post.Statuses.PUBLISHED, ) self.assertTrue(new_post.is_published)
После запуска тестов мы видим , что у нас запустилось два теста и один тест упал. Тут в выводе мы видим на какой строчке упал и по какой причине

Давайте пофиксим наш метод Is_published в модели Post
@property def is_published(self): return self.status == Post.Statuses.PUBLISHED
Запустим заново тесты и посмотрим на результат:

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

В предыдущем посте мы бегло рассмотрели некоторые приемы тестирования кода на питоне. Все это применимо также и к Django-проектам, безусловно, но есть достаточное количество подводных камней и просто интересных штук, о которых я попробую рассказать.
- тестирование веб-сайтов — это сложно и непонятно
- юнит-тесты в django
- тестовая БД и как с ней бороться
- smoke testing
- покрытие кода (code coverage)
Тестирование веб-сайтов
Самый главный подводный айсберг тестирования Django-проектов заключается в том, что недостаточно написать тесты для питонокода. Разваливается верстка, JavaScript живет своей жизнью, веб-сервер не выдержал нагрузки и превратился в тыкву — все эти вещи выявить при помощи тестирования несколько сложнее, чем отследить неверный результат функции.
Поэтому проверка работоспособности веб-сайта — это обычно сложное явление, состоящее из нескольких независимых наборов тестов, часть которых (проверка внешнего вида в различных браузерах, например) может предполагать участие оператора. При отсутствии отдела QA роль тестировщика нередко возлагают на конечного пользователя, который потом всячески ругается. Так делать неправильно. Полковник Очевидность пост сдал.
Начнем же мы с (относительно) простых и понятных юнит-тестов.
Юнит-тесты в Django
Юнит-тесты в Django живут в модуле django.utils.unittest и являют собой расширение стандартного модуля unittest из поставки python 2.7 (unittest2). Что добавлено:
Тестовый HTTP-клиент. Имитирует работу браузера, может отправлять get- и post-запросы, сохраняет cookies между вызовами.
>>> from django.test.client import Client >>> c = Client() >>> response = c.post('/login/', ) >>> response.status_code 200
С тестовым клиентом связан ряд ограничений. Например, запросить можно только относительный путь, URL вида http://localhost:8000/ не сработает (по понятным причинам).
Расширенный набор проверок. Помимо стандартного набора, класс django.test.TestCase содержит также django-специфичные методы assert* , например:
assertContains(response, text, . ) # проверяет, что в ответе сервера содержится указанный текст; assertTemplateUsed(response, template_name, . ) # проверяет, что при рендеринге страницы использовался указанный шаблон; assertRedirects(response, expected_url, . ) # проверяет, было ли перенаправление;
Тестирование почты. Модуль django.core.mail сохраняет в переменной outbox список всех отправленных посредством send_mail() писем.
Условное исключение тестов. В случае, если выбранная СУБД не поддерживает (или, наоборот, поддерживает) транзакционность, можно исключить заведомо сломанные тесты при помощи декоратора @skipUnlessDBFeature(‘supports_transactions’) или @skipIfDBFeature(‘supports_transactions’) .
Тестирование запускается вот так:
$ ./manage.py test [список приложений]
По умолчанию прогоняются все тесты для всех приложений, перечисленных в INSTALLED_APPS . Пускалка (на языке оригинала — test runner) найдет юнит- и доктесты в файлах models.py и tests.py внутри каждого приложения. Чтобы импортировать доктесты из других модулей, можно использовать следующую запись:
from utils import func_a, func_b __test__ =
Здесь func_* — функция (или другая сущность), docstring которой нас интересует.
Для наблюдателя процесс тестирования выглядит следующим образом:
$ ./manage.py test main Creating test database for alias 'default'. . Ran 10 tests in 0.790s OK Destroying test database for alias 'default'.
Тестовая БД и как с ней бороться
Для запуска тестов Django всегда создает новую БД, чтобы исключить вероятность уничтожения данных в рабочем окружении. Если в settings.py не указано иное, тестовая БД предваряется словом test_. Применимо к MySQL, привилегии обычно задаются как-то так:
GRANT ALL PRIVILEGES ON `project`.* TO 'user'@'localhost'; GRANT ALL PRIVILEGES ON `test_project`.* TO 'user'@'localhost';
Создавать саму БД test_project при этом не нужно.
Хозяйке на заметку. Все работает быстрее, если добавить в конфиг MySQL строку
[mysqld] skip-sync-frm=OFF
Умозрительно, что сразу после создания никаких полезных данных в БД нет. Чтобы не порождать тестовый набор данных внутри каждого теста в отдельности, можно сделать это один раз и сохранить в fixture:
$ ./manage.py dumpdata > app/fixtures/test_data.json
class HelloTestCase(TestCase): fixtures = ['test_data.json', 'moar_data.json']
И еще. Старайтесь использовать для разработки и тестирования ту же СУБД, что и на production-сервере. Это сделает ваш сон на 28% * спокойнее.
* научно доказано, что 87.56% статистики берется с потолка.
Smoke testing
В среде радиолюбителей термин smoke test означает буквально следующее: подключаем к свежесобранной схеме питание и наблюдаем, в каком месте из нее пошел дым. Если дым не пошел, можно приступать к более наукообразной проверке правильности работы схемы.
Описанный подход практикуют также при тестировании приложений. Применимо к Django имеет определенный смысл описывать в tests.py точки входа из URLconf, например, так:
urlpatterns = patterns(None, url(r'^registration/$', registration, name='registration'), url(r'^login/$', . name='login'), url(r'^logout/$', logout_then_login, name='logout'), )
tests.py
from django import test from django.core.urlresolvers import reverse __test__ = >> c = test.Client() >>> c.get(reverse('registration')).status_code 200 >>> c.get(reverse('login')).status_code 200 >>> c.get(reverse('logout')).status_code 302 """>
Безусловно, такая проверка не заменит функционального тестирования регистрации и логина. Полковник Очевидность пост принял.
Покрытие кода (code coverage)
Покрытие кода — это метрика, показывающая, какой объем исходного кода был протестирован относительно всего объема полезного исходного кода в приложении. Низкое покрытие кода указывает на отсутствие тестов.
Хозяйке на заметку-2. Высокое покрытие кода не говорит об отсутствии ошибок (ни в коде, ни в тестах), это вымысел.
Для измерения покрытия кода на питоне существует coverage.py. Гугл помнит много попыток подружить coverage.py и Django, есть даже тикет #4501 (ему четыре года).
И сразу ложка дегтя: с Django 1.3 (и dev-версией) ни одно готовое решение для code coverage, похоже, не работает (поправьте меня, если это не так). Что, впрочем, не помешает нам запустить coverage.py руками.
$ coverage run --source=main,users manage.py test main users $ coverage html # генерация отчета

Перечислим только интересующие нас модули (ключ —source); если не указать, там будет в том числе django, mysqldb и половина стандартной поставки питона.
После этого в папке htmlcov (путь по умолчанию) можно наблюдать детальный отчет по каждой строке кода, покрытие по модулям и суммарное по проекту.
В следующем выпуске: статический анализ как превентивная мера, тестирование верстки и JS, нагрузочное тестирование.
Как покрыть приложение на Django модульными тестами
Примечание: чтобы использовать это руководство, вам понадобятся навыки, приобретенные после прочтения Как создать API с помощью Python и Django, а также код, над которым мы работали тогда. Мы продолжим работу с тем же API для приложения со списком дел, и будем использовать вот эту версию на GitHub.
Прошлым летом я работал над веб-приложением на Django со своими друзьями. Как-то раз я создал одну функцию и отправил своему другу на тестирование. Он ответил, что не смог ее протестировать, поскольку я случайно сломал функцию входа в систему. Однако это был не единственный случай, что-то подобное происходило каждую неделю. Основная проблема заключалась в том, что мы не использовали полноценное автоматизированное тестирование в нашем рабочем процессе.
Что такое автоматизированное тестирование?
Тестирование кода — одна из важнейших частей разработки. Тестирование может быть разным: можно протестировать приложение, всего лишь взглянув на веб-страницу, поиграв в видеоигру или проанализировав логи. Все зависит от типа вашего проекта. Ручное тестирование отнимает много времени и допускает возможность ошибок, поэтому профессиональные разработчики стараются уделять больше внимания автоматизированному тестированию. В этой статье мы рассмотрим общий вид автоматизированного тестирования, модульные тесты, а также поговорим о том, как автоматизированное тестирование может помочь нам в процессе разработки. Мы начнем с написания тестов для моделей и представлений (views) существующего API, затем, добавив новую функцию, попрактикуемся в разработке через тестирование.
Автоматизированное тестирование экономит время и делает программное обеспечение качественней. Поначалу ручное тестирование кажется быстрым: нужно просто запустить код и посмотреть, работает ли он. Однако со временем, чем больше и больше функций вы добавляете в свое приложение, тем больше времени начинает отнимать такой тип тестирования. К тому же вы можете просто забыть протестировать определенные вещи. Правильно реализованное автоматизированное тестирование охватывает все, работает всегда и занимает считанные секунды. Оно также делает код проще для понимания, что позволяет одновременно нескольким командам работать над кодом, не беспокоясь о том, что они могут сломать чью-то функцию.
Модульный тест по отдельности проверяет функциональность компонентов. Это тестирование самого низкого уровня, оно проверяет, что каждый компонент программы правильно работает в одиночку (интеграционные и системные тесты проверяют все компоненты вместе, а также их взаимодействия, но эта тема выходит за рамки данного руководства). При объектно-ориентированном подходе, например, вам пришлось бы писать модульные тесты для каждого объекта, а также для отдельных методов, в зависимости от их сложности. В Django мы используем модульное тестирование для каждой модели и представления.
Как в Django работает модульное тестирование?
Для работы с этим руководством, клонируйте вот этот проект из GitHub. Чтобы создать проект, выполните действия из первых трех параграфов в Как создать API с помощью Python и Django.
Когда вы используете python manage.py startapp appname для создания приложения на Django, один из создаваемых Django файлов папке appname имеет имя tests.py . Этот файл существует для размещения модульных тестов для моделей и других компонентов внутри приложения. По умолчанию этот файл содержит одну строчку кода: from django.test import TestCase . Test case содержит несколько связанных тестов для одного и того же фрагмента кода. TestCase — это объект Django, который мы будем наследовать для создания собственных модульных тестов. У класса есть два метода: setUp(self) и tearDown(self) , которые запускаются до и после отдельных тестовых функций для того, чтобы предоставить и очистить тестовую базу данных. Эта база независима от той базы данных, к которой вы получаете доступ с помощью python manage.py runserver . Чтобы взглянуть на код, откройте tests.py в папке todo нашего проекта.
class SigninTest(TestCase): def setUp(self): self.user = get_user_model().objects.create_user(username='test', password='12test12', email='test@example.com') self.user.save() def tearDown(self): self.user.delete() def test_correct(self): user = authenticate(username='test', password='12test12') self.assertTrue((user is not None) and user.is_authenticated) def test_wrong_username(self): user = authenticate(username='wrong', password='12test12') self.assertFalse(user is not None and user.is_authenticated) def test_wrong_pssword(self): user = authenticate(username='test', password='wrong') self.assertFalse(user is not None and user.is_authenticated)
Здесь мы тестируем функцию входа в систему. Поскольку мы используем встроенные в Django методы, все должно работать нормально, если в базе данных нет никаких проблем. Нам нужны только простые тесты: аутентифицируйтесь, если предоставлены верные данные, и не делайте этого, если нет. Благодаря этому примеру мы можем увидеть кое-что еще в модульном тестировании в Django. Прежде всего, все тестовые методы в тестовом случае должны начинаться с test_ , чтобы быть выполненными при запуске тестовой команды python manage.py test . Остальные методы в тестовом случае нужно воспринимать как вспомогательные функции. Также необходимо знать, что все тестовые методы должны принимать self в качестве аргумента, где self является ссылкой на объект TestCase . Класс TestCase , который мы наследуем для создания нашего класса, содержит методы утверждений для проверки логических значений. Вызов self.assertSomething() проходит, если переданные в качестве аргументов значения соответствуют утверждению, в противном случае этого не происходит. Тестовый метод проходит, только если каждое утверждение в методе проходит.
class TaskTest(TestCase): def setUp(self): self.user = get_user_model().objects.create_user(username='test', password='12test12', email='test@example.com') self.user.save() self.timestamp = date.today() self.task = Task(user=self.user, description='description', due=self.timestamp + timedelta(days=1)) self.task.save() def tearDown(self): self.user.delete() def test_read_task(self): self.assertEqual(self.task.user, self.user) self.assertEqual(self.task.description, 'description') self.assertEqual(self.task.due, self.timestamp + timedelta(days=1)) def test_update_task_description(self): self.task.description = 'new description' self.task.save() self.assertEqual(self.task.description, 'new description') def test_update_task_due(self): self.task.due = self.timestamp + timedelta(days=2) self.task.save() self.assertEqual(self.task.due, self.timestamp + timedelta(days=2))
Теперь давайте протестируем нашу модель: объект Task , определенный в models.py . Для тестового случая мы создаем пользователя и задачу (обратите внимание, что из-за того, что пользователь и задача связаны отношениями внешнего ключа, удаление пользователя в tearDown() приведет к удалению задачи). Здесь мы можем увидеть, что любой тестовый метод может иметь несколько утверждений и проходит только в том случае, если все они выполняются успешно. Когда мы обновляем задачу, мы можем записывать данные в базу вне функции setUp. В остальном, этот тест похож на тест функции входа. Большинство тестовых случаев для моделей представляют собой создание, чтение, модифицирование и удаление объектов в базе данных, хотя модели с методами и интереснее тестировать.
class SignInViewTest(TestCase): def setUp(self): self.user = get_user_model().objects.create_user(username='test', password='12test12', email='test@example.com') def tearDown(self): self.user.delete() def test_correct(self): response = self.client.post('/signin/', 'username': 'test', 'password': '12test12'>) self.assertTrue(response.data['authenticated']) def test_wrong_username(self): response = self.client.post('/signin/', 'username': 'wrong', 'password': '12test12'>) self.assertFalse(response.data['authenticated']) def test_wrong_pssword(self): response = self.client.post('/signin/', 'username': 'test', 'password': 'wrong'>) self.assertFalse(response.data['authenticated'])
Тестировать представления несколько сложнее, чем модели. Однако поскольку мы пишем API, в отличие от веб-приложения, здесь можно не волноваться по поводу тестирования фронтенда. Большую часть ручных тестов посредством Postman можно заменить на тесты представлений. self.client — HTTP-клиент тестовой библиотеки Django. Мы используем его для создания post-запроса к «/signin/» с учетными данными пользователя. Мы тестируем то же, что и раньше: верные учетные данные, неправильное имя пользователя и неправильный пароль. Это очень полезно, так как мы видим, что если тесты модели не выявляют ошибок, а тесты представлений выявляют — проблема не в модели, что в свою очередь позволяет тратить меньше времени на устранение багов. Мы делаем примерно то же самое для представлений, связанных с задачами.
class AllTasksViewTest(TestCase): def setUp(self): self.user = get_user_model().objects.create_user(username='test', password='12test12', email='test@example.com') self.user.save() self.timestamp = date.today() self.client.login(username='test', password='12test12') def tearDown(self): self.user.delete() def test_no_tasks(self): response = self.client.get('/all/') self.assertEqual(response.data, 'tasks': []>) def test_one_task(self): self.task1 = Task(user=self.user, description='description 1', due=self.timestamp + timedelta(days=1)) self.task1.save() response = self.client.get('/all/') self.assertEqual(response.data, 'tasks': [OrderedDict([('id', 1), ('description', 'description 1'), ('due', str(self.timestamp + timedelta(days=1)))])]>)
Этот случай тестирует конечную точку «/all/». На самом деле у этого теста больше методов, но фрагмент выше показывает только новое. Чтобы клиент мог действовать как вошедший в систему пользователь, в setUp мы используем self.client.login() . Затем мы создаем задачи и сравниваем их с ожидаемым отформатированным выводом. Этот пример хорошо иллюстрирует преимущества методов setUp() и tearDown() , так как задачи из одного теста не переносятся в другие. Опять же, этот тест изолирует компонент представления, поскольку базовая модель тестируется отдельно.
Когда разберетесь с тестовым кодом, запустите python manage.py test , чтобы выполнить все тесты. Давайте взглянем на результат:
Creating test database for alias 'default'. System check identified no issues (0 silenced). . FF. ====================================================================== FAIL: test_due_future (todo.tests.DueTodayTest) ---------------------------------------------------------------------- Traceback (most recent call last): File "/Users/Philip/Code/WFH/mkdev_blog/djangotesting/taskmanager/todo/tests.py", line 155, in test_due_future self.assertFalse(self.task.due_today()) AssertionError: True is not false ====================================================================== FAIL: test_due_past (todo.tests.DueTodayTest) ---------------------------------------------------------------------- Traceback (most recent call last): File "/Users/Philip/Code/WFH/mkdev_blog/djangotesting/taskmanager/todo/tests.py", line 161, in test_due_past self.assertFalse(self.task.due_today()) AssertionError: True is not false ---------------------------------------------------------------------- Ran 15 tests in 2.232s FAILED (failures=2) Destroying test database for alias 'default'.
Все тесты, не выявившие ошибок, помечаются . , а тесты, показавшие ошибки — F . Такие тесты также показывают, почему именно утверждения не прошли. Мы еще не говорили с вами о тех тестах, которые выявили ошибки, но мы исправимся чуть ниже. Вы могли заметить, что код теста очень подробен. Конечно же, мы протестировали лишь небольшую часть нашего функционала, но, несмотря на это, уже получили столько же строк кода, сколько и в файле представления. Этого и следовало ожидать. Если вы хотите, чтобы ваши тесты были точными, вы не можете проводить их слишком часто. При изменениях в коде некоторые тесты перестанут работать. Таким образом, вы сможете понять, какие ошибки и в каких тестах нужно будет устранить. Так что будьте готовы к тому, что код тестов будет все расти и расти, а в среднестатистическом приложении будет столько же строк тестового кода, сколько и у кода самого приложения.
Что такое разработка через тестирование?
Давайте на секундочку вернемся к рассказу из начала статьи. Наша команда неделя за неделей сражалась с багами и в результате написала модульные тесты для всей базы данных. Мы покрыли тестами абсолютно все, каждая строчка кода проверялась по меньшей мере одним тестом. Мы поработали так пару недель, пока не решили существенно изменить структуру нашей базы данных. Вместо того чтобы переписывать тесты, мы стали отбрасывать неработающие и буквально через несколько дней у нас стали снова вылезать случайные поломки. Разработка через тестирование помогла бы это предотвратить.
Чтобы тесты оставались актуальными, их нужно обновлять по мере обновления кода. Некоторые разработчики пользуются разработкой на основе тестов, чтобы всегда быть готовыми к любым изменениям в коде. Первое, что вы делаете при разработке функции — определяете что, собственно, эта функция будет делать. Разработка через тестирование формализует этот процесс, поскольку при таком подходе вы, прежде всего, прописываете тесты для этой самой функциональности. Основная идея заключается в том, что вы пишите один или несколько тестов, которые определяют функцию, переписываете код до тех пор, пока тесты не выявят ошибок, а затем снова пишите еще больше тестов. Вернемся к тестам, выявившим ошибки. Нам нужно написать метод due_today() в модели Task . Согласно тесту, этот метод должен возвращать True , если задача должна быть выполнена сегодня и False , если нет. Скопируйте код ниже для замены существующего метода due_today() в модели Task , а затем запустите тесты снова. python def due_today(self): return self.due == date.today()
Тест не показывает ошибок, что значит, что наша функция работает и можно продолжать. Подобный подход к разработке требует больших физических и умственных усилий поначалу для определения поведения кода, но в результате значительно упрощает сам процесс разработки.
Чтобы протестировать разобрались ли вы, попробуйте написать тесты для остальных представлений или используйте тесты для задания новых функций, а затем напишите эти функции. Одним из простых вариантов будет поле с логическим значением completed в модели task , которому может быть присвоено значение True как только задача будет выполнена. Это позволит нам не удалять выполненные задачи, а оставить их. Затем, подумайте о том, чтобы добавить тесты в ваши личные проекты. Да, вас может напугать перспектива покрытия тестами огромного проекта, который ранее не тестировался. Вместо того, чтобы пытаться протестировать все и сразу, попробуйте добавить тесты в маленькие фрагменты проекта или новые функции непосредственно во время разработки до полного покрытия.
Материалы для ознакомления:
- Официальное руководство Django, часть 5
- Обзор документации Django по тестированию
- Раздел по тестированию для продвинутых пользователей. Включает в себя покрытие тестами
- Документация Django по тестированию кода Django
© Copyright 2014 — 2023 mkdev | Privacy Policy
Учимся тестировать Django
Я программировал некоторое время и, знаете, совсем недавно начал внедрять в своем процессе разработки тестирование. Стоит сказать, что это руководство предназначено для тех, кто начинает с нуля. Если вы и без дополнительных объяснений понимаете документацию, я бы это пропустил. Но всем остальным, прежде чем начать, советую прочитать этот гайд.
1. Обзор тестирования
Автоматическое тестирование полезно для обеспечения качества кода. Если вы немного программировали, то наверняка знаете, что неизбежно будут появляться такие моменты, что вы выпустили новую функцию, которая сломала существующую функцию. Тесты, которые вы пишете, будут выполняться до того, как вы отправите какой-либо код, и со временем вы накопите много тестов. Поэтому, когда вы работаете над новой функцией через несколько месяцев, вы будете запускать уже автоматизированные тесты, гарантируя, что код, который вы пишете сегодня, будет работать так, как ожидалось. Этот процесс требует поначалу затрат времени и сил, но в конечном итоге он окупается. Благодаря автоматизированному тестированию вы сэкономите много времени на ручном тестировании и будете гораздо увереннее в коде, который релизите.
2. Тестируем код на хрупкость
tl; dr (краткий алгоритм) обнаружить ошибку, написать тест, который падает из-за ошибки, исправить ошибку, больше не волноваться.
Если вы не знаете, с чего начать, или если вы ищете способ облегчить тестирование, вам отлично подойдет тестирование антихрупкости. Антихрупкость основана на идее, что некоторые вещи становятся сильнее при воздействии критических состояний. По мере роста вашего приложения здесь и там будут появляться ошибки, и вам будет поручено их исправить. Однако, прежде чем исправить ошибку, вы можете написать тест, который не пройден из-за ошибки. Например, если у вас есть форма, которая создает пользователя и не проверяет правильность имени пользователя, вы можете написать тест, который специально проверяет проверку для этого поля. Вы можете вызвать тест username_validation_should_fail_when_length_is_less_than_8. Забавное имя для теста(и, возможно, вы могли бы его сократить), но важно писать очень конкретные тесты и давать осязаемые названия. Затем вы можете проверить валидацию, передав значения и в итоге должны потерпеть неудачу. Перед исправлением ошибки вы можете запустить тест и снова вызвать его неудачу. Затем, после исправления ошибки, вы можете быть уверены, что она снова не выйдет из строя.
Таким образом, ошибки все еще являются неприятностью, но по крайней мере они сделают ваше приложение более сильным с течением времени. В конце концов, вам никогда не придется исправлять одну и ту же ошибку дважды.
3. Тестирование в Джанго
Более поздние версии Django поставляются с тестовым окружением. На самом деле это просто оболочка для встроенного в Python фреймворка для тестирования модулей. Вы захотите поместить свои тесты в папку приложения, к которой они принадлежат, и вызовете файл tests.py. Начиная с Django 1.6, Django будет искать тесты в любом файле, который начинается с ключевого слова test. Таким образом, вы можете лучше организовать свои тесты, а не писать несколько тысяч строк тестов в одном файле.
4. Пишем первый тест
Этот шаг предполагает, что вы уже настроили приложение Django. Откройте файл с именем tests.py в приложении по нашему выбору (в Django основное приложение называется проектом, и оно часто состоит из многих приложений).
В верхней части файла вы импортируете среду тестирования Django:
from django import test И любые тесты, которые вы пишете, расширят класс test.TestCase. Итак, в первом тесте мы предположим, что нам нужно проверить, возвращает ли домашняя страница статус ответа 200. Если вы не настроили файл urls или ваши представления, это нормально. Мы можем начать с написания теста. Когда вы пишете тесты, хорошо бы заставить тест упасть, чтобы вы знали, что он работает.
class URLTests(test.TestCase): def test_homepage(self): response = self.client.get('/') self.assertEqual(response.status_code, 200)
Django запустит набор тестов URLTests и выполнит любые методы, начинающиеся с test. Так что в этом случае test_homepage будет запущен автоматически. Таким образом, первая строка теста просто запускает HTTP-запрос GET для извлечения домашней страницы. Ответ хранится в переменной ответа, и мы используем метод TestCase assertEqual, чтобы убедиться, что код состояния равен 200.
TestCase.assertEqual выдаст исключение, если утверждение не выполнено, и остановит тест, чтобы вы могли исправить проблему. На этом этапе, так как вы еще не создали домашнюю страницу, вы можете настроить URL-адрес, просмотреть и снова запустить тест.
5. Запуск тестов
Теперь, когда вы написали свой тест, пришло время его запустить. По мере развития тесты могут и должны проводиться часто. По крайней мере, они должны быть запущены до развертывания. С Django вы можете запустить все тесты в своем проекте или настроить тесты для одного приложения. Для запуска всех тестов вы можете просто запустить следующие команды в консоли:
./manage.py test Для запуска только тестов в определенном приложении, которое вы можете запустить:
./manage.py test app_name Как упоминалось ранее, эти команды найдут все применимые тесты и выполнят их. Тестовая база данных будет создана до и уничтожена после, и, как ожидается, вы будете уведомлены, если какие-либо тесты не пройдут.
Капнем глубже
Надеюсь, теперь у вас есть хорошее понимание того, почему, как и для чего вы будете использовать тестирование. Теперь пришло время изучать документацию.
