检测Django测试模式

83
我正在编写一个可重用的Django应用程序,需要确保仅在应用程序处于测试模式时才同步其模型。我尝试使用自定义的DjangoTestRunner,但是没有找到如何执行此操作的示例(文档仅显示如何定义自定义测试运行程序)。
那么,有人知道如何做吗?
编辑:
这是我的方法:
#in settings.py
import sys
TEST = 'test' in sys.argv

希望能帮到你。


4
免责声明:我在这里是因为我有同样的需求。然而,我想指出的是,在测试运行时让您的代码表现不同是非常不可取的。尽可能地,您希望以实际工作方式测试代码。 - brianmearns
可能是重复的问题:django - 如何检测测试环境 - Amir Ali Akbari
8个回答

92

45
今天我采用了类似的方法:`TEST = 'test' in sys.argv`产生相同的效果,但更易于阅读 :-) - Herberth Amaral
4
@jjmaestro 不写 TESTING = sys.argv[1] == 'test' 有什么原因吗?我认为这完全相同且更简单易懂。 - glarrain
10
使用切片(如 [1:2])意味着如果 sys.argv 的元素少于两个,您不会收到索引越界错误。请注意,翻译过程中不得添加解释或其他内容。 - brianmearns
1
@HerberthAmaral,您在第一条评论中的建议应该成为一个独立的答案。 - Jorge Leitao
5
当其他测试程序运行时,比如django jenkins,会导致此程序无法正常工作。 - dalore
显示剩余3条评论

58

所选答案是一个巨大的hack. :)

一个不那么巨大的hack是创建你自己的TestSuiteRunner子类并更改设置或进行其他必要的操作以供应用程序的其余部分使用。您可以在设置中指定测试运行程序:

TEST_RUNNER = 'your.project.MyTestSuiteRunner'
一般来说,你不想这样做,但如果你确实需要,它可以起作用。

一般而言不建议这么做,但如果你确实需要,这个方法是可行的。

from django.conf import settings
from django.test.simple import DjangoTestSuiteRunner

class MyTestSuiteRunner(DjangoTestSuiteRunner):
    def __init__(self, *args, **kwargs):
        settings.IM_IN_TEST_MODE = True
        super(MyTestSuiteRunner, self).__init__(*args, **kwargs)

注意:自 Django 1.8 版本开始,DjangoTestSuiteRunner 已被弃用。 您应该使用 DiscoverRunner 替代:

from django.conf import settings
from django.test.runner import DiscoverRunner


class MyTestSuiteRunner(DiscoverRunner):
    def __init__(self, *args, **kwargs):
        settings.IM_IN_TEST_MODE = True
        super(MyTestSuiteRunner, self).__init__(*args, **kwargs)

那应该可以解决问题,但我更喜欢简单的解决方案 :-) - Herberth Amaral
5
我喜欢这种方法,但请注意,此代码将在导入其他代码后运行。因此,如果您需要在模块级别的代码中检测条件,则该方法不起作用。 - Kevin Christopher Henry
9
Django文档强烈反对修改django.conf.settings,请参考:https://docs.djangoproject.com/en/1.11/topics/settings/#altering-settings-at-runtime。 - David Avsajanishvili
4
选定的答案是一种巨大的黑客攻击,但这个也是! - user31415629
1
这是我在生产代码中发现的一个巨大的漏洞,但我认为它对于不同的Python调用方式、测试调用方式以及将manage.py test替换为pytest等其他方式并不特别健壮。 - Alper

24

我不太确定你的使用情况,但我看到一种检测测试套件运行的方法是检查django.core.mail是否具有outbox属性,例如:

from django.core import mail

if hasattr(mail, 'outbox'):
    # We are in test mode!
    pass
else:
    # Not in test mode...
    pass

这个属性是由Django测试运行程序在setup_test_environment中添加的,并在teardown_test_environment中删除。你可以在这里检查源代码: https://code.djangoproject.com/browser/django/trunk/django/test/utils.py

编辑:如果您想为测试定义模型,那么您应该查看Django票号#7835,特别是以下部分:评论#24。其中部分如下:

显然,您可以直接在tests.py中定义模型。 Syncdb永远不会导入tests.py,因此这些模型不会同步到正常数据库,但是它们将被同步到测试数据库,并且可以在测试中使用。


