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)>]>
我已经确认我的 ArrangementPurchaseFactory
是 DjangoModelFactory
的子类(如下所示)
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 项,您必须考虑排序。
另一种选择是使用 assertQuerysetEqual
:https://docs.djangoproject.com/en/2.2/topics/testing/tools/#django.test.TransactionTestCase.assertQuerysetEqual
我正在为 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)>]>
我已经确认我的 ArrangementPurchaseFactory
是 DjangoModelFactory
的子类(如下所示)
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 项,您必须考虑排序。
另一种选择是使用 assertQuerysetEqual
:https://docs.djangoproject.com/en/2.2/topics/testing/tools/#django.test.TransactionTestCase.assertQuerysetEqual