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


698

即使是一个简单的Python模块,最常见的目录结构似乎也是将单元测试分成各自的test目录:

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

例如,请参见此Python项目howto

我的问题是,实际上运行测试的通常方法什么?我怀疑这对除我以外的所有人来说都是显而易见的,但是您不能仅从python test_antigravity.pytest目录运行,import antigravity因为模块不在路径上,它将失败。

我知道我可以修改PYTHONPATH和其他与搜索路径有关的技巧,但我不敢相信这是最简单的方法-如果您是开发人员,这很好,但如果用户只是想检查测试结果,就不能期望用户使用通过。

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

那么,如果您刚刚将源代码下载到我的新项目中,将如何运行单元测试?我希望有一个答案让我对用户说:“要运行单元测试,请执行X。”


5
@EMP当您需要设置搜索路径时,正确的解决方案是...设置搜索路径。您期望什么样的解决方案?
卡尔·梅耶

7
@CarlMeyer另一个更好的解决方案是使用unittest命令行界面,如下面我的答案中所述,因此您不必将目录添加到路径。
皮埃尔·

13
同样在这里。我刚刚着手为一个很小的Python项目编写我的第一个单元测试,花了几天的时间来思考一个事实,即我无法轻易地运行测试,而将源代码保存在src目录中,而将测试保存在test目录中,似乎与任何现有的测试框架。我最终会接受事物,找出一种方法。但这是一个非常令人沮丧的介绍。(而且我是Python之外的单元测试资深人士。)
Ates Goral

Answers:


655

我认为最好的解决方案是使用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*.py模块test


53
python -m unittest discovertest如果命名为,它将在目录中查找并运行测试test*.py。如果将子目录命名为tests,请使用python -m unittest discover -s tests,如果将测试文件命名为antigravity_test.py,请使用。python -m unittest discover -s tests -p '*test.py' 文件名可以使用下划线,但不能使用破折号。
Mike3d0g

10
ImportError: No module named 'test.test_antigravity'由于与unittest库的测试子模块发生冲突,因此在Python 3上对我来说失败,并显示错误。也许专家可以确认答案子目录名称并将其更改为例如“测试”(复数)。
expz

9
test_antigravity.py仍然为import antigravity和同时引发导入错误from antigravity import antigravity。我有两个__init_.py文件,并且正在python3 -m unittest discovernew project目录中调用。还有什么可能是错的?
imrek

19
文件test/__init__.py在这里至关重要,即使是空的
Francois

3
@ Mike3d0g不确定您是否要暗示目录名称test是特殊的……但仅出于记录目的,不是。:P python -m unittest discover和中的测试文件tests/一样test/
瑞安

49

对用户来说,最简单的解决方案是提供一个可执行脚本(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)现在具有内置的测试发现功能,因此,如果您要进行自动测试发现,则不再需要鼻子:您的用户说明可以很简单python -m unittest discover


我在子文件夹中进行了一些测试,例如“ Major Major”。它们可以与python -m unittest discover一起运行,但是我如何选择仅运行其中之一。如果我运行python -m unittest tests / testxxxxx,则它会因路径问题而失败。由于发现模式可以解决所有问题,因此我希望还有另一种无需手动编码即可解决路径问题的技巧,您建议第一点
Frederic Bazin 2012年

2
@FredericBazin如果只需要单个测试或测试文件,则不要使用发现,只需命名要运行的模块即可。如果将其命名为模块点路径(而不是文件路径),则可以正确找出搜索路径。有关更多详细信息,请参见Peter的答案。
卡尔·梅耶

在必须运行类似的情况下,此技巧很有用python -m pdb tests\test_antigravity.py。在pdb内部,我执行了sys.path.insert(0, "antigravity")该操作,该命令允许import语句像运行模块一样进行解析。
ixe013 '19

23

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

run_tests.py:

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

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

import glob
import 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在测试模块中。缺点是您需要更多的支持代码来执行特定的测试……我每次都运行它们。


1
我还想要项目目录中run tests脚本,并找到了一种更简洁的方法。强烈推荐。
z33k

18

从您链接到的文章:

创建一个test_modulename.py文件,并将您的unittest测试放入其中。由于测试模块与代码位于不同的目录中,因此您可能需要将模块的父目录添加到PYTHONPATH中才能运行它们:

$ cd /path/to/googlemaps

$ export PYTHONPATH=$PYTHONPATH:/path/to/googlemaps/googlemaps

$ python test/test_googlemaps.py

最后,鼻子还有一个更流行的Python单元测试框架(这很重要!)。鼻子可以帮助简化和扩展内置的单元测试框架(例如,它可以自动找到您的测试代码并为您设置PYTHONPATH),但是标准Python发行版中并未包含。

