오히려 좋아..

상황이 나쁘게만 흘러가는 것 같을 때 외쳐보자.. .

궁금한 마음으로 포트폴리오 보기

Language/Python

[FactoryBoy] Trouble Shooting

junha6316 2021. 11. 6. 11:12

테스트를 작성하면서 특정모델 A에 역참조인 모델을  A가 생성되면서 같이 생성해야하는 일이 있었는데 구현하면서 꽤 고생했다. 이 과정을 정리해둔다. 아래의 간단한 예시를 사용했다.

 

class Company(models.Model):
    
    name = models.CharField('회사명', max_length=100)
    created_at = models.DateTimeField('생성 일자', auto_now_add=True)
    updated_at = models.DateTimeField('업데이트 일자', auto_now=True)
    
  
class Product(models.Model):

    company = models.ForeignKey(Company, related_name='products', on_delete=models.CASACADE)
    name = models.CharField('제품 명', max_length=100)

 

1. RelatedFactoryList 사용하기

https://factoryboy.readthedocs.io/en/stable/reference.html?highlight=RelatedFactoryList#factory.RelatedFactoryList 

RelatedFactoryList는 모델 A를 외래키로 참조하고 있는 모델 B를 모델 A를 생성하면서 동시에 생성해야할 때 유용하다. 즉 일대다 관계있는 모델을 동시에 생성할 때 사용하면 좋다. 위의 예시를 빌려 설명하자면 Factory를 이용해 회사 모델을 생성하면서 제품 모델들도 동시에 생성하고 싶을 때이다. 위에 나와 있는 공식 홈페이지 대로 에시를 진행하면 잘 안될 뿐더러 관련된 내용도 자세하지 않다. 잘 안된다면 아래처럼 해보자.

class ProductFactory(factory.django.DjangoModelFactory):
    
    # 기본적으로 product가 비어있는 팩토리 생성
    company = factory.SubFactory(CompanyFactory, products=[])
    
    class Meta:
         model=Product
         
class CompanyFactory(factory.django.DjangoModelFactory):

     class Meta:
         model=Company
     
     # 3개의 product를 생성
     products = factory.RelatedFactoryList("ProductFactory", 'company', size=3)

위에 처럼 작성했을 때  CompanyFactory를 이용해 Company 모델을 생성하면 product도 3개씩 생성된다. 사실 이런 방식을 좋아하진 않는다. 테스트 코드에서 물론 자동 생성 되는 부분이 좋긴하지만 역참조하고 있는 모델을 커스텀해서 사용하기 어렵기 때문이다. 나는 이런식으로 사용한다.

class CompanyFactory(factory.django.DjangoModelFactory):

     class Meta:
         model=Company
     
     @factory.post_generation
     def products(self, create, extracted, **kwargs):
          if not create: # 생성되지 않았으면
              return None
              
            if extracted: # 입력값으로 주어진다면
                for product in extracted:
                    self.products.add(product)
                    self.save()
                return

위처럼 작성하면 product도 커스텀화 해서 작성할 수 있다.

produc_names = ['비비고', '바비고', '비바고']
CompanyFactory(
	 name="제일제당",
     products=[ProductFactory(name=name) for name in product_names]
)

 

2. Auto_now_add 속성 

created_at 같은 속성은 객체가 생성되는 시점에 할당 되기 떄문에 아무리 Factory로 인자로 넣어줘도 동작하지 않는다. 따라서 post_generation 을 이용해서 구현한다.

class ProductFactory(factory.django.DjangoModelFactory):
    
    company = factory.SubFactory(CompanyFactory, products=[])
    
    class Meta:
         model=Product
         
class CompanyFactory(factory.django.DjangoModelFactory):

     class Meta:
         model=Company
     
     # 3개의 product를 생성
     products = factory.RelatedFactoryList("ProductFactory", 'company', size=3)
     
     @factory.post_generation
     def created_at(self, create, extracted, **kwargs):
         if extracted:
             self.created_at=extracted
             self.save

3. Auto_now 속성 

auto_now 속성은 더 까다롭다. save 될 때마다 업데이트 되므로 임의로 값을 넣어주는게 증말로 어렵다. 내가 찾은 방법을 공유한다.

아래 방법이 번거롭긴 하지만 정확히되는 방법이다. save에 update_fields를 이용하는 방식이나 force_update, force_insert 같은 방식도 안되고 save메소드를 오버라이드 하자니 닭 잡는 칼에 소잡는 칼을 쓰는 느낌이였다. 다른 좋은 방법을 알고 계시다면 공유 바랍니다!

참고로 auto_now 속성은 save 메서드에 의해서 트리거 되는데 update, bulk_update는 이를 우회한다고 한다.

company = CompanyFactory(name="삼성")
Company.objects.filter(id=company.id).update(updated_at=timezone.now())