这不是通常的方式,但我认为它会起作用。我在Django核心内看到过这段代码,但我认为它可能有一个更“具体”的解决方案。关于使用情况:我编写的应用程序使用了一些模型和表单字段的变化。因此,我需要仅在测试模式下使模型正常工作,以便有很好的测试覆盖率;该应用程序是开源的,您可以在此处查看:https://github.com/herberthamaral/django-jqgrid - Herberth Amaral
1
啊,现在我明白你在问什么了。我已更新我的回答以包括处理仅用于测试的模型。我在项目的测试套件中有一个类似的用例:https://bitbucket.org/mlavin/django-selectable - Mark Lavin
非常感谢,马克。这是一个非常有用的资源,我认为它应该放在Django的文档中。 - Herberth Amaral
这个解决方案比被接受的答案还要糟糕。只有在您不更改电子邮件后端的情况下才能正常工作。 - lullis
3
我同意这有点像一个hack,但是"它只在您不更改电子邮件后端的情况下才能工作"并不准确。Django的测试套件环境总是会将电子邮件后端更改为使用内存后端,这就是为什么这个方法可行的原因。请参考https://docs.djangoproject.com/en/stable/topics/testing/tools/#email-services。 - Mark Lavin

15

我正在使用 settings.py 的覆盖方式。我有一个包含大部分设置的全局 settings.py 文件,然后我为其提供了一些覆盖。每个设置文件都以以下方式开始:

from myproject.settings import settings

然后继续覆盖一些设置。

  • prod_settings.py - 生产环境设置(例如,覆盖DEBUG=False)
  • dev_settings.py - 开发环境设置(例如,更多日志记录)
  • test_settings.py

然后我可以在基本的settings.py中定义UNIT_TESTS=False,并在test_settings.py中将其重写为UNIT_TESTS=True。

然后每当我运行命令时,我需要决定针对哪些设置运行(例如 DJANGO_SETTINGS_MODULE=myproject.test_settings ./manage.py test)。我喜欢这种清晰易懂的方式。


2
好的,清晰明了,这也是我做事的方式。你也可以使用 DJANGO_ENV=test ./manage.py test 进行测试,并在代码中使用 sys.env 获取。 - Nick Sweeting

3
好的,您可以简单地使用环境变量来实现这一点:
```html

好的,您可以简单地使用环境变量来实现这一点:

```
export MYAPP_TEST=1 && python manage.py test

然后在您的settings.py文件中:

import os

TEST = os.environ.get('MYAPP_TEST')

if TEST:
    # Do something

1
虽然这个页面上有很多好的答案,但我认为还有另一种方法来检查您的项目是否处于测试模式(如果在某些情况下无法使用 sys.argv[1:2] == ["test"])。
正如您所知道的,当您处于测试模式时,DATABASE 名称将更改为类似 "test_*" 的内容(DATABASE 默认名称将以 test 为前缀),或者您可以简单地打印它以找到运行测试时的数据库名称。由于我在我的一个项目中使用了 pytest,因此我无法使用
sys.argv[1:2] == ["test"]

因为这个参数原本并不存在。所以我使用此参数来检查是否处于测试环境中(您知道你的DATABASE名称是否已以test作为前缀,如果不是,则将test更改为DATABASE名称的前缀部分):

1)除了设置模块外的任何地方

from django.conf import settings

TESTING_MODE = "test" in settings.DATABASES["default"]["NAME"]

2) 在设置模块内部

TESTING_MODE = "test" in DATABASES["default"]["NAME"]

或者

TESTING_MODE = DATABASES["default"]["NAME"].startswith("test")  # for more strict checks

如果这个解决方案可行,您甚至不需要在settings.py模块中导入sys来检查此模式。

不错的想法,但是对于某些数据库(例如Oracle),应该使用USER而不是name,因为“NAME”可能是本地连接字符串。 - AdamC
1
我喜欢这个解决方案,真的很喜欢。然而,Django用户可以更改测试数据库的名称。参考:https://dev59.com/XG445IYBdhLWcg3wia6W ,当然只需要进行小的更改就可以解决这个问题。我也尝试了IS_TESTING = 'test' in sys.argv的解决方案,这个方案更易于阅读。这是一个非常好的开箱即用的解决方案! - YetAnotherDuck

0
这是我在Django 4.1.5中使用的技巧:IN_TEST = 'testserver' in settings.ALLOWED_HOSTS


0

我一直在使用Django 基于类的设置。我使用该软件包中的“switcher”并为testing=True加载不同的配置/类:

switcher.register(TestingSettings, testing=True)

在我的配置中,我有一个BaseSettingsProductionSettingsDevelopmentSettingsTestingSettings等。 它们按需彼此子类化。 在BaseSettings中,我有IS_TESTING=False,然后在TestingSettings中,我将其设置为True
如果保持类继承的清晰性,它会很好地工作。 但我发现它比Django开发人员通常使用的import *方法更有效。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接