我想为Django manage.py命令编写一个单元测试,该命令对数据库表进行后端操作。如何从代码中直接调用管理命令?
我不想在操作系统的shell上从tests.py执行命令,因为我不能使用使用manage.py测试设置的测试环境(测试数据库,测试虚拟电子邮件发件箱等…)
我想为Django manage.py命令编写一个单元测试,该命令对数据库表进行后端操作。如何从代码中直接调用管理命令?
我不想在操作系统的shell上从tests.py执行命令,因为我不能使用使用manage.py测试设置的测试环境(测试数据库,测试虚拟电子邮件发件箱等…)
当前回答
而不是做call_command技巧,你可以通过这样做来运行你的任务:
from myapp.management.commands import my_management_task
cmd = my_management_task.Command()
opts = {} # kwargs for your command -- lets you override stuff for testing...
cmd.handle_noargs(**opts)
其他回答
测试这些东西的最佳方法是将所需的功能从命令本身提取到独立的函数或类中。它有助于从“命令执行的东西”中抽象出来,并在没有额外需求的情况下编写测试。
但是如果你由于某种原因不能解耦逻辑形式的命令,你可以使用call_command方法从任何代码调用它,就像这样:
from django.core.management import call_command
call_command('my_command', 'foo', bar='baz')
而不是做call_command技巧,你可以通过这样做来运行你的任务:
from myapp.management.commands import my_management_task
cmd = my_management_task.Command()
opts = {} # kwargs for your command -- lets you override stuff for testing...
cmd.handle_noargs(**opts)
以下代码:
from django.core.management import call_command
call_command('collectstatic', verbosity=3, interactive=False)
call_command('migrate', 'myapp', verbosity=3, interactive=False)
...等于在终端输入以下命令:
$ ./manage.py collectstatic --noinput -v 3
$ ./manage.py migrate myapp --noinput -v 3
参见从django文档运行管理命令。
使用灵活的参数和捕获的输出运行manage命令的高级方法
argv = self.build_argv(short_dict=kwargs)
cmd = self.run_manage_command_raw(YourManageCommandClass, argv=argv)
# Output is saved cmd.stdout.getvalue() / cmd.stderr.getvalue()
向基本Test类中添加代码
@classmethod
def build_argv(cls, *positional, short_names=None, long_names=None, short_dict=None, **long_dict):
"""
Build argv list which can be provided for manage command "run_from_argv"
1) positional will be passed first as is
2) short_names with be passed after with one dash (-) prefix
3) long_names with be passed after with one tow dashes (--) prefix
4) short_dict with be passed after with one dash (-) prefix key and next item as value
5) long_dict with be passed after with two dashes (--) prefix key and next item as value
"""
argv = [__file__, None] + list(positional)[:]
for name in short_names or []:
argv.append(f'-{name}')
for name in long_names or []:
argv.append(f'--{name}')
for name, value in (short_dict or {}).items():
argv.append(f'-{name}')
argv.append(str(value))
for name, value in long_dict.items():
argv.append(f'--{name}')
argv.append(str(value))
return argv
@classmethod
def run_manage_command_raw(cls, cmd_class, argv):
"""run any manage.py command as python object"""
command = cmd_class(stdout=io.StringIO(), stderr=io.StringIO())
with mock.patch('django.core.management.base.connections.close_all'):
# patch to prevent closing db connecction
command.run_from_argv(argv)
return command
关于call_command的Django文档没有提到out必须重定向到sys.stdout。示例代码应该如下:
from django.core.management import call_command
from django.test import TestCase
from django.utils.six import StringIO
import sys
class ClosepollTest(TestCase):
def test_command_output(self):
out = StringIO()
sys.stdout = out
call_command('closepoll', stdout=out)
self.assertIn('Expected output', out.getvalue())