【问题标题】:Django manage.py : Is it possible to pass command line argument (for unit testing)Django manage.py:是否可以传递命令行参数(用于单元测试)
【发布时间】:2015-03-29 13:45:14
【问题描述】:

是否可以将命令行参数传递给 Django 的 manage.py 脚本,专门用于单元测试?即,如果我做类似的事情

manage.py test myapp -a do_this

我可以在单元测试的setUp函数中接收do_this的值吗?

P.S. @Martin 询问在测试中使用命令行参数的理由:

  • 一些广泛的测试需要花费大量时间,并且不需要在每次提交之前运行。我想让它们成为可选的。

  • 我的测试用例打印的偶尔调试消息应该是可选的

  • 有时我只是想让测试变得疯狂并尝试更多的数据排列。

使用命令行选项,以上所有内容都会非常方便。偶尔测试可能会更广泛或更冗长,否则会很快。

【问题讨论】:

  • 不管是或否,将参数添加到单元测试本身不是更有意义吗?这确实是单元测试的主要用例之一 - 检查不同的边缘情况等。如果您为每种情况定义不同的测试函数,您将能够像 manage.py test myapp.mytestcase 一样单独调用它们
  • @Martin Fair 点。我已经在答案中阐述了我的想法(P.S.)
  • 好的,那么我将创建testBasictestCrazy 等函数,并在提交时运行您需要的任何测试。我完全同意在每次提交时运行一个大项目的整个测试套件可能会很烦人 - 这就是为什么你应该为提交创建一个新测试或者只选择与提交相关的测试。
  • @user4150760 - “manage.py test myapp.mytestcase” 对我来说还不够。有时我想在特定的客户端 ID 上进行测试。然后呢?

标签: django django-testing django-manage.py


【解决方案1】:

跟进@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")

【讨论】:

    【解决方案2】:

    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

    此示例仅打印选项值,但您需要以某种方式将其传递给您的测试用例。我找不到有关如何将选项传递给 unittest 测试用例的任何快速信息,但您可能只使用全局(模块级别)变量或类变量(这不是那么可重入和优雅,但很容易和工作)。

    【讨论】:

      【解决方案3】:

      我自己也遇到了这个问题,想避免在命令行设置环境变量。环境变量确实有效,但很难跟踪哪些变量会产生影响,并且没有错误消息让您知道是否输入了错误。

      为了解决这个问题,我使用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 文档中有一个很好的tutorial

      【讨论】:

        【解决方案4】:

        我在我的项目中使用环境变量解决方法(仅适用于类 unix 的 shell)

        berry$ myvar=myval ./manage.py test 
        

        在你的模块中使用

        读取这个值
        os.environ.get('myvar')
        

        【讨论】:

          【解决方案5】:

          作为manage.py test -a do_this 的替代方法,您可以使用特定的settings file

          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:
                  ....
          

          如果您需要真正的动态选项,也许您可​​以在test_settings.py 中使用sys.argv,但这是一个非常肮脏的hack。

          【讨论】:

          • 感谢您提出此解决方法。你提到:you can use sys.argv in test_settings.py, but it is a really dirty hack:你能详细说明一下吗?
          • 您可以将任何参数传递给命令行:$ manage.py --settings=test_settings test some-specific-option,然后在test_settings.py 中使用类似这样的内容:if sys.argv[-1] == "something": # do stuff(毕竟您必须删除这个额外的参数:del sys.argv[-1]
          • 我没有发现任何 hacky 的地方。无论如何,我的 CI 有一个单独的设置文件,只需在其中放入另一个 var。简单胜于复杂。 :)
          猜你喜欢
          • 1970-01-01
          • 2022-11-29
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多