파이썬 테스트를 도와주는 Faker와 Factory Boy
임의의 값만 테스트하게 되는 문제
테스트 코드를 읽기 힘들게 만드는 이유 중 하나는 테스트용 임시 데이터를 생성하는 코드에서 임의의 값이 남발되기 때문입니다. 이런 식으로요.
이렇게 임의의 값을 사용하면, 해당 값에 대해서만 테스트 코드가 통과하는지도 모른다는 불안감이 생기기 마련인데요. Faker 라이브러리를 사용하여 이를 해결할 수 있습니다.
무작위 값을 생성해주는 Faker 라이브러리
Faker 라이브러리는 실행 시점에 무작위 값을 생성합니다. 다음과 같이요.
from faker import Faker
fake = Faker()
email = fake.email()
username = fake.user_name()
age = fake.pyint(min_value=0, max_value=100)
이를 활용하여 Django에서 임시 인스턴스를 만든다면 다음과 같은 식이 될 겁니다.
def test_post(self):
post = Post.objects.create(
blog=self.blog,
title=faker.sentence(),
tags=[faker.word()]
)
특정 값이 사라짐으로써 의도를 더 드러낼 수 있고, 매번 다른 값으로 테스트를 실행할 수 있어서 코드도 좀더 안정적일 수 있습니다.
반복되는 테스트 코드 문제
만약 테스트 코드에서 블로그 글을 열 개 만들어야 한다면 어떤 일이 발생할까요? 다음과 같이 굉장히 반복되는 테스트 코드가 만들어집니다.
def setUp(self):
self.blog = Blog.objects.create(title='test blog')
def test_10_posts(self):
post1 = Post.objects.create(
blog=self.blog,
title=faker.sentence()
)
post2 = Post.objects.create(
blog=self.blog,
title=faker.sentence()
)
post3 = Post.objects.create(
blog=self.blog,
title=faker.sentence()
)
...
임시 데이터 생성용 라이브러리를 사용하면 이를 간결하게 줄일 수 있는데, 개인적으로는 Factory Boy가 여러 모로 마음에 들어 사용하고 있습니다.
Factory Boy로 Django 테스트 코드 간략화하기
먼저 임시 데이터 생성용 Factory를 선언합니다.
import factory
class BlogFactory(factory.django.DjangoModelFactory):
title = factory.Faker('sentence')
class PostFactory(factory.django.DjangoModelFactory):
class Meta:
model = Post
title = factory.Faker('sentence')
이제 중복되는 테스트 코드를 Factory Boy로 다시 작성하면 다음과 같이 줄일 수 있습니다.
def setUp(self):
self.blog = BlogFactory()
def test_10_posts(self):
PostFactory.create_batch(10, blog=self.blog)
연관 모델 함께 생성하기 SubFactory
새 Blog를 열 개 만들고, 각 Blog에 Post를 하나씩 만들고 싶다면 SubFactory를 지정하면 됩니다.
class PostFactory(factory.django.DjangoModelFactory):
class Meta:
model = Post
blog = SubFactory(BlogFactory)
...
def test_10_blogs_and_posts(self):
PostFactory.create_batch(10)
이렇게 하면 Post를 생성하기 전 알아서 Blog를 생성해줍니다.
ChoiceField에 대응하는 FuzzyChoice
Post에 카테고리를 지정한다고 가정해보죠. general과 it, book 카테고리 중 하나를 선택할 수 있도록 ChoiceField로 지정해 두었을 겁니다. 이때 테스트용 Post는 세 카테고리 중 하나를 무작위로 골라서 넣어주면 좋겠죠.
class PostFactory(factory.django.DjangoModelFactory):
...
category = factory.fuzzy.FuzzyChoice(choices=['general', 'it', 'book'])
순차적인 값 Sequence
어떤 이유로 Post에 일련 번호를 지정해야 한다면 Sequence를 사용할 수 있습니다.
class PostFactory(factory.django.DjangoModelFactory):
...
slug = factory.Sequence(lambda n: f'slug-{n}')
이 외에도 특정 메서드를 실행한 값을 넣어줄 수 있는 LazyFunction이나 인스턴스 스스로의 값을 참조하여 또다른 값을 생성해야 할 때 사용하는 LazyAttribute 등 다양한 기능이 존재합니다. 더 자세한 내용은 Factory Boy 문서를 참고하시기 바랍니다.