模拟Django查询集以测试接受查询集的函数
我在我的Django项目里有一个工具函数,它接收一个查询集(queryset),从中获取一些数据并返回结果。我想为这个函数写一些测试。有没有办法可以“模拟”一个查询集?我想创建一个对象,它不接触数据库,我可以给它提供一组值(也就是一些虚假的数据行),然后它就能像查询集一样工作,让人可以对它进行字段查找、过滤、获取所有数据等操作。
有没有类似的东西已经存在了?
9 个回答
当然,你可以模拟一个查询集(QuerySet),其实你可以模拟任何东西。
你可以自己创建一个对象,给它你需要的接口,然后让它返回你想要的任何数据。简单来说,模拟就是提供一个“测试替身”,这个替身在测试中表现得足够像真实的东西。
一个简单的入门方法是先定义一个对象:
class MockQuerySet(object):
pass
然后创建一个这样的对象,把它交给你的测试。测试可能会失败,通常是因为出现了AttributeError
错误。这会告诉你在你的MockQuerySet
中需要实现什么。重复这个过程,直到你的对象足够丰富,能够满足测试的需求。
对于一个空的查询集,我会直接使用 none
,就像 keithhackbarth 已经提到的那样。
不过,如果想要模拟一个查询集,让它返回一系列值,我更喜欢用一个 Mock 对象,并且指定它的 spec
为模型的管理器。举个例子(这是 Python 2.7 的写法 - 我使用了外部的 Mock 库),下面是一个简单的测试,查询集经过过滤后再进行计数:
from django.test import TestCase
from mock import Mock
from .models import Example
def queryset_func(queryset, filter_value):
"""
An example function to be tested
"""
return queryset.filter(stuff=filter_value).count()
class TestQuerysetFunc(TestCase):
def test_happy(self):
"""
`queryset_func` filters provided queryset and counts result
"""
m_queryset = Mock(spec=Example.objects)
m_queryset.filter.return_value = m_queryset
m_queryset.count.return_value = 97
result = func_to_test(m_queryset, '__TEST_VALUE__')
self.assertEqual(result, 97)
m_queryset.filter.assert_called_once_with(stuff='__TEST_VALUE__')
m_queryset.count.assert_called_once_with()
不过,为了满足问题的要求,除了为 count
设置 return_value
,还可以很简单地调整为从 all
返回的模型实例的 list
。
注意,链式调用是通过设置 filter
来返回模拟的查询集来处理的:
m_queryset.filter.return_value = m_queryset
这需要应用于被测试函数中使用的任何查询集方法,比如 exclude
等等。
我不知道有没有这样的情况,但为什么不直接用真实的数据查询呢?测试框架已经准备好了,可以让你在测试中创建示例数据,而且每次测试时数据库都会被重新创建,所以用真实的数据似乎没有什么不妥。