详细记录您的Django代码的性能。
项目描述
详细记录您的Django代码的性能。
django-perf-rec类似于Django的assertNumQueries,但更强大。它允许您跟踪代码中发生的单个查询和缓存操作。您可以在测试中使用它,如下所示
def test_home(self):
with django_perf_rec.record():
self.client.get("/")
然后,它在测试文件旁边存储一个YAML文件,该文件跟踪查询和操作,看起来像这样
MyTests.test_home:
- cache|get: home_data.user_id.#
- db: 'SELECT ... FROM myapp_table WHERE (myapp_table.id = #)'
- db: 'SELECT ... FROM myapp_table WHERE (myapp_table.id = #)'
当再次运行测试时,新记录将与YAML文件中的记录进行比较。如果它们不同,将引发断言失败,导致测试失败。魔法!
查询和键被“指纹化”,用#和…替换了看起来可能变化的 信息。这是为了避免例如当主键不同、使用随机数据、向表中添加新列等时产生虚假失败。
如果您与测试一起检查YAML文件,您将拥有不可破坏的性能,并且与assertNumQueries相比,您将获得关于任何回归的更多信息。如果您对失败的测试中的更改感到满意,只需删除文件并重新运行测试以重新生成它。
有关更多信息,请参阅我们的介绍性博客文章,该文章介绍了我们为什么制作它的原因。
您的测试是否缓慢? 查阅我的书籍 加快Django测试速度,其中涵盖了许多编写更快、更准确测试的方法。
安装
使用 pip
python -m pip install django-perf-rec
要求
支持Python 3.8到3.12。
支持Django 3.2到5.1。
API
record(record_name: str | None=None, path: str | None=None, capture_traceback: callable[[Operation], bool] | None=None, capture_operation: callable[[Operation], bool] | None=None)
返回一个上下文管理器,用于单个性能测试。
必须以关键字参数的形式传递参数。
path 是存储记录的目录或文件的路径。如果它以 '/' 结尾,或者留为 None,则将自动根据调用代码中的文件名确定文件名,并将 .py[c] 扩展名替换为 .perf.yml。如果它指向一个不存在的目录,则会创建该目录。
record_name 是性能文件内要使用的记录名称。如果留为 None,则代码假设您处于Django TestCase 内,并使用魔法堆栈检查来找到该测试用例,并使用基于测试用例名称 + 测试方法名称 + 可选的计数器(如果您在同一测试方法内多次调用 record())的名称。
在打开期间,上下文管理器跟踪所有连接上的所有数据库查询以及所有定义的缓存上的所有缓存操作。它使用跟踪操作中使用的连接/缓存名称,除了默认的之外。
当上下文管理器退出时,它将使用收集到的操作列表。如果使用 path 指定的相关文件不存在,或者不包含特定 record_name 的数据,则将创建并保存该文件,测试将通过而不进行断言。然而,如果记录在文件中 确实 存在,则收集的记录将与原始记录进行比较,如果不同,将引发 AssertionError。在pytest上运行时,这将使用其花哨的断言重写;在其他测试运行器/使用中,完整的差异将附加到消息中。
示例
import django_perf_rec
from app.models import Author
class AuthorPerformanceTests(TestCase):
def test_special_method(self):
with django_perf_rec.record():
list(Author.objects.special_method())
capture_traceback,如果不为 None,则应该是一个函数,它接受一个参数,即给定的数据库或缓存操作,并返回一个 bool 值,指示是否应捕获该操作的回溯(默认情况下,它们不会)。捕获回溯允许对导致操作的代码路径进行精细的调试。请注意,仅因回溯的存在而不同的记录不会匹配,并引发 AssertionError,因此通常不适用于永久记录回溯。
例如,如果您想了解哪些代码路径查询 my_table 表,您可以使用如下 capture_traceback 函数
def debug_sql_query(operation):
return "my_tables" in operation.query
def test_special_method(self):
with django_perf_rec.record(capture_traceback=debug_sql_query):
list(Author.objects.special_method())
这里的性能记录将包括附加到包含“my_table”的每个SQL查询上的标准Python回溯。
capture_operation,如果非 None,则应是一个函数,它接受一个参数,即给定的数据库或缓存操作,并返回一个 bool 值,表示是否应该记录该操作(默认情况下,所有操作都会被记录)。不记录某些操作可以隐藏一些代码路径,以便在测试中忽略,例如,在生产中由外部服务替换的数据库查询。
例如,如果您知道在测试中某个表的查询在生产中都会被替换,则可以使用如下所示的 capture_operation 函数
def hide_my_tables(operation):
return "my_tables" in operation.query
def test_special_function(self):
with django_perf_rec.record(capture_operation=hide_my_tables):
list(Author.objects.all())
TestCaseMixin
这是一个混合类,可以添加到您自定义的 TestCase 子类中,这样您就可以在代码库中使用 django-perf-rec,而无需在每个单独的测试文件中导入它。它添加了一个方法,record_performance(),其签名与上面的 record() 相同。
示例
# yplan/test.py
from django.test import TestCase as OrigTestCase
from django_perf_rec import TestCaseMixin
class TestCase(TestCaseMixin, OrigTestCase):
pass
# app/tests/models/test_author.py
from app.models import Author
from yplan.test import TestCase
class AuthorPerformanceTests(TestCase):
def test_special_method(self):
with self.record_performance():
list(Author.objects.special_method())
get_perf_path(file_path)
封装了在 record() 中用于从当前正在运行的测试的文件路径形成 path 的逻辑,主要将 ‘.py’ 或 ‘.pyc’ 替换为 ‘.perf.yml’。您可能希望在从测试之外的位置调用 record() 时使用此功能(这会导致自动检查失败),以匹配相同的文件名。
get_record_name(test_name, class_name=None)
封装了在 record() 中用于从当前正在运行的测试的详细信息形成 record_name 的逻辑。您可能希望在从测试之外的位置调用 record() 时使用此功能(这会导致自动检查失败),以匹配相同的 record_name。
设置
您可以通过在 Django 设置中使用名为 PERF_REC 的字典来自定义行为,例如
PERF_REC = {
"MODE": "once",
}
下面解释了此字典的可能键。
HIDE_COLUMNS
可以使用 HIDE_COLUMNS 设置来更改 django-perf-rec 在它创建的记录文件中简化 SQL 的方式。它接受一个布尔值
True(默认)会导致查询中的列列表折叠,例如,SELECT a, b, c FROM t 变为 SELECT ... FROM t。这对于因为选择的列通常不会影响典型的 Django 应用程序中的查询时间来说很有用,它使得记录更容易阅读,并且不需要在每次更改模型字段时更新。
False 停止折叠行为,导致文件中输出所有列。
MODE
可以使用 MODE 设置来更改在测试运行期间不存在性能记录时 django-perf-rec 的行为。
'once'(默认)默默地创建缺少的记录。
'none' 在记录不存在时引发 AssertionError。您可能希望在 CI 中使用此模式,以确保新测试失败,如果它们对应的功能记录尚未提交。
'all' 创建缺少的记录,然后引发 AssertionError。
'overwrite' 默默创建或更新记录。
Pytest 中的用法
如果您正在使用 Pytest,您可能希望在 Pytest 固定例程内部调用 record(),并使其自动应用于所有测试。我们在测试套件中有此示例,请参阅 test_pytest_fixture_usage.py 文件。
项目详情
下载文件
下载适合您平台的文件。如果您不确定选择哪个,请了解更多关于安装包的信息。