【django】 pytest factory boyとfakerの使い方

当ページのリンクには広告が含まれています。
  • URLをコピーしました!

サンプルコード

Django/DjangoRESTframeworkについて記事まとめ

目次

Factory Boyとは?

https://pytest-factoryboy.readthedocs.io/en/latest/

テストコードで扱うテストデータを簡単に用意することができます。

例えば、テストデータを導入する場合、fixturejsonymlファイルを用意して読み込む必要がありますが、プライマリキーの扱いやリレーション関係などを意識する必要があるためかなり面倒になります。

しかし、factory boyを使用することにより、簡単にテストデータを用意するための記述ができるようになります。

Factory Boyのインストール

$ pip install pytest-factoryboy

テストデータ生成用のFakerもあわせてインストールされます。

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のモデルを扱えるようになります。今回はdjangoUserモデルを使用します。

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を使ってテスト内でデータの追加も可能です。

Using factory_boy with ORMs

ユーザーを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

build()create()の違いは、保存先がメモリかデータベースのどちらに保存するかの違いです。値を利用する場合にはcreateにする必要があります。

テストを実行すると・・・

=================================== 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 ============================== 

ちゃんと処理できていますね!

参考

サンプルコード

Django/DjangoRESTframeworkについて記事まとめ

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

目次