Mixer -- удобная генерация данных для тестирования
Содержание:
Mixer — это удобная библиотека для генерации данных для тестов. Из коробки обладает поддержкой Django ORM, SQLAlchemy ORM, Mongoengine ODM и простым синтаксисом для интеграции любых бэкэндов. Работает в версиях python 2-х и 3-х версий.
Обычно при разработке тестов, программисты нуждаются в генерации данных для них. Тут можно выделить два пути, первый — подготовить данные заранее (используя так называемые fixtures) и второй — генерировать данные для тестов непосредственно в момент выполнения.
Первый обладает рядом минусов. Во-первых данные необходимо готовить заранее. Во-вторых при изменении структуры данных необходимо тратить ресурсы на поддержку актуальности fixtures. В третьих вы не можете получить больше данных чем приготовили.
Mixer предлагает второй способ. Вы получаете данные непосредственно в тестах, в любом необходимом количестве и не зависите от изменений в их структуре.
Пример работы
Несколько примеров для Django как для наиболее распространненного сейчас python-фреймворка.
Создадим стандартного пользователя используя django.contrib.auth.models.User.
from mixer.backend.django import mixer
from django.contrib.auth.models import User
user = mixer.blend(User)
Мы только что создали и сохранили в тестовой базе пользователя, со всеми необходимыми полями. Разумеется Mixer правильно обработает отношения и при необходимости автоматически создаст и объекты зависимых моделей. Вам нет необходимости настраивать параметры создавамого объекта, вы просто указываете модель и остальное Mixer делает самостоятельно.
По-умолчанию Mixer старается генерировать данные не только учитывая тип полей, но и анализируя их названия. Например при проверке примеров библиотека сгенерировала пользователя с логином mixcookie1957, адресом электронной почты: limb_candy@wordpress.br и именем: Robinson Wolverhampton. Но такое поведение разумеется можно отключить и получать абсолютно случайные данные.
Если вам нужно сгенерировать несколько объектов, имеется короткий синтаксис. Создадим еще 5 пользователей.
users = mixer.cycle(5).blend(User)
Зачастую возникает неоходимость предопределить некоторые значения генерируемого объекта. Синтаксис Mixer прост и интуитивно понятен. Создадим пользователя с логином testuser и группой admin.
from django.contrib.auth.models import User, Group
group = mixer.blend(Group, name='admin')
user = mixer.blend(User, username='testuser', groups=[group])
Но Mixer позволяет сделать это еще быстрее. Вышеприведенный код генерации можно заменить одной строчкой:
user = mixer.blend(User, username='testuser', groups__name='admin')
Синтаксис знаком пользователям Django, "__" позволяет указывать в фильтрации запросов, поля зависимых объектов. Так и в Mixer разделитель "__" позволяет предзадавать поля зависимых объектов не заботясь об их генерации. Использование может быть очень гибким.
messages = mixer.cycle(10).blend(
Message,
author__name='Duck Nukem',
author__age='100',
author__clan__title='crazy beaches',
)
По-умолчанию Mixer пропускает поля имеющие значение по умолчанию или позволяющие None значение. Вы всегда можете предзадать их вручную как в вышеприведенном примере или позволить Mixer сделать это за вас используя волшебный атрибут mixer.random.
class Product(models.Model):
title = models.CharField(max_length=100)
price = models.DecimalField(default=0)
type = models.IntegerField(
choices=((1, 'food'), (2, 'drinks')),
null=True
)
# Здесь price=0 и type=None
mixer.blend(Product)
# А здесь price это какой то decimal, а type равен `food` или `drinks`
mixer.blend(Product, price=mixer.random, type=mixer.random)
В качестве предзаданных данных Mixer поддерживает также функции и генераторы.
# Пример с функцией
def get_super():
return "super"
mixer.blend(Superman, what=get_super)
# Пример с генератором
gen = (v for v in [10, 20, 30, 40, 50])
products = mixer.cycle(5).blend(Product, price=gen)
# Еще один
authors = mixer.cycle(10).blend(Author)
books = mixer.cycle(5).blend(Book, author=(a for a in authors))
Синтаксис для работы Flask, SQLAlchemy, Mongoengine схож и в данной статье рассматриваться не будет. Импортируете необходимый бэкэнд, указываете модель и получаете данные.
Продвинутое использование
Указание моделей через путь
Вам необязательно импортировать модели для генерации тестовых данных. Mixer способен сделать это за вас. Для Django достаточно указать строку вида: <имя_приложения>.<имя_модели>. Для других бэкэндов придется указать python путь для импорта. Пример из начала статьи можно изменить так:
user = mixer.blend('auth.user')
Другие волшебные атрибуты:
mixer.fake
Поведение mixer.fake похоже на mixer.random, но в отличии от последнего, генерирующего случайные данные, данный атрибут генерирует данные «фейковые данные» анализируя имя поля. Например для строкового поля login это будет строка имитирующая логин пользователя, а не просто случайный набор символов.
test = mixer.blend('app.message', content=mixer.fake)
Note
И mixer.random и mixer.fake позволяют принудительно указать тип генерируемого значения: mixer.random(int), mixer.fake(models.CharField)
mixer.select
mixer.select похож на mixer.fake и mixer.random, но работает с уже существующими данными. В приведенном ниже примере пользователю присваивается одна из существующих в базе данных групп.
user = mixer.blend('auth.user', groups=mixer.select)
mixer.select может принимать параметры фильтрации, например для Django:
user = mixer.blend('auth.user', groups=mixer.select(active=True))
mixer.mix
mixer.mix это волшебный атрибут указывающий на будущее сгенерированное значение. Например создадим пользователя с одинаковыми username и last_name.
user = mixer.blend('auth.user', username=mixer.mix.first_name)
Как мы помним mixer.mix указывает на объект который будет сгенерирован. Поэтому mixer.mix.first_name будет указывать на поле этого объекта и цель достигнута.
Использование mixer.mix может быть и более интересным.
ship = mixer.blend(
Ship,
captain__country = mixer.mix.region.country,
title=mixer.mix.captain.nick
)
mixer.mix может принимать функцию от одного аргумента, которая получит при вызове сгенерированный объект.
def get_username(user):
return user.first_name.lower() + user.last_name.lower()
user = mixer.blend(
username = mixer.mix(get_username)
)
mixer.sequence
Mixer, как уже указывалось, умеет принимать в качестве значений полей генераторы. mixer.sequence это помощник для создания генераторов из функций. Он принимает функцию от одного аргумента и на ее основе создает генератор.
Например несколько пользователей с логинами вида: 'test0', 'test1' и тд.
mixer.cycle(10).blend('auth.user', username=mixer.sequence(
lambda c: 'test' + c
))
В функцию будет приходить счетчик итераций. Вышеприведенная операция встречается довольно часто, поэтому mixer.sequence поддерживает короткий синтаксис. При передачи строки он трансформирует ее в функцию: lambda c: value.format(c).
Результат кода аналогичен предыдущему.
mixer.cycle(10).blend('auth.user', username=mixer.sequence('test{0}'))
Преимущества по сравнению с другими библиотеками
Mixer поддерживает python второй и третьей версии.
Библиотека обладает интеграцией с Django ORM, SQLAlchemy ORM, Mongoengine ODM и легко расширяется для других бэкэндов. Независимо от проекта вы получаете единый интерфейс для генерации данных.
Mixer из коробки умеет генерировать не только случайные, но и фейковые данные, с которыми приятно работать.
Mixer интуитивно понятен, гибок в настройке и прост.
Надеюсь с данным модулем генерация данных станет для вас легче и приятнее.