【问题标题】:Running unittest with typical test directory structure使用典型的测试目录结构运行 unittest
【发布时间】:2010-12-26 04:44:50
【问题描述】:

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

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

例如看到这个Python project howto

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

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

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

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

【问题讨论】:

  • @EMP 当您需要设置搜索路径时,正确的解决方案是... 设置搜索路径。您期待什么样的解决方案?
  • @CarlMeyer 另一个更好的解决方案是使用unittest 命令行界面,如我的answer below 中所述,因此您不必将目录添加到路径中。
  • 这里也一样。我刚刚开始为一个小型 Python 项目编写我的第一个单元测试,并花了几天时间试图解释这样一个事实,即我无法在将源代码保存在 src 目录中并在测试目录中进行测试的同时轻松运行测试,似乎与任何现有的测试框架。我最终会接受事情,想办法;但这是一个非常令人沮丧的介绍。 (而且我是 Python 之外的单元测试老手。)

标签: python unit-testing


【解决方案1】:

我认为最好的解决方案是使用unittestcommand line interface,这会将目录添加到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 discovery,它将为您发现并运行所有测试,它们必须是名为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 模块。

【讨论】:

  • python -m unittest discover 将在 test 目录中查找并运行测试(如果它们被命名为 test*.py)。如果您将子目录命名为tests,请使用python -m unittest discover -s tests,如果您将测试文件命名为antigravity_test.py,请使用python -m unittest discover -s tests -p '*test.py' 文件名可以使用下划线,但不能使用破折号。
  • 这对我来说在 Python 3 上失败,错误 ImportError: No module named 'test.test_antigravity' 因为与单元测试库的测试子模块冲突。也许专家可以确认并将答案子目录名称更改为例如“测试”(复数)。
  • 我的test_antigravity.py 仍然会为import antigravityfrom antigravity import antigravity 引发导入错误。我有两个__init_.py 文件,我从new project 目录调用python3 -m unittest discover。还有什么问题?
  • file test/__init__.py 在这里很重要,即使是空的
  • @Mike3d0g 不确定您是否要暗示目录名称 test 是特殊的......但只是为了记录,它不是。 :P python -m unittest discovertests/ 以及 test/ 中的测试文件一起使用。
【解决方案2】:

