python django에서 faker와 factory boy를 사용한 더미 데이터 작성 방법

정훈·2024년 2월 29일
0

Faker와 Factory boy 사용 방법

이번 글에서는 python django에서 orm에 대한 더미 데이터를 생성하는 라이브러리인 factory boy와 가짜로 데이터를 생성하는 faker를 사용하여 테스트 데이터 관련 코드를 작성하는 방법에 대해서 작성했습니다.
factory boy와 faker를 사용하는 다양한 코드를 작성해 보았으니 많은 분들에게 참고가 되었으면 좋겠습니다.

faker와 factory boy를 활용한 더미 데이터 정의 방법

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']
  1. 6 Line → Faker를 한국어 버전으로 실행
  2. 9 Line→ factory boyDjangoModelFactory를 상속 받는다.
  3. 10-11 Line → meta 클래스에 사용할 model을 정의
  4. 13-14 Line → 6 Line에서 정의한 fake로 MaterialCases와 매칭되는 더미 데이터 생성

faker에서 정형화된 더미 데이터 생성 방법

예제에서는 전화번호를 예시로 했지만 다양한 데이터를 정의할 수 있습니다.

    
    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()
  1. 4 Line → faker에서 Provider를 상속 Provider는 PHP Faker 에서 차용한 개념으로 테스트 데이터를 생성하는 생성기
  2. 5-6 Line → kor_phone_number를 정의하여 한국형 phone_number 생성 함수 작성
  3. 10 Line → add_provider를 활용하여 매서드 추가
  4. 12 Line → 12줄과 같이 사용가능

password 저장 후 암호화되지 않는 password를 가져오는 방법

아래와 같이 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()
  1. 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)
  1. 이 코드를 override을 해보겠습니다.
    @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를 외래키로 사용하는 시공 테이블을 예시로 하겠습니다.

  1. 기본 적인 외래 키
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를 사용합니다.

  1. 외래키로 사용하는 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에서 ContrmngFactoryuser_id를 받아와야 합니다. 그럴 때 사용 할 수 있는 방법은 LazyAttribute입니다. LazyAttribute는 선언된 모든 변수를 생성 뒤에 만들어져야 하는 변수들을 정의하는 함수입니다.

파일 오브젝트 표현 하기

여기에서는 이미지 파일과 단순하게 형식만 갖춘 파일을 저장하는 방법에 대해서만 설명합니다. 다양한 더미 파일들을 만들 수 있지만, 테스트를 위한 코드이므로 더미 파일을 생성합니다.

  1. 이미지 오브젝트 저장

    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’이라는 파일 핸들링 오류가 발생합니다.

  2. 파일 오브젝트 저장

    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에 정형화되지 않은 데이터를 참고하여 작성하는 것을 추천해 드립니다. 추가로 이렇게 생성된 데이터를 확인하면 무분별한 바이너리로 채워진 파일이 생성되는데 이건 안에 내용이 중요하지 않을 경우에만 사용하는 것을 추천합니다.

profile
누군가에게 빛이 되길...

0개의 댓글