也许您应该按照提示看一下鼻子


3
是的,这对我来说是可行的,但是我实际上是在要求最简单的说明,这些说明可以给用户我的模块以使他们运行测试。修改路径实际上可能就是它,但是我正在寻找更简单的方法。
Major Major 2009年

4
那么,在处理了数百个项目之后,您的python路径是什么样的?我是否应该手动进入并清理路径?如果是这样,这是一个令人讨厌的设计!
jeremyjjbrown 2014年

11

我有一个相同的问题,有一个单独的单元测试文件夹。根据上述建议,我将绝对源路径添加到sys.path

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

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

import antigravity
import unittest

9

如果您运行“ python setup.py development”,则该软件包将位于路径中。但是您可能不想这样做,因为您可能会感染系统python安装,这就是为什么存在virtualenvbuildout之类的工具的原因。


7

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)test/models通过添加__init__.py文件将目录变成一个包。这使得子目录中的测试用例可以从父test目录访问。

# ProjectName/test/models/__init__.py

from .test_thing_1 import Thing1TestCase        

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

# ProjectName/test/__main__.py

import sys
import 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 unittest
from 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)

5

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


这给了我一个invalid command 'develop',如果我要的话就不提这个选项setup.py --help-commandssetup.py本身是否需要某些东西才能起作用?
Major Major 2009年

可以-问题是import setuptools我的setup.py文件中缺少。但是我想这确实表明,这对于其他人的模块并非始终有效。
Major Major 2009年

1
如果您有pip,则可以使用它来以“可编辑”模式安装软件包:pip install -e .这同样将软件包添加到Python环境中,而无需复制源代码,使您可以继续编辑它所在的位置。
埃里克·史密斯

pip install -e .与完全相同python setup.py developsetup.py即使实际上并没有,它也只是修补了您使用setuptools的补丁,因此无论哪种方式都可以工作。
卡尔·梅耶

5

如果您使用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文件内部,将路径添加到项目的根目录。这将暂时添加

PYTHONPATH = C:\您的\ PYTHON \ PROJECT \ ROOT_DIRECTORY

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


5

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

python -m unittest discover -s ../test

如果要将其放入项目目录中的批处理文件中,可以执行以下操作:

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

5

我有相同的问题很长时间了。我最近选择的是以下目录结构:

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

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

让我知道你们是否喜欢它。

希望能有所帮助,


4

以下是我的项目结构:

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

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

import unittest
import 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()

4

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

我使用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()

4

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

例如:

./run_tests antigravity/*.py

或使用globlobtests/**/*.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单元测试?


4

Python 3+

添加到@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即使为空也不会起作用。


2

没有伏都教,您无法从父目录导入。这是至少与Python 3.6兼容的另一种方式。

首先,具有以下内容的文件test / context.py:

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

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

import unittest
try:
    import context
except ModuleNotFoundError:
    import test.context    
import antigravity

请注意,此try-except子句的原因是

  • 使用“ python test_antigravity.py”运行时,导入test.context失败,并且
  • 在new_project目录中使用“ python -m unittest”运行时,导入上下文失败。

有了这个技巧,他们俩都可以工作。

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

$ pwd
/projects/new_project
$ python -m unittest

或使用以下命令运行单个测试文件:

$ cd test
$ python test_antigravity

好的,与其在test_antigravity.py中包含context.py的内容相比,没有什么漂亮,但也许有一点。欢迎提出建议。


2

如果测试目录中有多个目录,则必须在每个目录中添加一个__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 START
                        Directory to start discovery ('.' default)
  -t TOP, --top-level-directory TOP
                        Top level directory of project (defaults to start
                        directory)

1

无论您位于哪个工作目录中,此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在生产过程中,无需文件来负担您的包/内存开销。


1

这样一来,您就可以从任何位置运行测试脚本,而不必从命令行中弄乱系统变量。

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

import sys, os

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

将其添加到所有测试脚本的顶部。这样会将主项目文件夹添加到系统路径,因此从那里开始工作的所有模块导入现在都可以工作。而且从哪里运行测试都没有关系。

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


0

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

基于以下目录结构(一般带有专用的源目录):

new_project/
    src/
        antigravity.py
    test/
        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添加更多目录。


0

您应该真正使用pip工具。

用于pip install -e .在开发模式下安装软件包。这是pytest推荐的一种非常好的做法(请参阅其良好做法文档,在这里您还可以找到两个要遵循的项目布局)。


为什么要拒​​绝这个答案?我阅读了接受的答案,尽管它不错,但pytest运行测试的方式更好,因为控制台输出的颜色是彩色的,其中包含堆栈跟踪信息和详细的断言错误信息。
aliopi
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.