【django】 pytest factory boyとfakerの使い方
Django/DjangoRESTframeworkについて記事まとめ
Factory Boyとは?
https://pytest-factoryboy.readthedocs.io/en/latest/
テストコードで扱うテストデータを簡単に用意することができます。
例えば、テストデータを導入する場合、fixture
のjson
やyml
ファイルを用意して読み込む必要がありますが、プライマリキーの扱いやリレーション関係などを意識する必要があるためかなり面倒になります。
しかし、factory boy
を使用することにより、簡単にテストデータを用意するための記述ができるようになります。
Factory Boyのインストール
$ pip install pytest-factoryboy
https://faker.readthedocs.io/en/master/
Factory Boyの使い方
djangoのモデルを扱う
まずはクラスを作成します。factory
をインポートし、factory.django.DjangoModelFactory
をオーバーライドします。
https://pytest-factoryboy.readthedocs.io/en/latest/index.html?highlight=django#integration
これでdjango
のモデルを扱えるようになります。今回はdjango
のUser
モデルを使用します。
import factory
from django.contrib.auth.models import User
class UserFactory(factory.django.DjangoModelFactory):
class Meta:
model = User
username = 'name'
is_staff = 'True'
上記のように、class Meta
でどのモデルを使用するのか、フィールド名で初期値を指定できます。
Faker
faker
を使用すると、簡単にテストデータが作成できます。例えば
$ faker name
鈴木 充
$ faker address
岐阜県川崎市幸区松石30丁目14番16号
$ faker profile
{'job': '行政書士', 'company': '株式会社高橋銀行', 'ssn': '452-47-4381', 'residence': '岐阜県豊島区台東36丁目18番8号', 'current_location': (Decimal('30.513085'), Decimal('-120.543348')), 'blood_group': 'O-', 'website': ['http://www.sasaki.com/', 'http://www.suzuki.jp/'], 'username': 'takuma30', 'name': '山口 直人', 'sex': 'M', 'address': '鳥取県千代田区外国府間27丁目12番15号 パーク平須賀881', 'mail': 'ryosukematsuda@hotmail.com', 'birthdate': datetime.date(2016, 4, 5)}
こんな感じでデータが作成できます。これを利用してusername
に利用しました。
import factory
from faker import Faker
from django.contrib.auth.models import User
fake = Faker('ja_jp')
class UserFactory(factory.django.DjangoModelFactory):
class Meta:
model = User
username = fake.name()
is_staff = 'True'
Factoryの登録(register)
先程作成したUserFactory
をテストコードに使用する方法。conftest.py
を作成し、pytest_factoryboy
からregister
をインポートしてregister(UserFactory)
と登録します。
https://pytest-factoryboy.readthedocs.io/en/latest/index.html?highlight=register#model-fixture
import pytest
from pytest_factoryboy import register
from tests.factories import UserFactory
register(UserFactory)
簡単にテストを実行して確認してみます。
def test_new_user(user_factory):
print(user_factory.username)
assert True
pytest -rP
を複数回実行すると毎回違うusernaem
になっていることが確認できました。
________________________________ test_new_user ________________________________
---------------------------- Captured stdout call -----------------------------
伊藤 涼平
============================== 1 passed in 0.11s ==============================
________________________________ test_new_user ________________________________
---------------------------- Captured stdout call -----------------------------
伊藤 健一
============================== 1 passed in 0.15s ==============================
データの追加
factory
を使ってテスト内でデータの追加も可能です。
ユーザーを2名追加して、カウント数を見てみます。
from django.contrib.auth.models import User
@pytest.mark.django_db
def test_new_useer(user_factory):
user = user_factory.create(username="test")
user2 = user_factory.create(username="test2")
print(user.username)
count = User.objects.all().count()
print(count)
assert True
実行すると
=================================== PASSES ====================================
_______________________________ test_new_useer ________________________________
---------------------------- Captured stdout call -----------------------------
test
2
============================== 1 passed in 0.26s ==============================
OKですね!
Userモデル以外で検証
User
モデル以外の別のモデルで試してみましょう。今回はよくある形でブログで最低限必要なモデルを作成してみます。
ブログモデルの初期設定
$ djago-admin startapp blog
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'blog', <-- 追加
]
モデルの作成
from django.db import models
# Create your models here.
class Category(models.Model):
name = models.CharField('title', max_length=20)
def __str__(self):
return self.name
class Tag(models.Model):
name = models.CharField('title', max_length=20)
def __str__(self):
return self.name
class Blog(models.Model):
title = models.CharField('title', max_length=50)
category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True)
tags = models.ManyToManyField(Tag)
content = models.TextField('content')
created_at = models.DateField('created_at', auto_now_add=True)
updated_at = models.DateField('updated_at', auto_now=True)
def __str__(self):
return self.title
$ python manage.py makemigrations
$ python manage.py migrate
factories.pyの編集
まずはモデルをインポートして、django
import factory
from faker import Faker
from blog import models
fake = Faker('JA_jp')
class CategoryFactory(factory.django.DjangoModelFactory):
class Meta:
model = models.Category
name = fake.job()
class TagFactory(factory.django.DjangoModelFactory):
class Meta:
model = models.Tag
name = fake.word()
class BlogFactory(factory.django.DjangoModelFactory):
class Meta:
model = models.Blog
title = "test_blog_title"
content = fake.text()
category = factory.SubFactory(CategoryFactory)
@factory.post_generation
def tags(self, create, extracted, **kwargs):
if not create:
# Simple build, do nothing.
return
if extracted:
# A list of groups were passed in, use them
for tag in extracted:
self.tags.add(tag)
リレーションのあるcategory
については、factory.SubFactory()
としています。
https://factoryboy.readthedocs.io/en/stable/reference.html?highlight=subfactory#subfactory
また、ManytoMany
フィールドの場合は少し工夫が必要で、@factory.post_generation
で関数を作成します。
https://factoryboy.readthedocs.io/en/latest/recipes.html#custom-manager-methods
conftest.pyへfactoryを登録
conttest.py
へ先程作成したfactory
を登録します。
from pytest_factoryboy import register
from tests.factories import UserFactory, CategoryFactory, TagFactory, BlogFactory
register(UserFactory)
register(CategoryFactory)
register(TagFactory)
register(BlogFactory)
テストの作成
最後に、データベースへの登録についてテストを行います。それぞれのモデルに対してテストを作成してみましょう。
def test_category(category_factory):
category = category_factory.build()
print(category)
assert True
def test_tag(db, tag_factory):
tag = tag_factory.build()
print(tag)
assert True
def test_blog(db, blog_factory, tag_factory):
tag = tag_factory.create(name="sample1")
tag2 = tag_factory.create(name="sample2")
blog = blog_factory.create(tags=(tag, tag2))
print(blog.title)
print(blog.content)
print(blog.category)
print(blog.tags.all())
assert True
テストを実行すると・・・
=================================== PASSES ====================================
__________________________________ test_tag ___________________________________
---------------------------- Captured stdout call -----------------------------
チーズ
__________________________________ test_blog __________________________________
---------------------------- Captured stdout call -----------------------------
test_blog_title
分割鉱山人形あなた自身見落とす品質供給。彼女狭い協力憲法普通のトーンブラケット。
サラダキャビンハードウェア。装置ブラケットタワー拡張厳しい催眠術。サラダボトル行
進。
ブランチヘア職人パン午前シュガー。特徴鉱山中央持ってる供給敵厳しい。デッドピック
保持するメニュー血まみれの彼。
行政書士
<QuerySet [<Tag: sample1>, <Tag: sample2>]>
________________________________ test_category ________________________________
---------------------------- Captured stdout call -----------------------------
行政書士
============================== 3 passed in 0.30s ==============================
ちゃんと処理できていますね!
参考
- Using factory_boy with ORMs
- Pythonメモ : fakerでテストデータを生成する – もた日記
- Django + factory_boyで、1対多や多対多のリレーションを持つテストデータを作る – メモ的な思考的な
Django/DjangoRESTframeworkについて記事まとめ
コメント