이번 글에서는 python django에서 orm에 대한 더미 데이터를 생성하는 라이브러리인 factory boy와 가짜로 데이터를 생성하는 faker를 사용하여 테스트 데이터 관련 코드를 작성하는 방법에 대해서 작성했습니다.
factory boy와 faker를 사용하는 다양한 코드를 작성해 보았으니 많은 분들에게 참고가 되었으면 좋겠습니다.
factory boy와 faker를 사용하여 더미 클래스를 정의합니다.
import factory
from faker import Faker
from materials.models import MaterialCases
fake = Faker('ko-KR')
class MaterialCasesFactory(factory.django.DjangoModelFactory):
class Meta:
model = MaterialCases
material_name = fake.sentence(nb_words=3)
material_unit = fake.word(ext_word_list=['톤', '매', 'M3']
factory boy
에 DjangoModelFactory
를 상속 받는다.예제에서는 전화번호를 예시로 했지만 다양한 데이터를 정의할 수 있습니다.
from faker import Faker
from faker.providers.phone_number import Provider
class PhoneNumberProvider(Provider):
def kor_phone_number(self):
return f'010-{self.msisdn()[:4]}-{self.msisdn()[:4]}'
fake = Faker()
fake.add_provider(PhoneNumberProvider)
fake.unique.kor_phone_number()
Provider
를 상속 Provider
는 PHP Faker 에서 차용한 개념으로 테스트 데이터를 생성하는 생성기kor_phone_number
를 정의하여 한국형 phone_number
생성 함수 작성아래와 같이 factory를 정의하면 UserFactory() 선언으로 user를 생성한다면 암호화된 password를 받게 됩니다. 이런 문제를 해결하는 방법에 관해서 서술합니다.
class UserFactory(factory.django.DjangoModelFactory):
class Meta:
model = MyUser
userid = f"{fake.user_name()}{int(fake.random.random() * 1000)}"
name = fake.word()
email = fake.email()
phone = fake.unique.kor_phone_number()
password = fake.password()
DjangoModelFactory
에서 모델을 생성하는 함수는 아래 코드 입니다. @classmethod
def _create(cls, model_class, *args, **kwargs):
"""Create an instance of the model, and save it to the database."""
if cls._meta.django_get_or_create:
return cls._get_or_create(model_class, *args, **kwargs)
manager = cls._get_manager(model_class)
return manager.create(*args, **kwargs)
@classmethod
def _create(cls, model_class, *args, **kwargs):
raw_password = kwargs['password']
kwargs['password'] = make_password(kwargs['password'])
user = super()._create( model_class, *args, **kwargs)
user.password = raw_password
return user
이 방법은 TestCode를 작성할 때만 작성해주시길 바랍니다. 만약 테스트 코드가 아닌 실 코드에서 사용이 된다면 사용자의 비밀번호가 노출되는 아주 끔찍한 사태가 발생합니다.
여기에서는 외래키에 대해 표현을 하는 방법에 관해서 서술합니다. 위에 선언한 User를 외래키로 사용하는 시공 테이블을 예시로 하겠습니다.
class ContrmngFactory(factory.django.DjangoModelFactory):
class Meta:
model = Contrmng
buildingname = faker.word()
address = faker.address()
user_id = factory.SubFactory(UserFactory)
faker
에서 제공하는 SubFactory()
함수를 사용하여 외래키를 표현할 수 있습니다. 외래키를 표현할 방법은 많지만, SubFactory
를 사용하는 이유는 test코드에서 ContrmngFactory
를 생성하게 되면 User도 자동으로 생성할 수 있기 때문입니다. 이러면 테스트 코드 작성 시에 많은 작업들을 생략 할 수 있어 저희는 SubFactory
를 사용합니다.
factory
의 값 사용class DailyFactory(factory.django.DjangoModelFactory):
class Meta:
model = Daily
contr_name = faker.word()
contrmng_id = factory.SubFactory(ContrmngFactory)
user_id = factory.LazyAttribute(lambda obj: obj.contrmng_id.user_id)
이 코드를 보면 위에서 선언한 ContrmngFactory
를 사용합니다. 하지만 DailyFactory
에서 ContrmngFactory
의 user_id
를 받아와야 합니다. 그럴 때 사용 할 수 있는 방법은 LazyAttribute
입니다. LazyAttribute
는 선언된 모든 변수를 생성 뒤에 만들어져야 하는 변수들을 정의하는 함수입니다.
여기에서는 이미지 파일과 단순하게 형식만 갖춘 파일을 저장하는 방법에 대해서만 설명합니다. 다양한 더미 파일들을 만들 수 있지만, 테스트를 위한 코드이므로 더미 파일을 생성합니다.
이미지 오브젝트 저장
class DrawingFactory(factory.django.DjangoModelFactory):
class Meta:
model = Drawing
title = faker.word()
level = faker.random_int(min=0, max=10)
image = factory.LazyAttribute(
lambda _: ContentFile(
factory.django.ImageField()._make_data(
{'width': 1024, 'height': 768}
), 'example.jpg'
)
)
contrmng_id = factory.SubFactory(ContrmngFactory)
created_datetime = faker.date_time()
updated_datetime = faker.date_time()
이미지 저장을 사용할 때도 이전에 사용한 LazyAttribute
가 사용됩니다. lambda를 사용해 factory에서 django 이미지 필드에 들어갈 수 있는 가로와 세로의 길이를 지정 후 이름을 지정하면 됩니다. 여기서 LazyAttribute
가 사용되는 이유는 그렇지 않으면 이미지에 대한 파일 이름과 같은 정보들이 동적으로 생성되지 않아 AttributeError: 'function' object has no attribute '_committed’
이라는 파일 핸들링 오류가 발생합니다.
파일 오브젝트 저장
class ScheduleFactory(factory.django.DjangoModelFactory):
class Meta:
model = Schedule
title = fake.word()
note = fake.text()
file = ContentFile(fake.binary(length=1024), name='dummy_file.xlsx')
start_date = fake.date_time_between(start_date='-30d', end_date='-29d')
end_date = fake.date_time_between(start_date=start_date, end_date=start_date + timezone.timedelta(days=100))
CONVERT_YN = fake.word(ext_word_list=['Y', 'Z', 'N'])
contrmng_id = factory.SubFactory(ContrmngFactory)
간단한 엑셀 형식의 파일이름을 가진 데이터를 생성하려고 한다면 이 방법을 사용하는 게 좋습니다. 만약에 실제로 엑셀 파일 안에 데이터 내용이 들어간 엑셀을 만들고 싶은 경우에는 2번에 faker에 정형화되지 않은 데이터를 참고하여 작성하는 것을 추천해 드립니다. 추가로 이렇게 생성된 데이터를 확인하면 무분별한 바이너리로 채워진 파일이 생성되는데 이건 안에 내용이 중요하지 않을 경우에만 사용하는 것을 추천합니다.