是否可以将命令行参数传递给 Django 的
manage.py
脚本,特别是用于单元测试?即如果我做类似的事情
manage.py test myapp -a do_this
我可以在单元测试的
do_this
函数中接收到值setUp
吗?
P.S. @Martin 询问在测试中使用命令行参数的理由:
一些广泛的测试需要花费大量时间,并且不需要在每次提交之前运行。我想让它们成为可选的。
我的测试用例偶尔打印的调试消息应该是可选的
有时我只是想让测试变得疯狂并尝试更多的数据排列。
使用命令行选项,上述所有内容都会非常方便。有时测试可能会更加广泛或冗长,否则会很快。
我自己也遇到了这个问题,我想避免在命令行上设置环境变量。环境变量当然有效,但很难跟踪哪些变量会产生影响,而且如果您输错了其中一个变量,也不会出现任何错误消息来通知您。
为了解决这个问题,我使用
argparse
将额外的参数提取到命令行参数中。例如,我的 manage.py
文件现在看起来像这样:
#!/usr/bin/env python
import os
import sys
import argparse
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "project.settings")
argv = sys.argv
cmd = argv[1] if len(argv) > 1 else None
if cmd in ['test']: # limit the extra arguments to certain commands
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument('--foo', default='bar')
args, argv = parser.parse_known_args(argv)
# We can save the argument as an environmental variable, in
# which case it's to retrieve from within `project.settings`,
os.environ['FOO'] = args.foo
# or we can save the variable to settings directly if it
# won't otherwise be overridden.
from django.conf import settings
settings.foo = args.foo
from django.core.management import execute_from_command_line
# parse_known_args strips the extra arguments from argv,
# so we can safely pass it to Django.
execute_from_command_line(argv)
argparse
是一个非常好的库,有很多功能。 Python 文档中有一个很好的教程。
我在我的项目中使用环境变量解决方法(仅适用于类 UNIX shell)
berry$ myvar=myval ./manage.py test
在您的模块中使用
读取该值os.environ.get('myvar')
Django 允许从 testrunner 类添加自定义命令行选项。您可以创建默认 testrunner 类的子类并添加您自己的选项,然后让 django 使用您的自定义 testrunner,如下所示。
例如,在你的Django项目目录中创建一个testrunner.py,包含:
from django.test.runner import DiscoverRunner
class TestRunner(DiscoverRunner):
def __init__(self, option=None, **kwargs):
super().__init__(**kwargs)
print("Passed option: {}".format(option))
@classmethod
def add_arguments(cls, parser):
DiscoverRunner.add_arguments(parser)
parser.add_argument('-o', '--option', help='Example option')
这是一个从默认运行器派生的测试运行器(因此它的工作方式与默认运行器一样),除了它告诉 django 添加额外的命令行选项(在
add_arguments()
类方法中)并处理此额外选项的值构造函数。要使用这个新的运行程序运行,请按如下方式传递其名称:
./manage.py test --testrunner=testrunner.TestRunner -o foo
当然,您可以将此类放在其他任何地方,只要您在命令行上将完整的导入名称传递给它即可。
请注意,您必须使用
--testrunner=foo
,不能使用两个单独的参数(--testrunner foo
),因为这样额外的参数不起作用。修复待定:https://github.com/django/django/pull/10307
此示例仅打印选项值,但您需要以某种方式将其传递到测试用例。我找不到任何有关如何将选项传递给单元测试测试用例的快速信息,但您可能只需使用全局(模块级别)变量或类变量(这不是那么可重入和优雅,但很简单)有效)。
作为
manage.py test -a do_this
的替代方法,您可以使用特定的设置文件
manage.py --settings=project.test_settings test
并在此文件中定义您想要的任何内容。
# test_setting.py
SPECIFIC_OPTION = "test"
# tests.py
from django.conf import settings
...
def setUp(self):
if settings.SPECIFIC_OPTION:
....
如果你需要真正的动态选项,也许你可以在
sys.argv
中使用test_settings.py
,但这是一个非常肮脏的黑客。
按照 @Matthijs 的回答,您可以扩展
setup_test_environment
方法,类似于 DiscoveryRunner
处理 调试模式 的方式。
它会更改 settings.DEBUG
的值,可以通过导入 django.conf.settings
在测试中使用它。但您也可以添加自己的设置:
from django.test.runner import DiscoverRunner
class TestRunner(DiscoverRunner):
def __init__(self, option=None, **kwargs):
super().__init__(**kwargs)
print("Passed option: {}".format(option))
self.option = option
@classmethod
def add_arguments(cls, parser):
DiscoverRunner.add_arguments(parser)
parser.add_argument('-o', '--option', help='Example option')
def setup_test_environment(self, **kwargs):
super(TestRunner, self).setup_test_environment(**kwargs)
settings.TEST_SETTINGS = {
'option': self.option,
}
在测试中,您可以简单地通过设置访问该选项
from django.test import TestCase
from django.conf import settings
class MyTestCase(TestCase):
def test_something(self):
if settings.TEST_SETTINGS['option']:
print("Do stuff")
Django 为此有一个内置机制,
tags
。使用这样的东西:
from django.test import tag
class Foo(...):
@tag('slow')
def test_slow_something(...):
...
执行慢速测试:
./manage.py test --tag=slow
了解更多信息https://docs.djangoproject.com/en/4.2/topics/testing/tools/#topics-tagging-tests