Python单元测试应该放在哪里?

564
如果您正在编写一个库或应用程序,单元测试文件应该放在哪里?
将测试文件与主要应用程序代码分离是很好的做法,但将它们放在应用程序根目录下的“tests”子目录中会使导入要测试的模块更加困难。
这里是否存在最佳实践?
18个回答

252

对于一个名为module.py的文件,单元测试通常应该命名为test_module.py,遵循Pythonic的命名惯例。

有几个通常接受的位置可以放置test_module.py

  1. module.py在同一目录中。
  2. ../tests/test_module.py(与代码目录相同级别)中。
  3. tests/test_module.py(代码目录下一级)中。

我更喜欢第一种方法,因为它能够简单地找到测试并导入它们。无论你使用哪种构建系统,都可以轻松配置以运行以test_开头的文件。实际上,默认的unittest用于测试发现的模式是test*.py


12
默认情况下,load_tests protocol 会搜索名称为 test*.py 的文件。此外,这个排名最高的谷歌搜索结果这个 unittest 文档 都使用 test_module.py 的格式。 - dfarrell07
7
使用foo_test.py可以在使用tab键自动补全时节省一两个按键,因为你不需要浏览以“test_”开头的一堆文件。 - juniper-
12
@juniper,按照你的思路,当你编程时,模块测试会出现在自动完成中。这可能会令人感到困惑或烦恼。 - Medeiros
21
在部署代码时,我们不希望将测试部分部署到生产环境。因此,将它们放置在一个目录'./tests/test_blah.py'中,方便在部署时轻松删除它们。同时,有些测试需要使用示例数据文件,将其放置在测试目录中非常重要,以免部署测试数据。 - Kevin J. Rice
3
@endolith,不要在生产环境中测试代码(功能测试),只在开发和QA以及可能的预发布环境中运行测试。生产环境是被锁定的,并且可以访问生产数据库等关键基础设施,测试可能会以错误的方式访问这些关键基础设施。最好将测试从生产环境中排除,除非您正在尝试执行某些非常特定的操作,例如监视服务,在这种情况下,我们正在谈论监视而不是单元测试。 - Kevin J. Rice
显示剩余3条评论

102

只有1个测试文件

如果只有1个测试文件,建议将其放在顶层目录中:

module/
    lib/
        __init__.py
        module.py
    test.py

在命令行界面下运行测试

python test.py

许多测试文件

如果有许多测试文件,请将其放在一个名为tests的文件夹中:

module/
    lib/
        __init__.py
        module.py
    tests/
        test_module.py
        test_module_function.py
# test_module.py

import unittest
from lib import module

class TestModule(unittest.TestCase):
    def test_module(self):
        pass

if __name__ == '__main__':
    unittest.main()

在CLI中运行测试

# In top-level /module/ folder
python -m tests.test_module
python -m tests.test_module_function

使用unittest discovery

unittest discovery 可以找到包文件夹中的所有测试。

tests/ 文件夹中创建一个 __init__.py 文件。

module/
    lib/
        __init__.py
        module.py
    tests/
        __init__.py
        test_module.py
        test_module_function.py

在CLI中运行测试

# In top-level /module/ folder

# -s, --start-directory (default current directory)
# -p, --pattern (default test*.py)

python -m unittest discover

参考资料

单元测试框架


2
我有你在“多个测试文件”下概述的相同结构,但是当我尝试使用from lib import module时,我会收到一个错误:ImportError: No module named 'lib' - Shervin Rad
2
@ShervinRad 你需要在 module 文件夹中运行,而不是 libtests 文件夹,Python 使用相对路径进行导入。 - Steely Wing

52

常见的做法是将测试目录放在与您的模块/包相同的父目录中。因此,如果您的模块名为foo.py,则目录布局如下:

parent_dir/
  foo.py
  tests/

当然,没有一种方法是唯一的。您还可以创建一个名为 tests 的子目录,并使用 绝对导入 导入模块。

无论您将测试放在哪里,我建议您使用 nose 运行它们。 Nose 会在您的目录中搜索测试。这样,您就可以根据组织需要在任何地方放置测试了。


