使用典型的测试目录结构运行unittest

对于一个简单的Python模块来说,非常常见的目录结构似乎是将单元测试分离到它们自己的test目录中:

new_project/antigravity/antigravity.pytest/test_antigravity.pysetup.pyetc.

我的问题只是实际运行测试的通常方法是什么?我怀疑这对除了我之外的每个人来说都是显而易见的,但是你不能只是从测试目录运行python test_antigravity.py,因为它的import antigravity会失败,因为模块不在路径上。

我知道我可以修改PYTHONPATH和其他搜索路径相关的技巧,但我不相信这是最简单的方法——如果你是开发人员,这很好,但如果你的用户只是想检查测试是否通过,期望他们使用是不现实的。

另一种选择是将测试文件复制到另一个目录中,但这似乎有点愚蠢,并且忽略了将它们放在单独的目录中开始的意义。

所以,如果你刚刚将源代码下载到我的新项目中,你将如何运行单元测试?我更喜欢一个让我对我的用户说:“运行单元测试做X。”

355810 次浏览

使用setup.py develop使您的工作目录成为已安装的Python环境的一部分,然后运行测试。

从您链接到的文章:

创建一个test_modulename.py文件把你的单元测试放进去。因为测试模块位于单独的您的代码中的目录,您可能需要添加模块的父目录到你的PYTHONPATH为了运行他们:

$ cd /path/to/googlemaps
$ export PYTHONPATH=$PYTHONPATH:/path/to/googlemaps/googlemaps
$ python test/test_googlemaps.py

最后,还有一个更受欢迎的Python单元测试框架(这很重要!)鼻子有助于简化和扩展内置unittest框架(它可以,对于例如,自动找到您的测试编码并设置您的PYTHONPATH你),但它不包括在标准Python发行版。

也许你应该看看鼻子,因为它建议?

如果您运行“pythonsetup.py开发”,那么包将在路径中。但您可能不想这样做,因为您可能会感染您的系统python安装,这就是为什么像虚拟环境扩建这样的工具存在的原因。

对您的用户来说,最简单的解决方案是提供一个可执行脚本(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及更早版本中向后移植为单元测试2)现在内置了测试发现,因此如果您想要自动测试发现,不再需要鼻子:您的用户指令可以像python -m unittest discover一样简单。

我通常会在项目目录(源目录和test共用的目录)中创建一个“运行测试”脚本来加载我的“所有测试”套件。这通常是样板代码,所以我可以在项目之间重用它。

run_tests.py:

import unittestimport test.all_teststestSuite = test.all_tests.create_test_suite()text_runner = unittest.TextTestRunner().run(testSuite)

测试/all_tests.py(从如何在目录中运行所有Python单元测试?

import globimport unittest
def create_test_suite():test_file_strings = glob.glob('test/test_*.py')module_strings = ['test.'+str[5:len(str)-3] for str in test_file_strings]suites = [unittest.defaultTestLoader.loadTestsFromName(name) \for name in module_strings]testSuite = unittest.TestSuite(suites)return testSuite

使用此设置,您确实可以在测试模块中仅include antigravity。缺点是您需要更多支持代码来执行特定测试……我每次都运行它们。

我在使用单独的单元测试文件夹时遇到了同样的问题。从上面提到的建议中,我将绝对源路径添加到sys.path

以下解决方案的好处是,可以运行文件test/test_yourmodule.py而无需首先更改测试目录:

import sys, ostestdir = os.path.dirname(__file__)srcdir = '../antigravity'sys.path.insert(0, os.path.abspath(os.path.join(testdir, srcdir)))
import antigravityimport unittest

你真的应该使用pip工具。

使用pip install -e .在开发模式下安装您的包。这是一个非常好的做法,由pytest推荐(请参阅他们的良好做法留档,您还可以在其中找到两个项目布局)。

在我看来,最好的解决方案是使用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 packageimport antigravity
# import the antigravity modulefrom antigravity import antigravity
# or an object inside the antigravity modulefrom 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模块。

可以使用运行选定或所有测试的包装器。

例如:

./run_tests antigravity/*.py

或者要递归运行所有测试,请使用全球化tests/**/*.py)(由shopt -s globstar启用)。

包装器基本上可以使用argparse来解析参数,例如:

parser = argparse.ArgumentParser()parser.add_argument('files', nargs='*')

然后加载所有测试:

for filename in args.files:exec(open(filename).read())

然后将它们添加到您的测试套件中(使用inspect):

