以典型测试目录结构运行单元测试
对于一个简单的Python模块,常见的目录结构是把单元测试放在一个单独的test
目录里:
new_project/
antigravity/
antigravity.py
test/
test_antigravity.py
setup.py
etc.
我想问的就是通常怎么运行这些测试呢?我觉得这对大家来说应该很明显,但你不能直接在测试目录里运行python test_antigravity.py
,因为它里面的import antigravity
会失败,因为这个模块不在路径里。
我知道可以修改PYTHONPATH或者用其他搜索路径的技巧,但我觉得这不是最简单的方法——对开发者来说可以,但不现实让用户这样做,他们只是想检查测试是否通过。
另一种选择是把测试文件复制到其他目录,但这样感觉有点傻,也失去了最开始把它们放在单独目录的意义。
所以,如果你刚下载了我新项目的源代码,你会怎么运行单元测试呢?我希望能给我的用户一个简单的指示:“要运行单元测试,请做X。”
27 个回答
给用户提供最简单的解决方案,就是提供一个可执行的脚本(比如 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
就可以了。
我很长时间以来一直遇到同样的问题。最近我选择了以下的文件夹结构:
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 的存在就是为了避免你需要记住这个问题。
我非常喜欢这种方法,因为我不需要去碰我的源代码文件夹、单元测试或者环境变量,一切都能顺利运行。
我认为最好的解决办法是使用 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
的内容。