17
我想这么做,但我无法使其起作用。为了运行测试,我在父目录中,并输入以下命令:“python tests\foo_test.py”,并在foo_test.py文件中添加代码: “from ..foo import this, that, theother”。但是,运行失败并显示:“ValueError: Attempted relative import in non-package”。父目录和tests文件夹中均有__init__.py文件,因此我不确定它们为什么不被视为packages。我怀疑这是因为从命令行运行的顶层脚本即使在带有__init__.py的目录中也不能被认为是(部分)package的原因。那么,我该如何运行这些测试?今天我使用的是Windows操作系统。 - Jonathan Hartley
4
我发现运行单元测试的最佳方法是安装您的库/程序,然后使用nose运行单元测试。我建议使用virtualenv和virtualenvwrapper来使这个过程更加容易。 - Cristian
4
谢谢Casey - 但是我所有相关目录下都有一个__init__.py文件。我不知道我做错了什么,但我在我的所有Python项目中都遇到了这个问题,我不明白为什么其他人没有遇到。哦,亲爱的。 - Jonathan Hartley
9
对于我的问题,使用Python2.6或更新版本的一种解决方案是从项目根目录运行测试,方法是:python -m project.package.tests.module_tests(而不是python project/package/tests/module_tests.py)。这将把测试模块的目录添加到路径中,因此测试可以进行相对导入以获取被测试的模块的父目录。 - Jonathan Hartley
nosenose2处于维护模式。来自nose网站的消息:在过去的几年中,nose一直处于维护模式,并且如果没有新的个人/团队接管维护工作,它很可能会停止更新。新项目应该考虑使用Nose2py.test或普通的unittest/unittest2 - alpha_989
显示剩余3条评论

32
我们在编写生成Python程序单元测试的Pythoscope(https://pypi.org/project/pythoscope/)时,也遇到了同样的问题。在我们选择目录之前,我们向Python测试列表中的人们进行了投票,得到了许多不同的意见。最终,我们选择将“tests”目录放在与源代码相同的目录中。在该目录中,我们为父目录中的每个模块生成一个测试文件。

2
太棒了!刚刚在一些旧代码上运行了Pythoscope(很棒的标志),效果非常惊人。瞬间就能获得最佳实践,而且你可以让其他开发人员填写现在失败的测试样例。现在要把它接入到bamboo中了吗? :) - Will

26

我偶尔会查阅关于测试位置的话题,大多数建议在库代码旁边使用单独的文件夹结构,但每次争论都是相同的,不太有说服力。因此,我最终将我的测试模块放在核心模块旁边。

这样做的主要原因是:重构

当我移动文件时,测试模块也随着核心代码一起移动;如果它们在一个单独的树中,很容易丢失测试。坦白说,迟早会得到完全不同的文件夹结构,例如djangoflask和许多其他项目。如果你不介意,这也没问题。

你应该问自己的主要问题是:

我是在编写:

  • a) 可重用的库还是
  • b) 构建一个捆绑了一些半分离模块的项目?

如果是a:

一个单独的文件夹和额外的维护结构可能更适合。没有人会抱怨你的测试被部署到生产环境中。

但是,如果测试与核心文件夹混合在一起,则将测试排除在分发之外也同样容易;在setup.py中添加此代码即可:

find_packages("src", exclude=["*.tests", "*.tests.*", "tests.*", "tests"]) 

如果 b:

你可能希望像我们每个人一样编写可重用的库,但大多数情况下它们的寿命与项目的寿命绑定在一起。轻松维护您的项目应该是首要考虑的事情。

然后,如果你做得很好,你的模块适合另一个项目,它可能会被复制——而不是分叉或制作成单独的库——进入这个新项目中,并移动与其相邻的测试文件到同一文件夹结构中比在杂乱无章的测试文件夹中查找测试文件容易得多。(你可能会说,它一开始就不应该是一团糟,但让我们现实点吧)。

所以选择仍然取决于您,但我会认为,通过混合测试,您可以像使用单独文件夹时一样完成所有相同的工作,但减少了保持整洁的努力。


1
部署测试到生产环境有什么问题吗?实际上,根据我的经验,这是非常有用的:它允许用户在他们的环境中运行测试...当你收到错误报告时,你可以要求用户运行测试套件以确保一切都正常,并直接发送测试套件的热修补程序...此外,除非您在设置文件中出了问题,否则将测试放在模块.tests中并不意味着它会最终进入生产环境。 - anarcat
2
正如我在答案中所写的,我通常不会将测试分开。但是,将测试放入分发包中可能会导致执行一些在生产环境中通常不希望执行的内容(例如,某些模块级别的代码)。当然,这取决于测试的编写方式,但为了安全起见,如果您将它们关闭,没有人会因错误而运行有害的东西。我不反对在分发中包含测试,但我理解作为经验法则,这样更安全。将它们放在单独的文件夹中使得不包括它们在分发中变得非常容易。 - Janusz Skonieczny

26

我也倾向于像Jeremy Cantrell所说的那样将我的单元测试放在文件本身中,尽管我倾向于不将测试函数放在主体中,而是将所有内容都放在一个

