Django / FactoryBoy - 相等查询不相等?

Django / FactoryBoy - Equal queries not equal?

我正在为 Django 应用程序编写测试,但我的一个测试因一个奇怪的错误而失败,其中 assertEqual 比较失败,即使两个查询集中的对象匹配。

测试量很大,所以我写了一个小测试来重现错误:

class StrangeBehaviorTest(TestCase):

    def test_init(self):
        purchase = ArrangementPurchaseFactory()

        self.assertTrue(purchase)
        self.assertTrue(purchase.arrangement_period)
        self.assertEqual(ArrangementPurchase.objects.count(), 1)

        fetched = ArrangementPurchase.objects.filter(
            id=1)

        self.assertEqual(fetched.first().id, purchase.id)
        self.assertEqual(fetched.first(), purchase)

        self.assertEqual(fetched, ArrangementPurchase.objects.filter(
            id=1
        ))

当我 运行 此测试时,最后一个断言失败并出现以下错误:

AssertionError: <QuerySet [<ArrangementPurchase: 1 : user_0 - Profound bandwidth-monitored pricing structure (vanaf 2019-04-24)>]> != <QuerySet [<ArrangementPurchase: 1 : user_0 - Profound bandwidth-monitored pricing structure (vanaf 2019-04-24)>]>

我已经确认我的 ArrangementPurchaseFactoryDjangoModelFactory 的子类(如下所示)

class ArrangementPurchaseFactory(factory.django.DjangoModelFactory):

    class Meta:
        model = arrangement_models.ArrangementPurchase

    user = factory.SubFactory(UserFactory)
    arrangement_period = factory.SubFactory(ArrangementPeriodFactory)
    purchase_date = factory.LazyFunction(
        lambda: timezone.now() - datetime.timedelta(days=10)
    )
    expire_date = factory.LazyFunction(
        lambda: timezone.now() + datetime.timedelta(days=30)
    )
    tenant_demo_purchase = False
    price_paid = factory.LazyFunction(lambda: Decimal(0))
    linked_order_id = factory.Faker('sha1')
    rabo_purchase_pending = False

据我所知,两个查询集中的对象都存在于数据库中(对象有一个 id 值),并且 fetched 查询的 pk 值与现有 purchase.id

那么为什么测试失败了?有谁知道我错过了什么?

因为这些querySet的值虽然相等,但实际上是不同的对象

您需要的是assertQuerysetEqual。来自文档:

TransactionTestCase.assertQuerysetEqual(qs, values, transform=repr, ordered=True, msg=None)[https://docs.djangoproject.com/en/2.2/topics/testing/tools/] Asserts that a queryset qs returns a particular list of values values.

The comparison of the contents of qs and values is performed using the function transform; by default, this means that the repr() of each value is compared. Any other callable can be used if repr() doesn’t provide a unique or helpful comparison.

By default, the comparison is also ordering dependent. If qs doesn’t provide an implicit ordering, you can set the ordered parameter to False, which turns the comparison into a collections.Counter comparison. If the order is undefined (if the given qs isn’t ordered and the comparison is against more than one ordered values), a ValueError is raised.

Output in case of error can be customized with the msg argument.

Django 不提供 QuerySet 实例之间的任何特定相等性比较(参见代码:https://github.com/django/django/blob/master/django/db/models/query.py#L185)。

当未提供自定义 __eq__ 方法时,Python 回退到比较内存中的对象地址。 QuerySet 的两个不同实例,即使从相同的参数构建,也会有不同的地址,并且比较不相等。

如果你想比较查询集的内容(即数据库中的对象列表),你必须评估它们,例如将它们转换为列表:

self.assertEqual(list(fetched), list(ArrangementPurchase.objects.filter(id=1)))

由此,Python将比较列表,其__eq__方法比较列表的内容而不是它们的内存地址。

注意:如果列表中的项目超过 1 项,您必须考虑排序。

另一种选择是使用 assertQuerysetEqualhttps://docs.djangoproject.com/en/2.2/topics/testing/tools/#django.test.TransactionTestCase.assertQuerysetEqual