首页 文章

运行具有典型测试目录结构的unittest

提问于
浏览
526

即使是简单的Python模块,非常常见的目录结构似乎是将单元测试分成它们自己的 test 目录:

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

例如,见Python project howto .

我的问题只是 What's the usual way of actually running the tests? 我怀疑除了我以外,每个人都很明显,但是你不能只是从测试目录运行 python test_antigravity.py ,因为它的 import antigravity 会失败,因为模块不在路径上 .

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

另一种选择只是将测试文件复制到另一个目录中,但它看起来有点愚蠢,并且忽略了将它们放在一个单独的目录中的重点 .

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

18 回答

  • 19

    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
    

    Mandatory 不要忘记 __init__.py 即使是空的否则将无效 .

  • -3

    Solution/Example for Python unittest module

    鉴于以下项目结构:

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

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

    例如:

    ./run_tests antigravity/*.py
    

    或者以递归方式运行所有测试使用globbingtests/**/*.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?

  • 1

    如果你运行"python setup.py develop"那么包将在路径中 . 但是你可能不想这样做,因为你可能会感染你的系统python安装,这就是为什么像virtualenvbuildout这样的工具存在的原因 .

  • 492

    如果您使用VS代码并且您的测试与项目位于同一级别,则运行和调试代码不能立即使用 . 你可以做的是改变你的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中的调试单元测试

  • 5

    在我看来,最好的解决方案是使用 unittest command 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
    

    Running a single test module:

    要运行单个测试模块,在本例中为 test_antigravity.py

    $ cd new_project
    $ python -m unittest test.test_antigravity
    

    只需像导入它一样引用测试模块 .

    Running a single test case or test method:

    您还可以运行单个 TestCase 或单个测试方法:

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

    Running all tests:

    您还可以使用test discovery来发现并运行所有测试,它们必须是名为 test*.py 的模块或包(可以使用 -p, --pattern 标志进行更改):

    $ cd new_project
    $ python -m unittest discover
    

    这将运行 test 包中的所有 test*.py 模块 .

  • 0

    我有同样的问题,有一个单独的单元测试文件夹 . 从提到的建议我添加 absolute source pathsys.path .

    以下解决方案的好处是,可以在不首先更改为test-directory的情况下运行文件 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
    
  • 5

    以下是我的项目结构:

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

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

    python -m unittest discover -s ../test
    

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

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

    我通常在项目目录(源目录和 test 共有的)中创建一个"run tests"脚本来加载我的"All Tests"套件 . 这通常是样板代码,所以我可以重用它从项目到项目 .

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

  • 1

    如果测试目录中有多个目录,则必须向每个目录添加 __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)
    
  • 4

    无论您在哪个工作目录中,此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 文件来增加包/内存开销 .

  • 3

    没有一些伏都教,你无法从父目录导入 . 这是另一种适用于至少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"和_运行时

    • import test.context失败
      从new_project目录运行"python -m unittest"时
    • 导入上下文失败 .

    有了这个诡计他们都工作 .

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

    $ pwd
    /projects/new_project
    $ python -m unittest
    

    或运行单个测试文件:

    $ cd test
    $ python test_antigravity
    

    好吧,它比在test_antigravity.py中使用context.py的内容更漂亮,但也许有点 . 欢迎提出建议 .

  • 4

    对您的用户来说,最简单的解决方案是提供一个可执行脚本( 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"一样简单 .

  • 18

    从您链接的文章:

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

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

    $ python test / test_googlemaps.py
    最后,还有一个更受欢迎的Python单元测试框架(它非常重要!),鼻子 . nose有助于简化和扩展内置单元测试框架(例如,它可以自动找到您的测试代码并为您设置PYTHONPATH),但它不包含在标准Python发行版中 .

    也许你应该看看它建议的nose

  • 8

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

  • 8

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

    我使用Python 3.6.2

    cd new_project
    
    pytest test/test_antigravity.py
    

    要安装pytest: sudo 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()
    
  • 2

    你应该真的使用pip工具 .

    使用 pip install -e 在开发模式下安装软件包 . 这是一个非常好的做法 .

    在下面给出的参考网址中,给出了2个经典项目(带测试)布局,您可以按照其中任何一个进行操作 .

    Ref

相关问题