以典型测试目录结构运行单元测试

1004 投票
27 回答
425095 浏览
提问于 2025-04-15 16:58

对于一个简单的Python模块,常见的目录结构是把单元测试放在一个单独的test目录里:

new_project/
    antigravity/
        antigravity.py
    test/
        test_antigravity.py
    setup.py
    etc.

我想问的就是通常怎么运行这些测试呢?我觉得这对大家来说应该很明显,但你不能直接在测试目录里运行python test_antigravity.py,因为它里面的import antigravity会失败,因为这个模块不在路径里。

我知道可以修改PYTHONPATH或者用其他搜索路径的技巧,但我觉得这不是最简单的方法——对开发者来说可以,但不现实让用户这样做,他们只是想检查测试是否通过。

另一种选择是把测试文件复制到其他目录,但这样感觉有点傻,也失去了最开始把它们放在单独目录的意义。

所以,如果你刚下载了我新项目的源代码,你会怎么运行单元测试呢?我希望能给我的用户一个简单的指示:“要运行单元测试,请做X。”

27 个回答

55

给用户提供最简单的解决方案,就是提供一个可执行的脚本(比如 runtests.py),这个脚本可以帮助用户搭建所需的测试环境。如果需要的话,它还可以临时把你的项目根目录添加到 sys.path 中。这样用户就不需要自己设置环境变量,像下面这样的代码在启动脚本中就可以很好地工作:

import sys, os

sys.path.insert(0, os.path.dirname(__file__))

这样,你给用户的指示可以简单到只需要输入 "python runtests.py"。

当然,如果你需要的路径确实是 os.path.dirname(__file__),那么你根本不需要把它添加到 sys.path;因为Python会自动把当前运行脚本所在的目录放在 sys.path 的最前面。所以根据你的目录结构,只要把 runtests.py 放在合适的位置,可能就足够了。

另外,Python 2.7 及以上版本的 unittest模块(在Python 2.6及更早版本中可以通过 unittest2 使用)现在已经内置了 测试发现 功能,所以如果你想要自动化测试发现,就不再需要nose了:用户只需输入 python -m unittest discover 就可以了。

66

我很长时间以来一直遇到同样的问题。最近我选择了以下的文件夹结构:

project_path
├── Makefile
├── src
│   ├── script_1.py
│   ├── script_2.py
│   └── script_3.py
└── tests
    ├── __init__.py
    ├── test_script_1.py
    ├── test_script_2.py
    └── test_script_3.py

然后在测试文件夹里的 __init__.py 脚本中,我写了以下内容:

import os
import sys
PROJECT_PATH = os.getcwd()
SOURCE_PATH = os.path.join(
    PROJECT_PATH,"src"
)
sys.path.append(SOURCE_PATH)

对于分享这个项目来说,Makefile 是超级重要的,因为它确保脚本能够正确运行。这里是我在 Makefile 中放的命令:

run_tests:
    python -m unittest discover .

Makefile 重要的不仅仅是它运行的命令,还有它是从 哪里运行的。如果你在测试文件夹里执行 python -m unittest discover .,它是不会工作的,因为单元测试中的 init 脚本会调用 os.getcwd(),这会指向错误的绝对路径(这个路径会被加到 sys.path 中,而你的源代码文件夹就会缺失)。虽然 discover 能找到所有的测试,但它们不会正确运行。所以 Makefile 的存在就是为了避免你需要记住这个问题。

我非常喜欢这种方法,因为我不需要去碰我的源代码文件夹、单元测试或者环境变量,一切都能顺利运行。

917

我认为最好的解决办法是使用 unittest命令行接口,这样它会自动把你的目录添加到 sys.path 中,你就不需要手动添加了(这个操作是在 TestLoader 类中完成的)。

比如说,如果你的目录结构是这样的:

new_project
├── antigravity.py
└── test_antigravity.py

你只需要运行:

$ cd new_project
$ python -m unittest test_antigravity

对于像你这样的目录结构:

new_project
├── antigravity
│   ├── __init__.py         # make it a package
│   └── antigravity.py
└── test
    ├── __init__.py         # also make test a package
    └── test_antigravity.py

test 包里面的测试模块中,你可以像往常一样导入 antigravity 包及其模块:

# import the package
import antigravity

# import the antigravity module
from antigravity import antigravity

# or an object inside the antigravity module
from antigravity.antigravity import my_object

运行单个测试模块:

如果你想运行一个单独的测试模块,比如 test_antigravity.py

$ cd new_project
$ python -m unittest test.test_antigravity

只需像导入它一样引用这个测试模块即可。

运行单个测试用例或测试方法:

你也可以运行一个单独的 TestCase 或者一个单独的测试方法:

$ python -m unittest test.test_antigravity.GravityTestCase
$ python -m unittest test.test_antigravity.GravityTestCase.test_method

运行所有测试:

你还可以使用 测试发现,它会自动发现并运行所有的测试,这些测试必须是命名为 test*.py 的模块或包(可以通过 -p, --pattern 选项来更改):

$ cd new_project
$ python -m unittest discover
$ # Also works without discover for Python 3
$ # as suggested by @Burrito in the comments
$ python -m unittest

这将会运行 test 包里面所有的 test*.py 模块。

这里可以找到更新的官方文档,关于 discovery 的内容。

撰写回答