对您的用户而言,最简单的解决方案是提供一个可执行脚本(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 定位在正确的位置即可。

此外,unittest module in Python 2.7+(在 Python 2.6 及更早版本中被反向移植为unittest2)现在内置了test discovery,因此如果您想要自动测试发现,则不再需要鼻子:您的用户指令可以是就像python -m unittest discover 一样简单。

【讨论】:

  • 我将一些测试放在像“Major Major”这样的子文件夹中。它们可以使用 python -m unittest discover 运行,但我如何选择只运行其中一个。如果我运行 python -m unittest tests/testxxxxxx 那么它会因路径问题而失败。由于探索模式解决了所有问题,我希望还有另一个技巧可以解决路径问题,而无需您在第一点建议的手动编码路径修复
  • @FredericBazin 如果您只想要一个测试或测试文件,请不要使用发现,只需命名您要运行的模块即可。如果将其命名为模块点分路径(而不是文件路径),它可以正确找出搜索路径。有关详细信息,请参阅彼得的答案。
  • 这个 hack 在我必须运行类似 python -m pdb tests\test_antigravity.py 的场景中很有用。在 pdb 中,我执行了 sys.path.insert(0, "antigravity"),它允许 import 语句解析,就像我正在运行模块一样。
【解决方案3】:

我通常在加载我的“所有测试”套件的项目目录(源目录和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(来自How do I run all Python unit tests in a directory?

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。缺点是你需要更多的支持代码来执行特定的测试......我每次都运行它们。

【讨论】:

  • 我也想要项目目录中的run tests 脚​​本,然后找到a lot cleaner way 来做。强烈推荐。
【解决方案4】:

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

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 文件夹、我的单元测试或我的环境变量,并且一切运行顺利。

如果你们喜欢,请告诉我。

希望对你有帮助,

【讨论】:

  • 自从我写了这个答案后,我找到了一种避免 sys.path.append 解决方法的方法。如果我找到时间,我会更新我的答案。
  • “如果我有时间我会更新我的答案”
  • @JoaquínL.Robles :哈哈是的,当我写最后一条评论时,我才刚刚开始担任 CTO 的工作。没想到时间这么少!
  • 今天怎么样? :)
  • @programmar: 还在发疯哈哈我猜这个答案在我永远长长的待办事项列表中丢失了..
【解决方案5】:

来自您链接到的文章:

创建一个 test_modulename.py 文件并 将您的单元测试测试放入其中。自从 测试模块在一个单独的 代码中的目录,您可能需要 添加模块的父目录 到您的 PYTHONPATH 以便运行 他们:

$ cd /path/to/googlemaps

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

$ python test/test_googlemaps.py

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

也许您应该按照它的建议查看nose

【讨论】:

  • 是的,这对我有用,但我真的要求提供最简单的指令,我可以向用户提供我的模块以让他们运行测试。修改路径实际上可能是这样,但我正在寻找更直接的东西。
  • 那么在你完成了一百个项目之后,你的 python 路径是什么样的呢?我应该手动进入并清理我的路径吗?如果是这样,这是一个可恶的设计!
【解决方案6】:

我有同样的问题,有一个单独的单元测试文件夹。根据上述建议,我将绝对源路径添加到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

【讨论】:

    【解决方案7】:

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

    【讨论】:

      【解决方案8】:

      Python 单元测试模块的解决方案/示例

      给定以下项目结构:

      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 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)
      

      【讨论】:

        【解决方案9】:

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

        python -m unittest discover -s ../test
        

        如果你想把它放在项目目录的批处理文件中,你可以这样做:

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

        【讨论】:

        • 在此页面上的所有答案中,这是唯一对我有用的答案。我怀疑其他答案缺少一些重要信息。
        • 我们必须这样做是非常愚蠢的。但是你能做什么..这是最简单易行的解决方案
        【解决方案10】:

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

        【讨论】:

        • 这给我一个invalid command 'develop',如果我要求setup.py --help-commands,这个选项不会被提及。 setup.py 本身是否需要有一些东西才能让它工作?
        • 没关系 - 问题是我的 setup.py 文件中缺少 import setuptools。但我想这确实表明这不会一直适用于其他人的模块。
        • 如果你有pip,你可以用它来安装你的包在"editable" mode:pip install -e . 这同样将包添加到Python环境而不复制源,允许你继续编辑它所在的位置。
        • pip install -e .python setup.py develop 完全相同,它只是对您的setup.py 进行猴子补丁以使用setuptools,即使它实际上并没有,所以它可以工作。
        【解决方案11】:

        如果您使用 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:\YOUR\PYTHON\PROJECT\ROOT_DIRECTORY

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

        【讨论】:

          【解决方案12】:

          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,即使为空,否则将不起作用。

          【讨论】:

            【解决方案13】:

            如果没有一些巫术,您无法从父目录导入。这是另一种至少适用于 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 子句的原因是

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

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

            现在您可以运行 所有 测试目录中的测试文件:

            $ pwd
            /projects/new_project
            $ python -m unittest
            

            或运行单个测试文件:

            $ cd test
            $ python test_antigravity
            

            好吧,它并不比在 test_antigravity.py 中包含 context.py 的内容更漂亮,但也许有一点。欢迎提出建议。

            【讨论】:

              【解决方案14】:

              以下是我的项目结构:

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

              【讨论】:

                【解决方案15】:

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

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

                【讨论】:

                  【解决方案16】:

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

                  例如:

                  ./run_tests antigravity/*.py
                  

                  或递归运行所有测试使用globbing (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)
                  

                  查看this 示例了解更多详情。

                  另见:How to run all Python unit tests in a directory?

                  【讨论】:

                    【解决方案17】:

                    你真的应该使用 pip 工具。

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

                    【讨论】:

                    • 为什么不赞成这个答案?我阅读了接受的答案,虽然它还不错,但 pytest 运行测试要好得多,因为你会得到控制台输出,彩色,堆栈跟踪信息和详细的断言错误信息。
                    【解决方案18】:

                    如果您的测试目录中有多个目录,那么您必须在每个目录中添加一个__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)
                    

                    【讨论】:

                      【解决方案19】:

                      此 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 文件来负担您的包/内存开销。

                      【讨论】:

                        【解决方案20】:

                        这种方式可以让您在任何地方运行测试脚本,而不会在命令行中弄乱系统变量。

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

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

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

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

                        【讨论】:

                          【解决方案21】:

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

                          PYTHONPATH=$PWD python test/test_antigravity.py
                          

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

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

                          【讨论】:

                            【解决方案22】:

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

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

                            new_project/
                                src/
                                    antigravity.py
                                test/
                                    test_antigravity.py
                            

                            Windows:(在new_project

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

                            如果您想在批处理 for 循环中使用它,请参阅 this question

                            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 添加更多目录。

                            【讨论】:

                              【解决方案23】:

                              你项目中的unittest有setup.py文件。试试:

                              python3 setup.py build
                              

                              python3 setup.py develop --user
                              

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

                              【讨论】:

                                猜你喜欢
                                • 1970-01-01
                                • 2018-03-16
                                • 2011-07-14
                                • 1970-01-01
                                • 1970-01-01
                                • 2011-04-02
                                • 1970-01-01
                                • 2014-09-30
                                相关资源
                                最近更新 更多