매우 간단한 Post, Comment 모델이 있다고 가정하자. 개발환경에서 테스트를 위해 10000개 정도의 Post와 Comment를 만들고 싶다.
class TimeRecordingMixin(models.Model):
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
abstract = True
post_states = (("PUBLISHED", "발행"), ("HIDDEN", "숨김"), ("DELETED", "삭제"))
class Post(TimeRecordingMixin, models.Model):
title = models.CharField(max_length=40, blank=False, null=False)
content = models.TextField(blank=False, null=False)
status = models.CharField(
max_length=20, choices=post_states, blank=False, null=False,
default="PUBLISHED"
)
class Meta:
db_table = "post"
ordering = ["-created_at"]
class Comment(TimeRecordingMixin, models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name="comments")
content = models.CharField(max_length=120, blank=False, null=False)
class Meta:
db_table = "comment"
그럴때 factory를 사용해주면 되는데, Django ORM이므로factory.django.DjangoModelFactory를 사용해주면 된다.
사용법은 매우 간단하다. DjangoModelFactory를 상속받은 팩토리 클래스를 만들고, 아래와 같이 메타 클래스에 모델을 선언하면 된다.
from factory.django import DjangoModelFactory
from factory import Faker
from post.models import Post
class PostFactory(DjangoModelFactory):
class Meta:
model = Post
title = Faker("text", max_nb_chars=100)
content = Faker("text", max_nb_chars=200)
여기서 title, content은 있어도 없어도 상관없다. 그저 초기값일뿐이다. 만약 title이나 content가 null=False 이면 PostFactory 클래스를 생성할때 꼭 값을 넣어줘야한다. ex) PostFactory(title="테스트", content="테스트 content")
만약 null=True인데 blank=True이고 PostFactory()로 post를 만들면 기본으로 '' 같은 빈 값이 들어간다.
이 PostFactory 클래스는 이렇게 사용할 수 있다.
PostFactory(): Post를 create
PostFactory.build(): create 하지 않고 인스턴스만 만든다.
PostFactory.create(): create
PostFactory.stub(): DB에 저장되는 값이 아닌 테스트를 위한 단순 데이터
PostFactory.build_batch(size=10): size만큼 인스턴스를 만든다.
PostFactory.create_batch(size=10): size만큼 create 한다.
바로 테스트해보자
from django.test import TestCase
from post.post_factory import PostFactory
from post.models import Post
class PostFactoryTest(TestCase):
def test_build는_instance만_생성(self):
post = PostFactory.build(title="test")
self.assertEqual(post.title, "test")
self.assertIsNone(post.id)
posts = PostFactory.build_batch(10)
self.assertEqual(len(posts), 10)
post_size = Post.objects.all().count()
self.assertEqual(post_size, 0) # 실제론 저장 안됨
def test_create는_create(self):
post = PostFactory(title="test")
self.assertEqual(post.title, "test")
self.assertEqual(post.id, 1)
posts = PostFactory.create_batch(10)
self.assertEqual(len(posts), 10)
post_size = Post.objects.all().count()
self.assertEqual(post_size, 11) # 실제로 저장됨
def test_stub은_단순_데이터(self):
post = PostFactory.stub(title="stub test")
self.assertEqual(post.title, "stub test") # DB에 저장될 수 없는 값
with self.assertRaises(AttributeError):
self.assertIsNone(post.id)
테스트 결과
build는 인스턴스만 만들뿐, 실제로 저장을 하지 않으니 id값이 존재하지 않았고
create는 id가 존재하고, count 쿼리 결과 값으로 생성된걸 확인할 수 있었으며
test_stub은 인스턴스가 아니므로(Post가 아니므로) id를 참조하려고하면 AttributeError가 발생했다.
Faker로 초기값을 선언하면 이렇게 임의의 값을 넣어주는걸 확인할 수 있다.
Comment 경우는 어떻게?
위의 PostFactory 같은 경우는 정말 단순히 title, content같은 text값을 넣어주면 된다. 하지만 comment의 경우 post가 있어야 comment가 존재할 수 있는데 이런 경우는 어떻게 Factory클래스를 만들 수 있을까?
이럴땐 SubFactory가 있다. 위에서 만든 PostFactory를 SubFactory로 사용하면 된다.
from factory.django import DjangoModelFactory
from factory import Faker, SubFactory
from post.models import Comment
from post.post_factory import PostFactory
class CommentFactory(DjangoModelFactory):
class Meta:
model = Comment
post = SubFactory(PostFactory)
content = Faker("text", max_nb_chars=200)
테스트 해보자
from django.test import TestCase
from post.comment_factory import CommentFactory
from post.post_factory import PostFactory
from post.models import Post
class CommentFactoryTest(TestCase):
def test_comment를_create(self):
comment = CommentFactory(content="test")
self.assertEqual(comment.content, "test")
self.assertEqual(comment.id, 1)
self.assertIsNotNone(comment.post)
self.assertEqual(comment.post.id, 1)
post = Post.objects.get(id=comment.post.id)
self.assertIsNotNone(post.comments)
self.assertEqual(post.comments.count(), 1)
def test_comment를_create_batch(self):
comments = CommentFactory.create_batch(size=10)
comment_post = comments[0].post
self.assertNotEquals(len(comments), comment_post.comments.count())
post = PostFactory()
post_comments = CommentFactory.create_batch(size=10, post=post)
self.assertEqual(len(post_comments), post.comments.count())
Comment를 만들때, Post를 하나씩 같이 만들게 된다. (comment1을 만들때 post1 만들고, comment1을 만들게 됨)
Django shell에서도 확인 가능하다.
그래서 특정 post에 comment를 여러개 만들고 싶으면 post하나를 PostFactory로 미리 만들고, create할때 인자로 넣어주면 된다.
이 기능들 외에도 Lazy Attribute, Sequence 같은 유용한 기능들을 제공하므로 공식문서를 참고해서 편하게 테스트 코드를 작성하자!