alltests = unittest.TestSuite()for name, obj in inspect.getmembers(sys.modules[__name__]):if inspect.isclass(obj) and name.startswith("FooTest"):alltests.addTest(unittest.makeSuite(obj))

并运行它们:

result = unittest.TextTestRunner(verbosity=2).run(alltests)

查看这个示例了解更多详细信息。

另见:如何在目录中运行所有Python单元测试?

如果您使用VS Code并且您的测试与您的项目位于同一级别,那么运行和调试您的代码就无法开箱即用。您可以做的是更改您的launch.json文件:

{"version": "0.2.0","configurations": [{"name": "Python","type": "python","request": "launch","stopOnEntry": false,"pythonPath": "${config:python.pythonPath}","program": "${file}","cwd": "${workspaceRoot}","env": {},"envFile": "${workspaceRoot}/.env","debugOptions": ["WaitOnAbnormalExit","WaitOnNormalExit","RedirectOutput"]}]}

这里的关键行是envFile

"envFile": "${workspaceRoot}/.env",

在项目的根目录中添加. env文件

在您的. env文件中添加项目根目录的路径。这将暂时添加

PYTHON PATH=C:\您的\PYTHON\项目\ROOT_DIRECTORY

您的项目的路径,您将能够使用VS Code中的调试单元测试

Python unittest模块的解决方案/示例

鉴于以下项目结构:

ProjectName├── project_name|    ├── models|    |    └── thing_1.py|    └── __main__.py└── test├── models|    └── test_thing_1.py└── __main__.py

您可以使用python project_name从根目录运行您的项目,它调用ProjectName/project_name/__main__.py


要使用python test运行测试,有效地运行ProjectName/test/__main__.py,您需要执行以下操作:

1)通过添加__init__.py文件将test/models目录转换为包。这使得子目录中的测试用例可以从父test目录访问。

# ProjectName/test/models/__init__.py
from .test_thing_1 import Thing1TestCase

2)修改test/__main__.py中的系统路径以包含project_name目录。

# ProjectName/test/__main__.py
import sysimport unittest
sys.path.append('../project_name')
loader = unittest.TestLoader()testSuite = loader.discover('test')testRunner = unittest.TextTestRunner(verbosity=2)testRunner.run(testSuite)

现在您可以在测试中成功地从project_name导入内容。

# ProjectName/test/models/test_thing_1.py
import unittestfrom project_name.models import Thing1  # this doesn't work without 'sys.path.append' per step 2 above
class Thing1TestCase(unittest.TestCase):
def test_thing_1_init(self):thing_id = 'ABC'thing1 = Thing1(thing_id)self.assertEqual(thing_id, thing.id)

以下是我的项目结构:

ProjectFolder:- project:- __init__.py- item.py- tests:- test_item.py

我发现最好在setUp()方法中导入:

import unittestimport sys
class ItemTest(unittest.TestCase):
def setUp(self):sys.path.insert(0, "../project")from project import item# further setup using this import
def test_item_props(self):# do my assertions
if __name__ == "__main__":unittest.main()

实际运行测试的通常方法是什么

我使用Python 3.6.2

cd new_project
pytest test/test_antigravity.py

安装pytestsudo pip install pytest

我没有设置任何路径变量,我的导入也没有使用相同的“测试”项目结构失败。

我评论了这个东西:if __name__ == '__main__'像这样:

test_antigravity.py

import antigravity
class TestAntigravity(unittest.TestCase):
def test_something(self):
# ... test stuff here

# if __name__ == '__main__':##     if __package__ is None:##         import something#         sys.path.append(path.dirname(path.dirname(path.abspath(__file__))))#         from .. import antigravity##     else:##         from .. import antigravity##     unittest.main()

我注意到,如果您从“src”目录运行unittest命令行界面,则无需修改即可正确导入工作。

python -m unittest discover -s ../test

如果您想将其放入项目目录中的批处理文件中,您可以这样做:

setlocal & cd src & python -m unittest discover -s ../test

这个BASH脚本将从文件系统中的任何地方执行python unittest测试目录,无论您在哪个工作目录中。

这在停留在./src./example工作目录中并且需要快速单元测试时很有用:

#!/bin/bash
this_program="$0"dirname="`dirname $this_program`"readlink="`readlink -e $dirname`"
python -m unittest discover -s "$readlink"/test -v

在生产过程中,不需要test/__init__.py文件来负担您的包/内存开销。

python3+

添加到@Pierre

使用unittest目录结构,如下所示:

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

