【问题标题】:In FactoryBoy, how do I setup my factory with an empty many-to-many member field?在 FactoryBoy 中,如何使用空的多对多成员字段设置我的工厂?
【发布时间】:2020-11-01 06:11:27
【问题描述】:

我将 Django 3 与 Python 3.8 一起使用。我有以下型号...

class Coop(models.Model):
    objects = CoopManager()
    name = models.CharField(max_length=250, null=False)
    types = models.ManyToManyField(CoopType, blank=False)
    addresses = models.ManyToManyField(Address)
    enabled = models.BooleanField(default=True, null=False)
    phone = models.ForeignKey(ContactMethod, on_delete=models.CASCADE, null=True, related_name='contact_phone')
    email = models.ForeignKey(ContactMethod, on_delete=models.CASCADE, null=True, related_name='contact_email')
    web_site = models.TextField()

我创建了以下工厂(使用工厂男孩)尝试在测试中创建模型...

class CoopFactory(factory.DjangoModelFactory):
    """
        Define Coop Factory
    """
    class Meta:
        model = Coop

    name = "test model"
    enabled = True
    phone = factory.SubFactory(PhoneContactMethodFactory)
    email = factory.SubFactory(EmailContactMethodFactory)
    web_site = "http://www.hello.com"

    @factory.post_generation
    def addresses(self, create, extracted, **kwargs):
        if not create:
            # Simple build, do nothing.
            return

        if extracted:
            # A list of types were passed in, use them
            for address in extracted:
                self.addresses.add(address)
        else:
            address = AddressFactory()
            self.addresses.add( address )

    @factory.post_generation
    def types(self, create, extracted, **kwargs):
        if not create:
            # Simple build, do nothing.
            return

        if extracted:
            # A list of types were passed in, use them
            for type in extracted:
                self.types.add(type)
        else:
            print("Creating type ...\n")
            type = CoopTypeFactory()
            self.types.add( type )

但我无法创建一个多对多字段(类型)为空的工厂。我尝试了以下

@pytest.mark.django_db
def test_coop_create_with_no_types(self):
    """ Test customer model """    # create customer model instance
    coop = CoopFactory.create(types=[])
    print("size: ", coop.types.all().count())
    self.assertIsNotNone(coop)
    self.assertIsNotNone( coop.id )

types.all().count() 的值始终等于1。如何正确设置带有空多对多字段的工厂?

编辑:响应给出的答案,传递工厂使用的成员字段的正确方法是什么?我试过了

@pytest.mark.django_db
def test_coop_create_with_existing_type(self):
    """ Test customer model """    # create customer model instance
    coop_from_factory = CoopFactory()
    self.assertIsNotNone(coop_from_factory)

    coop_types = coop_from_factory.types
    coop = CoopFactory.create(types=[coop_types.all().first()], addresses=coop_from_factory.addresses.all())
    self.assertIsNotNone(coop)

但出现此错误,对于“for _ in range(extracted):”行...

Traceback (most recent call last):
  File "/Users/davea/Documents/workspace/chicommons/maps/web/tests/test_models.py", line 48, in test_coop_create_with_existing_type
    coop = CoopFactory.create(types=coop_types, addresses=coop_from_factory.addresses.all())
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/factory/base.py", line 564, in create
    return cls._generate(enums.CREATE_STRATEGY, kwargs)
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/factory/django.py", line 141, in _generate
    return super(DjangoModelFactory, cls)._generate(strategy, params)
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/factory/base.py", line 501, in _generate
    return step.build()
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/factory/builder.py", line 296, in build
    postgen_results[declaration_name] = declaration.declaration.call(
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/factory/declarations.py", line 622, in call
    return self.function(
  File "/Users/davea/Documents/workspace/chicommons/maps/web/tests/factories.py", line 128, in types
    for _ in range(extracted):
TypeError: 'ManyRelatedManager' object cannot be interpreted as an integer

【问题讨论】:

    标签: python-3.x django unit-testing django-3.0 factory-boy


    【解决方案1】:

    解决方法是将if extracted 更改为if extracted is not None

    说明

    在 Python 中,空列表是错误的1,但不是 None

    coop = CoopFactory.create(types=[])
    

    空列表[]作为参数extracted传递给后生成钩子types

    @factory.post_generation
    def types(self, create, extracted, **kwargs):
        if not create:
            # Simple build, do nothing.
            return
    
        if extracted:
            # A list of types were passed in, use them
            for type in extracted:
                self.types.add(type)
        else:
            print("Creating type ...\n")
            type = CoopTypeFactory()
            self.types.add( type )
    

    由于if extracted 是一个真值测试1,因此错误的空列表属于else 块,其中创建了type。所以,types.all().count() 的值等于 1。

    1https://docs.python.org/3/library/stdtypes.html#truth-value-testing

    【讨论】:

      【解决方案2】:

      跳过

              else:
                  print("Creating type ...\n")
                  type = CoopTypeFactory()
                  self.types.add( type )
      

      这始终会默认创建 CoopType。

      文档中可能不清楚的一件事是@factory.post_generation 钩子总是被调用。这意味着示例代码中所有 post_generation 钩子中的 else 语句将始终被调用。

      更多信息:Simple Many-to-many relationship

      如果我想直接创建默认值,我经常使用的模式是 向工厂添加一个函数,在本例中,该函数将转换为:

      @factory.post_generation
      def create_types(self, create, extracted, **kwargs):
          if not create:
              # Simple build, do nothing.
              return
      
          if extracted:
              for _ in range(extracted):
                  self.types.add(CoopTypeFactory())
      

      允许使用CoopFactory(create_types=3)

      这是我的完整示例:

      @factory.post_generation
      def types(self, create, extracted, **kwargs):
          if not create:
              # Simple build, do nothing.
              return
      
          if extracted:
              # A list of types were passed in, use them
              for type in extracted:
                  self.types.add(type)
          # Removed this because it always creates 1 CoopType as default and
          # it may not be the desired behaviour for all tests.
          # else:
          #     print("Creating type ...\n")
          #     type = CoopTypeFactory()
          #     self.types.add( type )
      
      # Adding this function to have a simple way of just adding default CoopTypes
      @factory.post_generation
      def create_types(self, create, extracted, **kwargs):
          if not create:
              # Simple build, do nothing.
              return
      
          if extracted: # This must be an integer
              for _ in range(extracted):
                  self.types.add(CoopTypeFactory())
      

      这给出了可选的用法:

      CoopFactory(create_types=3) 将调用 create_types 并将 int 3 放入提取的参数中并创建 3 个默认 CoopTypes。 (这使得使用简单)

      CoopFactory(types=[CoopTypeFactory()]) 将调用类型并将 1 CoopType 的列表放入提取的参数中。 (如果这些对象需要某些特定值,这可以更好地控制 CoopTypes 的创建方式)

      【讨论】:

      • 嗨,谢谢。当使用“for _ in range(extracted):”时,为我的成员字段传递非空参数的正确方法是什么?为了响应您的建议,我已将收到的错误添加为对我的问题的编辑。
      • 看起来您没有在错误日志中传递整数。我添加了更详细的解释。我希望它澄清。
      • 哦,我看到你传入一个数字并告诉它创建 X 个这些类型。我实际上是在寻找一种方法来传递工厂将用于类型的对象集合。换句话说,我想说,创建这个对象,使用所有默认字段,但是如果我指定“类型”,那么使用我指定的。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-03-07
      • 1970-01-01
      • 1970-01-01
      • 2011-02-01
      • 2021-08-21
      • 1970-01-01
      相关资源
      最近更新 更多