我试图找出实用程序函数执行的查询数。我已经为这个函数编写了一个单元测试,并且该函数运行良好。我想要做的是跟踪函数执行的SQL查询的数量,以便我可以看到在重构之后是否有任何改进。
def do_something_in_the_database():
# Does something in the database
# return result
class DoSomethingTests(django.test.TestCase):
def test_function_returns_correct_values(self):
self.assertEqual(n, <number of SQL queries executed>)
编辑:我发现有一个待定的Django feature request。但是票仍然是开放的。与此同时还有另一种方法可以解决这个问题吗?
答案 0 :(得分:54)
由于Django 1.3有一个assertNumQueries可用于此目的。
答案 1 :(得分:40)
Vinay的回答是正确的,只有一个小的补充。
Django的单元测试框架在运行时实际上将DEBUG设置为False,因此无论您在settings.py
中拥有什么,除非重新启用,否则在单元测试中connection.queries
中不会包含任何内容调试模式。 Django文档将rationale for this解释为:
无论配置文件中DEBUG设置的值如何,所有Django测试都以DEBUG = False运行。这是为了确保您观察到的代码输出与生产设置中的输出相匹配。
如果您确定启用调试不会影响您的测试(例如,如果您专门测试数据库命中,就像您听起来一样),解决方案是在您的单元测试中暂时重新启用调试,然后把它放回去:
def test_myself(self):
from django.conf import settings
from django.db import connection
settings.DEBUG = True
connection.queries = []
# Test code as normal
self.assert_(connection.queries)
settings.DEBUG = False
答案 2 :(得分:7)
如果您使用pytest
,pytest-django
有django_assert_num_queries夹具用于此目的:
def test_queries(django_assert_num_queries):
with django_assert_num_queries(3):
Item.objects.create('foo')
Item.objects.create('bar')
Item.objects.create('baz')
答案 3 :(得分:4)
如果DEBUG
中settings.py
设置为True(可能在您的测试环境中也是如此),那么您可以按如下方式计算测试中执行的查询:
from django.db import connection
class DoSomethingTests(django.test.TestCase):
def test_something_or_other(self):
num_queries_old = len(connection.queries)
do_something_in_the_database()
num_queries_new = len(connection.queries)
self.assertEqual(n, num_queries_new - num_queries_old)
答案 4 :(得分:3)
在现代Django(&gt; = 1.8)中,它有很好的文档记录(它还记录了1.7)here,你有方法 reset_queries 而不是分配 connection.queries = [] 确实引发了错误,类似于django&gt; = 1.8:
class QueriesTests(django.test.TestCase):
def test_queries(self):
from django.conf import settings
from django.db import connection, reset_queries
try:
settings.DEBUG = True
# [... your ORM code ...]
self.assertEquals(len(connection.queries), num_of_expected_queries)
finally:
settings.DEBUG = False
reset_queries()
您也可以考虑在setUp / tearDown上重置查询以确保为每个测试重置查询而不是在finally子句上执行查询,但这种方式更明确(尽管更详细),或者您可以使用 reset_queries < / strong>在try子句中,您需要多次评估从0开始计数的查询。
答案 5 :(得分:2)
如果您不想使用TestCase(使用assertNumQueries)或将设置更改为DEBUG = True,则可以使用上下文管理器CaptureQueriesContext(与assertNumQueries相同)。
from django.db import ConnectionHandler
from django.test.utils import CaptureQueriesContext
DB_NAME = "default" # name of db configured in settings you want to use - "default" is standard
connection = ConnectionHandler()[DB_NAME]
with CaptureQueriesContext(connection) as context:
... # do your thing
num_queries = context.initial_queries - context.final_queries
assert num_queries == expected_num_queries
答案 6 :(得分:0)
如果要为此使用装饰器,请使用nice gist:
import functools
import sys
import re
from django.conf import settings
from django.db import connection
def shrink_select(sql):
return re.sub("^SELECT(.+)FROM", "SELECT .. FROM", sql)
def shrink_update(sql):
return re.sub("SET(.+)WHERE", "SET .. WHERE", sql)
def shrink_insert(sql):
return re.sub("\((.+)\)", "(..)", sql)
def shrink_sql(sql):
return shrink_update(shrink_insert(shrink_select(sql)))
def _err_msg(num, expected_num, verbose, func=None):
func_name = "%s:" % func.__name__ if func else ""
msg = "%s Expected number of queries is %d, actual number is %d.\n" % (func_name, expected_num, num,)
if verbose > 0:
queries = [query['sql'] for query in connection.queries[-num:]]
if verbose == 1:
queries = [shrink_sql(sql) for sql in queries]
msg += "== Queries == \n" +"\n".join(queries)
return msg
def assertNumQueries(expected_num, verbose=1):
class DecoratorOrContextManager(object):
def __call__(self, func): # decorator
@functools.wraps(func)
def inner(*args, **kwargs):
handled = False
try:
self.__enter__()
return func(*args, **kwargs)
except:
self.__exit__(*sys.exc_info())
handled = True
raise
finally:
if not handled:
self.__exit__(None, None, None)
return inner
def __enter__(self):
self.old_debug = settings.DEBUG
self.old_query_count = len(connection.queries)
settings.DEBUG = True
def __exit__(self, type, value, traceback):
if not type:
num = len(connection.queries) - self.old_query_count
assert expected_num == num, _err_msg(num, expected_num, verbose)
settings.DEBUG = self.old_debug
return DecoratorOrContextManager()
答案 7 :(得分:0)
这是上下文管理器的工作原型,具有AssertNumQueriesLessThen
import json
from contextlib import contextmanager
from django.test.utils import CaptureQueriesContext
from django.db import connections
@contextmanager
def withAssertNumQueriesLessThen(self, value, using='default', verbose=False):
with CaptureQueriesContext(connections[using]) as context:
yield # your test will be run here
if verbose:
msg = "\r\n%s" % json.dumps(context.captured_queries, indent=4)
else:
msg = None
self.assertLess(len(context.captured_queries), value, msg=msg)
它可以简单地用于您的单元测试,例如用于检查每个Django REST API调用的查询数量
with self.withAssertNumQueriesLessThen(10):
response = self.client.get('contacts/')
self.assertEqual(response.status_code, 200)
如果您想要将实际查询的列表漂亮地打印到stdout,也可以提供确切的数据库using
和verbose