要运行测试模块test_antigravity.py

$ cd new_project$ python -m unittest test.test_antigravity

或者一个TestCase

$ python -m unittest test.test_antigravity.GravityTestCase

强制性不要忘记__init__.py,即使是空的,否则将无法工作。

如果没有一些巫术,您就无法从父目录导入。这是至少适用于Python 3.6的另一种方法。

首先,有一个包含以下内容的文件测试/context.py:

import sysimport ossys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))

然后在文件test/test_antigravity.py中导入以下内容:

import unittesttry:import contextexcept ModuleNotFoundError:import test.contextimport antigravity

请注意,使用这个try除外子句的原因是

  • 使用“pythontest_antigravity.py”运行时进口test.context失败
  • 使用new_project目录中的“python-m unittest”运行时,导入上下文失败。

有了这个诡计,他们俩都工作。

现在您可以使用以下命令在test目录中运行所有测试文件:

$ pwd/projects/new_project$ python -m unittest

或者运行一个单独的测试文件:

$ cd test$ python test_antigravity

好吧,这并不比test_antigravity.py内context.py的内容漂亮多少,但也许有点。欢迎提出建议。

如果您的测试目录中有多个目录,那么您必须向每个目录添加一个__init__.py文件。

/home/johndoe/snakeoil└── test├── __init__.py└── frontend└── __init__.py└── test_foo.py└── backend└── __init__.py└── test_bar.py

然后要一次运行每个测试,请运行:

python -m unittest discover -s /home/johndoe/snakeoil/test -t /home/johndoe/snakeoil

来源:python -m unittest -h

  -s START, --start-directory STARTDirectory to start discovery ('.' default)-t TOP, --top-level-directory TOPTop level directory of project (defaults to startdirectory)

如果您正在寻找仅限命令行的解决方案:

基于以下目录结构(用专用源目录概括):

new_project/src/antigravity.pytest/test_antigravity.py

windows:(在new_project中)

$ set PYTHONPATH=%PYTHONPATH%;%cd%\src$ python -m unittest discover -s test

如果您想在批处理for循环中使用它,请参阅这个问题

Linux:(在new_project中)

$ export PYTHONPATH=$PYTHONPATH:$(pwd)/src  [I think - please edit this answer if you are a Linux user and you know this]$ python -m unittest discover -s test

使用这种方法,还可以在必要时向PYTHONPATH添加更多目录。

这种方式将允许您从任何您想要的地方运行测试脚本,而无需从命令行混淆系统变量。

这会将主项目文件夹添加到python路径中,找到的位置相对于脚本本身,而不是相对于当前工作目录。

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

将其添加到所有测试脚本的顶部。这将把主项目文件夹添加到系统路径,因此从那里工作的任何模块导入现在都可以工作。从哪里运行测试并不重要。

显然,您可以更改project_path_hack文件以匹配主项目文件夹位置。

我有同样的问题已经很久了。我最近选择的是以下目录结构:

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

在test文件夹的__init__.py脚本中,我写了以下内容:

import osimport sysPROJECT_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之所以重要,不仅是因为它运行的命令,还因为它从哪里来。如果你在测试中cd并执行python -m unittest discover .,它将无法工作,因为unit_tests中的init脚本调用os.getcwd(),这将指向不正确的绝对路径(该路径将附加到sys.path并且你将丢失源文件夹)。脚本会运行,因为发现找到所有测试,但它们不会正常运行。所以Makefile存在是为了避免记住这个问题。

我真的很喜欢这种方法,因为我不必碰我的src文件夹、单元测试或环境变量,一切都运行得很顺利。

适用于*nix系统(macOS、Linux)的简单解决方案;可能还有Windows上的Git bash。

PYTHONPATH=$PWD python test/test_antigravity.py

print语句很容易工作,不像pytest test/test_antigravity.py。“脚本”的完美方式,但不是真正的单元测试。

当然,我想做一个适当的自动化测试,我会考虑pytest与适当的设置。

unittest在您的项目中有setup.py文件。尝试:

python3 setup.py build

python3 setup.py develop --user

做配置路径的工作等等。试试看!

使用cwd作为根项目目录(在您的情况下为new_project),您可以在任何目录中运行以下命令而无需__init__.py

python -m unittest discover -s test

但是你需要test_antigravity.py中的import作为:

from antigravity import antigravity.your_object

而不是:

import antigravity.your_object

如果你不喜欢from antigravity,你可能会喜欢Alan L的回答