如何将Django单元测试分散到多个文件中?


126
  • 我有一个python-django应用程序
  • 我正在使用单元测试框架
  • 测试被安排在模块目录中的“ tests.py”文件中
  • 我正在通过运行测试 ./manage.py test app

现在..

  • tests.py文件变得越来越大/复杂/混乱
  • 我想分解tests.py成较小的测试集合...

怎么样?

Answers:


47

行为在Django 1.6中已更改,因此不再需要创建包。只要命名您的文件test*.py

来自Django 1.7文档

在运行测试时,测试实用程序的默认行为是在名称以test开头的任何文件中查找所有测试用例(即unittest.TestCase的子类),并自动从这些测试用例中构建测试套件,并运行该套件。

根据Django 1.6文档

测试发现基于unittest模块的内置测试发现。默认情况下,它将在当前工作目录下的任何名为“ test * .py”的文件中发现测试。

先前的行为,来自Django 1.5文档

在运行测试时,测试实用程序的默认行为是在models.py和tests.py中找到所有测试用例(即unittest.TestCase的子类),并根据这些测试用例自动构建测试套件,并运行该套件。

定义模块测试套件的另一种方法是:如果在models.py或tests.py中定义了一个名为suite()的函数,则Django测试运行器将使用该函数为该模块构造测试套件。这遵循建议的单元测试组织。有关如何构造复杂的测试套件的更多详细信息,请参见Python文档。


4
在django 2.6中并没有发现任何东西……
LtWorf

2
目前使用Django 1.10,我希望把我所有的test*.py文件名为文件夹中tests,以保持清洁的文件夹-这是可能的,但你必须运行./manage.py test app.tests和所有相关的进口需要升了一级(from .models变得from ..models)。
Scott Stevens

123

请注意,这种做法无异于Django的1.6不再有效,看到这个帖子

您可以在内部创建tests文件夹___init___.py(以使其成为一个包)。然后在其中添加拆分测试.py文件,并将所有文件导入___init___.py

即:替换 test.py用外观和行为类似于文件的模块文件:

tests在相关应用下创建目录

应用程式
app \ models.py
app \ views.py
应用\测试
app \ tests \ __ init__.py
app \ tests \ bananas.py
app \ tests \ apples.py

将子模块导入到app\tests\__init__.py

from bananas import *
from apples import *

现在,您可以使用./manage.py,就像它们都在一个文件中一样:

./manage.py test app.some_test_in_bananas

1
h 您的意思是在我正在测试的应用程序下创建一个“测试”模块;不是称为测试的新应用程序。我明白了 太棒了 谢谢!
约翰·米

@John:我再也听不懂我的回答了!:-)但是您完全正确,即使它是正确的,它也太模糊了-您的示例很清楚,这与我的原始措辞背道而驰。
TomaszZieliński

2
@Tomasz ..您的话仍然存在-完整无缺。自从您使我走上正确的道路以来,我就将其充实了一点。
约翰·米

@John:如果那是你的意思,我一点也不生气:)看到我自己的回答有点不同很有趣
TomaszZieliński

4
@jMyles,如果用“常规django测试运行程序”来表示,python manage.py test myapp那么实际上这个答案确实有效。(刚刚尝试过)
柯克·沃尔

27

托马斯(Tomasz)所说的答案是正确的。但是,确保导入__init__.py的文件与您的文件结构匹配可能会很麻烦。

自动检测文件夹中的所有测试,可以在__init__.py以下位置添加:

import unittest

def suite():   
    return unittest.TestLoader().discover("appname.tests", pattern="*.py")

这将允许您运行,./manage.py test appname但不能处理运行特定的测试。为此,您可以使用以下代码(也在中__init__.py):

import pkgutil
import unittest

for loader, module_name, is_pkg in pkgutil.walk_packages(__path__):
    module = loader.find_module(module_name).load_module(module_name)
    for name in dir(module):
        obj = getattr(module, name)
        if isinstance(obj, type) and issubclass(obj, unittest.case.TestCase):
            exec ('%s = obj' % obj.__name__)

现在,您可以通过运行所有测试,也可以通过运行manage.py test app特定测试manage.py test app.TestApples


您将第二块放在哪里?
rh0dium13年

这两个部分都进入__init__.py
Bryce Drennan

请注意,如果您的任何测试软件包名称与在测试运行期间导入的顶级模块名称一致,则pkgutil片段将导致导入失败,因为测试被添加为sys.modules[packagename]。一个快速的解决方法是对del导致上述问题的任何问题进行解决。(或者您可以重命名文件夹;))
Paul Fenney 2013年

很棒,但是我遇到了一个错误,在该错误中,当运行应用程序级别测试(python manage.py test appName)时,第二段代码将引发错误,指出该错误__path__不可用。我通过将第二个代码段包装在if '__path__' in locals():支票中来避免了这种情况,从而达到了目的。感谢你的回答!
alukach 2013年

1
+1这也保证了初始化文件遵循共同的编码标准,即不具备*或者未使用的导入
马丁B.

13

只需使您的目录结构像这样:

myapp/
    __init__.py
    tests/
        __init__.py
        test_one.py
        test_two.py
        ...
    ...

并且python manage.py test myapp将按预期工作。



2

无需在init中编写任何代码。只需在您的应用程序中创建一个子目录。唯一的要求就是不称其为测试* 例如

app/
app/__init_.py
app/serializers.py
app/testing/
app/testing/__init__.py
app/testing/tests_serializers.py

1
为什么不能将其以“测试”开头呢?
Serp C

我正在Django 1.11.4中使用此答案。使用它的原因:(1)文件“ app / testing / __ init__.py”仍然为空,并且(2)该命令仍为基本的“ python manage.py test app”
rprasad

2

使用Django 2.2,一个简单且相当不错的解决方案是test在应用程序内创建一个文件夹,您可以将相关test_...py文件放入其中,只需将其添加__init__.py到该test文件夹中即可。


1

如果您的设置较为复杂,或者不想使用from ... import *-type语句,则可以定义一个suite在tests.py(或tests / __ init__.py)中调用的函数,该函数返回的实例unittest.TestSuite


0

我认为./manage.py test只是运行所有测试技巧(在Django> = 1.7中)。

如果你的组织测试是关于分组采樱桃谬误和你的风扇nose使用Django的鼻子

python manage.py test another.test:TestCase.test_method

如果您知道鼻子,那么您就会知道如何对所有文件“通配”更好。

聚苯乙烯

这只是一个更好的做法。希望有帮助。答案是从这里借来的:当您的应用程序具有测试目录时,在Django中运行特定的测试用例


0

我有两个文件。一个是tests.py,另一个是test_api.py。我可以如下单独运行它们。

   manage.py test companies.tests
   manage.py test companies.test_api

有关文件命名约定,请参阅@osa的响应。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.