В этой статье предполагается, что вы знакомы со следующим:
- проектирование моделей Django
- выполнение операций CRUD на модели Django
- запуск миграции Django с
makemigrations
иmigrate
- создание модульных тестов с помощью Python-фреймворка unittest
Цель этой статьи — поощрить автоматическое модульное тестирование моделей Django перед фиксацией схемы моделей в базе данных.
В конце этой статьи вы должны быть в состоянии:
- создайте тестовый скрипт в экосистеме Django, чтобы протестировать базовую неразборчивость (cчтение, rчтение, updating и d >удаление) операций над моделями Django
- выполнять тесты на основе различной степени детализации
- выполнить тестовый скрипт с различными уровнями детализации
Модели и база данных Django
Модели Django — это представления таблиц базы данных, которые принадлежат схеме базы данных для приложения. Схема базы данных обычно состоит из таблиц, полей таблиц и их ограничений, индексов и отношений таблиц. Поскольку модель Django является абстракцией таблицы реляционной базы данных, она реализована как класс Python с данными и поведением.
Джанго Миграция
Процесс миграции Django состоит из двух этапов:
makemigrations
, которая создает файлы миграции для описания шагов по преобразованию класса модели Python в таблицу базы данных. Эти файлы миграции находятся в подкаталогеmigrations
внутри каталога приложения.migrate
для реализации шагов в файлах миграции и создания соответствующих таблиц и их отношений и ограничений, если таковые имеются, в базе данных.
Перед выполнением шага 2 процесса миграции, который фиксирует наши модели в базе данных, мы должны пойти в обход и написать тесты для наших моделей Django. Тестирование модели должно охватывать:
- валидация данных
- модель поведения
- модельные отношения
- основные грубые операции
Пользователи Django могут воспользоваться преимуществами тестирования моделей с помощью различных инструментов, встроенных в Django.
Джанго БД
Поскольку наши тесты требуют, чтобы файлы миграции уже существовали для наших моделей, нам нужно выполнить шаг 1 — makemigrations
процесса миграции. Мы можем проверить базу данных, чтобы убедиться, что для наших моделей не были созданы таблицы. Предполагая, что мы используем базу данных SQLite
по умолчанию, мы можем вызвать команду dbshell
в терминале:
$ python manage.py dbshell SQLite version 3.36.0 2021-06-18 18:36:39 Enter ".help" for usage hints. sqlite> .tables sqlite>
Выполнение команды .tables
внутри dbshell
возвращает пустую строку, потому что мы еще не создали никаких таблиц в базе данных с помощью команды migrate
.
Образец заявления
Для целей этой статьи мы собираемся определить простое приложение pet
, включающее две модели, Owner
и Pet
, где владелец может владеть одним или несколькими домашними животными. Для простоты наши модели определяются следующим образом:
# models.py from django.db import models # An Owner of one or more pets class Owner(models.Model): name = models.CharField(max_length=20) def __str__(self): return self.name # A Pet must have an owner class Pet(models.Model): name = models.CharField(max_length=20) owner = models.ForeignKey( to=Owner, on_delete=models.CASCADE # when an owner is deleted, so is their pet ) def __str__(self): return self.name
Обратите внимание, что параметр поля on_delete=models.CASCADE
гарантирует, что объект pet
будет удален при удалении объекта Owner
.
Джанго тест
Сценарий тестирования по умолчанию
По умолчанию установка Django предоставляет пустой тестовый файл tests.py для каждого приложения.
$ ls pet __init__.py admin.py migrations/ tests.py __pycache__/ apps.py models.py views.py
Прецедент
Пустой файл tests.py может выглядеть так:
from django.test import TestCase
# Create your tests here.
Обратите внимание, что класс TestCase
импортируется из модуля test
Django. Мы будем писать наши тесты объектно-ориентированным способом, создав подкласс TestCase
для предоставления пользовательских данных и методов. django.test.TestCase
сам по себе является подклассом unittest.TestCase
Python, который позволяет тесту выполняться изолированно внутри транзакции.
Образец данных
В нашем тесте мы собираемся определить DemoTests
, подкласс TestCase
. Мы предоставим образцы данных внутри DemoTests
, определив items
, список словарей Python, сопоставляющий владельца с его питомцами как свойство класса. Например:
class DemoTests(TestCase): items = [ {"owner": 'Katie', "pet": ['Toto', 'Kitty']}, {"owner": 'Sue', "pet": ['Bunny', 'Scott']}, {"owner": 'Lynn', "pet": ['Skylar']}, ]
Вспомогательные свойства
Кроме того, мы собираемся определить два дополнительных свойства вспомогательного класса:
owner_names
, список имен владельцев вitems
pet_names
, список имен питомцев вitems
Например:
owner_names = [item["owner"] for item in items] # ['Katie', 'Sue', 'Lynn'] pet_names = [name for item in items for name in item["pet"]] # ['Toto', 'Kitty', 'Bunny', 'Scott', 'Skylar']
Вспомогательные методы
Далее мы собираемся определить метод вспомогательного класса pets_by_name
, который возвращает список имен домашних животных на основе имени владельца с использованием данных items
. Например:
# Given an owner name, return their a list of pets or an empty list def pets_by_owner(self, owner): for item in self.items: if item["owner"] == owner: return item["pet"] return []
Метод setUp()
Поскольку наши данные items
представляют собой структуру данных Python, нам нужно преобразовать содержимое items
в модели Django. TestCase
предоставляет метод класса setUp()
, который вызывается один раз для каждой транзакции. Мы можем определить метод setUp()
для создания наших моделей Django на основе items
. Например:
# Create data in the database once def setUp(self): for item in self.items: owner = Owner.objects.create(name=item["owner"]) for pet in item["pet"]: Pet.objects.create(name=pet, owner=owner) self.assertEqual( Owner.objects.all().count(), len(self.owner_names)) self.assertEqual( Pet.objects.all().count(), len(self.pet_names))
Для каждого элемента в items
мы используем Django QuerySet API, чтобы создать модель для Owner
и Pet
через objects.create()
. Обратите внимание на последние два метода assertEqual()
. Во-первых, нужно убедиться, что количество созданных объектов Owner
эквивалентно количеству имен владельцев в items
. Во-вторых, необходимо убедиться, что количество созданных объектов Pet
эквивалентно количеству имен питомцев в items
. Метод setUp()
может быть проверкой правильности создания наших моделей.
Определить тесты
Мы успешно создали объекты модели в методе класса setUp()
. Далее мы можем определить дополнительные тесты для созданных моделей как методы отдельных классов. К ним могут относиться:
- запрос несуществующего владельца
- запрос несуществующего питомца
- удаление существующего питомца
- удаление существующего владельца
- добавление нового питомца к существующему владельцу
- обновление имени существующего питомца
Каждый тест должен быть небольшим и конкретным, нацеленным на конкретную единицу действия. Правило именования методов тестового класса в unittest
состоит в том, чтобы добавлять к каждому тесту префикс test
. Например, тест для запроса несуществующего владельца может называться test_owner_not_found()
.
# Query for a non-existing owner def test_owner_not_found(self): print(f'\nRunning {self.id()}\n') names = list(self.owner_names) names.append('Lisa') for name in names: try: owner = get_object_or_404(Owner, name=name) self.assertEqual(owner.name, name) except Http404: print(f"Owner {name} not found")
Обратите внимание, что unittest
предоставляет метод класса, чтобы определить имя метода тестового класса. Мы можем использовать этот метод для отображения имени теста.
unittest
запускает каждый тест на основе алфавитного порядка имен тестов.
Примечание. Порядок, в котором будут выполняться различные тесты, определяется путем сортировки имен тестовых методов в соответствии со встроенным порядком строк.
Если вы хотите, чтобы ваши тесты выполнялись в определенном порядке, вы должны творчески подойти к именованию тестов. Например, test_01xxxx()
будет выполняться до test_02xxxx()
.
Чтобы увидеть образцы тестов для этой статьи, взгляните на мой репозиторий на GitHub.
Запустить тесты
Платформа тестирования Django unittest
обеспечивает гибкое выполнение тестов.
Запустить все тесты
Чтобы выполнить все тесты, определенные в tests.py, в нашем приложении pet
, просто введите в консоли:
$ ./manage.py test
or
$ ./manage.py test pet.tests
Запустить тестовый скрипт
Если у нас есть несколько тестовых файлов test1.py и test2.py внутри нашего приложения pet
, мы можем выбрать запуск только тестов, относящихся к конкретному тестовому файлу. Например:
$ ./manage.py test pet.test2
Будет выполнять тесты только внутри test2.py.
Запустите тестовый пример
Если в нашем файле tests.py есть несколько TestCase
, мы можем выбрать выполнение конкретного TestCase
с именем DemoTests
. Например:
$ ./manage.py test pet.tests.DemoTests
Запустите тестовый метод
Для более точной детализации мы также можем выбрать запуск только определенного метода тестирования, например test_owner_not_found()
, внутри нашего тестового скрипта tests.py. Например:
$ ./manage.py test pet.tests.DemoTests.test_owner_not_found
Тестовый вывод
Детализация по умолчанию
Пример вывода с детализацией по умолчанию успешного теста может быть следующим:
Creating test database for alias 'default'... System check identified no issues (0 silenced). . . . ---------------------------------------------------------------------- Ran 3 tests in 0.022s OK Destroying test database for alias 'default'...
Тестовый уровень детализации
Если вам нужен более подробный вывод при выполнении тестов, вы можете указать аргумент -v
, за которым следует число, 0
, 1
(по умолчанию), 2
или 3
для команды test
. Чем выше многословие, тем выше число. Например:
$ ./manage.py test -v 2
будет генерировать дополнительный вывод, например:
Creating test database for alias 'default' ('file:memorydb_default?mode=memory&c ache=shared')... Operations to perform: Synchronize unmigrated apps: messages, staticfiles Apply all migrations: admin, auth, contenttypes, pet, sessions Synchronizing apps without migrations: Creating tables... Running deferred SQL... Running migrations: Applying contenttypes.0001_initial... OK Applying auth.0001_initial... OK Applying admin.0001_initial... OK Applying admin.0002_logentry_remove_auto_add... OK Applying admin.0003_logentry_add_action_flag_choices... OK Applying contenttypes.0002_remove_content_type_name... OK Applying auth.0002_alter_permission_name_max_length... OK Applying auth.0003_alter_user_email_max_length... OK Applying auth.0004_alter_user_username_opts... OK Applying auth.0005_alter_user_last_login_null... OK Applying auth.0006_require_contenttypes_0002... OK Applying auth.0007_alter_validators_add_error_messages... OK Applying auth.0008_alter_user_username_max_length... OK Applying auth.0009_alter_user_last_name_max_length... OK Applying auth.0010_alter_group_name_max_length... OK Applying auth.0011_update_proxy_permissions... OK Applying auth.0012_alter_user_first_name_max_length... OK Applying pet.0001_initial... OK Applying sessions.0001_initial... OK System check identified no issues (0 silenced). test_add_pet (pet.test1.DemoTests) ... Running pet.test1.DemoTests.test_add_pet ok .... ---------------------------------------------------------------------- Ran 7 tests in 0.022s OK Destroying test database for alias 'default' ('file:memorydb_default?mode=memory &cache=shared')...
Обратите внимание, что Django migrations
выполняется в тестовой базе данных default
на основе файлов миграции, включая pet.0001_initial.py
, созданных makemigrations
. По умолчанию тестирование Django гарантирует, что тестовая база данных будет уничтожена после завершения тестирования. Для еще более подробного вывода вы можете попробовать опцию 3
.
Заключение
Добавление модульного тестирования на различных этапах разработки Django, как правило, является хорошей практикой для разработчика. По мере роста веб-приложения его сложность возрастает. Помимо моделей Django, среда тестирования Django также предоставляет инструменты для тестирования представлений. Обеспечение того, чтобы каждая часть приложения Django была хорошо протестирована перед развертыванием, имеет решающее значение и является неотъемлемой частью успешного проекта веб-разработки.
Первоначально опубликовано на https://github.com.
Дополнительные материалы на plainenglish.io