if __name__ == '__main__':
   do tests...

块。这最终会将文档添加到文件中作为“示例代码”,以说明如何使用正在测试的Python文件。

我应该补充说,我倾向于编写非常紧凑的模块/类。如果您的模块需要大量的测试,您可以将它们放在另一个模块中,但即使如此,我仍然会添加:

if __name__ == '__main__':
   import tests.thisModule
   tests.thisModule.runtests

这样做可以让任何阅读您源代码的人知道在哪里查找测试代码。


1
"正如Jeremy Cantrell所指出的那样" 在哪里? - endolith
我喜欢这种工作方式。即使是最简单的编辑器也可以配置热键来运行您的文件,因此当您查看代码时,可以立即运行测试。不幸的是,在除Python以外的其他语言中,这可能看起来很糟糕,所以如果您在C++或Java团队中工作,您的同事可能会对此表示不满。这也无法很好地与代码覆盖率工具配合使用。 - Keeely

14

我使用一个tests/目录,并使用相对导入来导入主应用程序模块。因此,在MyApp/tests/foo.py中,可能会有:

from .. import foo

导入 MyApp.foo 模块。


11
数值错误:在非包中尝试相对导入。 - cprn

13

我建议你在GitHub上检查一些主要的Python项目并获取一些想法。

当你的代码变得更加庞大并添加更多的库时,最好在与setup.py相同的目录中创建一个测试文件夹,并为每种测试类型(单元测试、集成测试等)镜像你的项目目录结构。

例如,如果你有这样一个目录结构:

myPackage/
    myapp/
       moduleA/
          __init__.py
          module_A.py
       moduleB/
          __init__.py
          module_B.py
setup.py

添加测试文件夹后,您的目录结构将如下所示:

myPackage/
    myapp/
       moduleA/
          __init__.py
          module_A.py
       moduleB/
          __init__.py
          module_B.py
test/
   unit/
      myapp/
         moduleA/
            module_A_test.py
         moduleB/
            module_B_test.py
   integration/
          myapp/
             moduleA/
                module_A_test.py
             moduleB/
                module_B_test.py
setup.py

许多编写正确的Python包使用相同的结构。一个非常好的例子是Boto包。 请查看https://github.com/boto/boto


1
建议使用“tests”而不是“test”,因为“test”是Python内置模块。https://docs.python.org/2/library/test.html - brodul
并不总是这样.. 例如 matplotlib 将其放在 matplotlib/lib/matplotlib/tests 下 (https://github.com/matplotlib/matplotlib/tree/38be7aeaaac3691560aeadafe46722dda427ef47/lib/matplotlib/tests) ,sklearn 将其放在 scikitelearn/sklearn/tests 下 (https://github.com/scikit-learn/scikit-learn/tree/master/sklearn/tests) - alpha_989

13

根据我在Python测试框架开发方面的经验,我建议将Python单元测试放在单独的目录中。保持对称的目录结构。这样有助于仅打包核心库而不打包单元测试。下面通过一个示意图来实现。

                              <Main Package>
                               /          \
                              /            \
                            lib           tests
                            /                \
             [module1.py, module2.py,  [ut_module1.py, ut_module2.py,
              module3.py  module4.py,   ut_module3.py, ut_module.py]
              __init__.py]

通过这种方式,当你使用rpm打包这些库时,你只需要打包主要的库模块(仅此而已)。这有助于在敏捷环境下保持可维护性。


2
我意识到这种方法的一个潜在优势是测试可以作为独立应用程序开发(甚至可能进行版本控制)。当然,每个优点都有一个缺点——保持对称性基本上就像做“双重记账”,并使重构变得更加繁琐。 - Jasha
软性跟进问题:为什么敏捷环境特别适合这种方法?(即,敏捷工作流程中的哪些方面使对称目录结构如此有益?) - Jasha

13

我不认为有已经确立的“最佳实践”。

我把我的测试放在应用代码之外的另一个目录中。然后,在运行所有测试之前,我会在我的测试运行脚本中将主应用程序目录添加到sys.path中(允许您从任何地方导入模块)。这样,当我发布代码时,我永远不必将测试目录从主代码中删除,节省了时间和精力,即使只是微小的一点。


4
我随后将主应用程序目录添加到sys.path。问题在于,如果您的测试包含一些辅助模块(例如测试Web服务器),并且您需要从适当的测试中导入这些辅助模块。 - Piotr Dobrogost
这是我的代码:os.sys.path.append(os.dirname('..')) - Mattwmaster58

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