Python单元测试在哪里?

Python单元测试在哪里?

Where do the Python unit tests go?

如果您正在编写库或应用程序,那么单元测试文件将放在哪里?

将测试文件与主应用程序代码分开是很好的,但是将它们放入应用程序根目录内的"test s"子目录是很难的,因为这样很难导入要测试的模块。

这里有最佳实践吗?


对于文件module.py,单元测试通常应按照pythonic命名约定称为test_module.py

有几个常见的地方可以放置test_module.py

  • module.py在同一目录中。
  • ../tests/test_module.py中(与代码目录处于同一级别)。
  • tests/test_module.py中(代码目录下的一级)。
  • 我更喜欢1,因为它简单地查找测试并导入它们。您使用的任何构建系统都可以轻松配置为运行从test_开始的文件。实际上,用于测试发现的默认unittest模式是test*.py


    一种常见的做法是将测试目录放在与模块/包相同的父目录中。因此,如果您的模块名为foo.py,则目录布局如下:

    1
    2
    3
    parent_dir/
      foo.py
      tests/

    当然,这是没有办法的。您还可以创建一个tests子目录并使用绝对导入导入模块。

    无论你把测试放在哪里,我都建议你用鼻子来运行它们。nose在目录中搜索测试。通过这种方式,您可以将测试组织到最有意义的地方。


    只有一个测试文件

    如果没有太多的测试文件,那么将其放在顶级目录中是很好的(我认为这是一种pythonic(推荐)方法):

    1
    2
    3
    4
    5
    module/
      lib/
        __init__.py
        module.py
      test.py

    许多测试文件

    如果有许多测试文件,请将其放在tests文件夹中:

    1
    2
    3
    4
    5
    6
    7
    module/
      lib/
        __init__.py
        module.py
      tests/
        test_module.py
        test_module2.py

    但是,如果您将测试放在tests文件夹中,则由于__main__无法导入相关模块,因此无法在cli中进行import ..lib测试,因此我们可以使用nose,或者将父目录添加到python导入路径中,为此,我将创建一个

    爱因斯坦

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    import sys
    import os

    # append module root directory to sys.path
    sys.path.append(
        os.path.dirname(
            os.path.dirname(
                os.path.abspath(__file__)
            )
        )
    )

    在里面

    1
    2
    3
    4
    module/
      tests/
        test_module.py
        env.py

    import env在测试导入模块之前

    测试模块

    1
    2
    3
    4
    5
    6
    7
    8
    import unittest
    # append parent directory to import path
    import env
    # now we can import the lib module
    from lib import module

    if __name__ == '__main__':
        unittest.main()

    在编写pythoscope(https://pypi.org/project/pythoscope/)时,我们遇到了同样的问题,它为python程序生成单元测试。在选择一个目录之前,我们在python列表中对测试人员进行了调查,有很多不同的意见。最后,我们选择将"tests"目录放在与源代码相同的目录中。在这个目录中,我们为父目录中的每个模块生成一个测试文件。


    我也倾向于把我的单元测试放在文件中,正如Jeremy Cantrell上面提到的,虽然我倾向于不把测试功能放在主体中,而是把所有东西放在

    1
    2
    if __name__ == '__main__':
       do tests...

    块。这最终会将文档作为"示例代码"添加到文件中,以了解如何使用正在测试的python文件。

    我应该补充一下,我倾向于写非常紧凑的模块/类。如果您的模块需要大量的测试,您可以将它们放在另一个模块中,但即使这样,我仍然要补充:

    1
    2
    3
    if __name__ == '__main__':
       import tests.thisModule
       tests.thisModule.runtests

    这使任何阅读源代码的人都知道在哪里查找测试代码。


    每隔一段时间,我会发现自己在检查测试放置的主题,每当大多数人在库代码旁边建议一个单独的文件夹结构时,我会发现每次参数都是相同的,并且没有那么令人信服。最后我把我的测试模块放在核心模块旁边。

    这样做的主要原因是:重构。

    当我四处移动东西时,我确实希望测试模块随代码一起移动;如果它们位于单独的树中,那么很容易丢失测试。老实说,你迟早会得到一个完全不同的文件夹结构,比如django、flask和其他许多文件夹。如果你不在乎,那就好了。

    你应该问自己的主要问题是:

    我写的是:

    • a)可重用库或
    • b)构建一个项目,而不是将一些半分离的模块捆绑在一起?

    如果A:

    单独的文件夹和维护其结构的额外工作可能更适合。没有人会抱怨您的测试被部署到生产环境中。

    但是,当测试与核心文件夹混合时,也很容易将它们排除在分发之外;请将其放入setup.py:

    1
    find_packages("src", exclude=["*.tests","*.tests.*","tests.*","tests"])

    如果B:

    你可能希望——就像我们每个人一样——你正在写可重用的库,但是大多数时候它们的生命都与项目的生命息息相关。应该优先考虑轻松维护项目的能力。

    如果你做的很好,并且你的模块很适合另一个项目,那么它很可能会被复制到这个新项目中,而不是被分叉或被制作成一个单独的库,与在一个单独的测试文件夹变得混乱的情况下查找测试相比,将放在它旁边的测试移动到同一个文件夹结构中是很容易的。(你可能会说一开始不应该是一团糟,但让我们现实一点)。

    所以这个选择仍然是你的,但是我认为,通过混合测试,你可以实现与单独文件夹相同的所有功能,但是在保持事物整洁方面做得更少。


    我使用一个tests/目录,然后使用相对导入导入导入主要应用程序模块。所以在myapp/tests/foo.py中,可能有:

    1
    from .. import foo

    导入MyApp.foo模块。


    我不相信有既定的"最佳实践"。

    我把测试放在应用程序代码之外的另一个目录中。然后,在运行所有测试之前,我将主应用程序目录添加到我的测试运行程序脚本中的sys.path(允许您从任意位置导入模块)(这也做了一些其他的事情)。这样一来,当我发布主代码时,我就不必从主代码中删除测试目录,这就节省了我的时间和精力(如果数量如此之少的话)。


    根据我在用Python开发测试框架方面的经验,我建议将Python单元测试放在一个单独的目录中。维护一个对称的目录结构。这将有助于只打包核心库,而不是打包单元测试。下面是通过示意图实现的。

    1
    2
    3
    4
    5
    6
    7
    8
                                  <Main Package>
                                   /          \
                                  /            \
                                lib           tests
                                /                \
                 [module1.py, module2.py,  [ut_module1.py, ut_module2.py,
                  module3.py  module4.py,   ut_module3.py, ut_module.py]
                  __init__.py]

    这样,当您使用RPM打包这些库时,您可以只打包主库模块(仅限)。这有助于维护性,特别是在敏捷环境中。


    我建议您检查一下Github上的一些主要的python项目并获得一些想法。

    当代码变大并且添加了更多的库时,最好在与setup.py相同的目录中创建一个测试文件夹,并为每个测试类型(unittest、integration,…)镜像项目目录结构。

    例如,如果您有如下目录结构:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    myPackage/
        myapp/
           moduleA/
              __init__.py
              module_A.py
           moduleB/
              __init__.py
              module_B.py
    setup.py

    添加测试文件夹后,您将拥有如下目录结构:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    myPackage/
        myapp/
           moduleA/
              __init__.py
              module_A.py
           moduleB/
              __init__.py
              module_B.py
    test/
       unit/
          myapp/
             moduleA/
                module_A_test.py
             moduleB/
                module_B_test.py
       integration/
              myapp/
                 moduleA/
                    module_A_test.py
                 moduleB/
                    module_B_test.py
    setup.py

    许多正确编写的python包使用相同的结构。一个很好的例子是boto包。查看https://github.com/boto/boto


    我是怎么做到的…

    文件夹结构:

    1
    2
    3
    4
    5
    project/
        src/
            code.py
        tests/
        setup.py

    setup.py指向src/作为包含项目模块的位置,然后运行:

    1
    setup.py develop

    它将我的项目添加到站点包中,指向我的工作副本。要运行我使用的测试:

    1
    setup.py tests

    使用我配置的测试运行程序。


    我更喜欢顶级测试目录。这确实意味着进口变得有点困难。为此,我有两个解决方案:

  • 使用设置工具。然后您可以将test_suite='tests.runalltests.suite'传递到setup()中,并且可以简单地运行测试:python setup.py test
  • 运行测试时设置pythonpath:PYTHONPATH=. python tests/runalltests.py
  • 以下是M2Crypto中代码如何支持这些内容:

    • http://svn.osafoundation.org/m2crypto/trunk/setup.py
    • http://svn.osafoundation.org/m2crypto/trunk/tests/alltests.py

    如果你喜欢用鼻子测试来运行测试,你可能需要做一些不同的事情。


    在C中,我通常将测试分成单独的组件。

    在python中——到目前为止——我倾向于编写doctest,其中测试在函数的docstring中,或者将它们放在模块底部的if __name__ =="__main__"块中。


    我们使用

    APP/SRC/CODE.PY

    应用程序/测试/代码 est.py

    APP/DOCS/…

    在每个测试文件中,我们在sys.path中插入".../src/"。这不是最好的解决方案,但有效。我认为如果有人出现在Wave/Maven之类的Java中,这将是非常棒的,无论你从事什么项目,都会给你标准的工作规范。


    如果测试很简单,只需将它们放在docstring中——大多数用于Python的测试框架都可以使用它:

    1
    2
    3
    >>> import module
    >>> module.method('test')
    'testresult'

    对于其他更复杂的测试,我将它们放在../tests/test_module.pytests/test_module.py中。


    在编写一个名为"foo"的包时,我将把单元测试放入一个单独的包"foo-test"。然后,模块和子包将具有与SUT包模块相同的名称。例如,模块foo.x.y的测试可以在foo-test.x.y中找到。每个测试包的uu init_uuy文件包含一个包含包的所有测试套件的所有测试套件。安装工具提供了一种指定主测试包的方便方法,因此在"python setup.py development"之后,您只需将"python setup.py test"或"python setup.py test-s foo_test.x.sometestsuite"用于特定的套件。


    我将测试放在与被测代码(CUT)相同的目录中;对于foo.py,测试将放在foo_ut.py或类似目录中。(我调整测试发现过程以找到这些。)

    这将测试放在代码旁边的目录列表中,使测试明显存在,并使打开测试尽可能容易,因为它们可能位于单独的文件中。(对于命令行编辑器,vim foo*和使用图形文件系统浏览器时,只需单击剪切文件,然后单击紧邻的测试文件。)

    正如其他人所指出的,这也使得重构和提取代码变得更加容易,以便在必要时在其他地方使用。

    我真的不喜欢将测试放在完全不同的目录树中的想法;为什么让开发人员在打开带有切口的文件时更难打开测试?绝大多数开发人员不是如此热衷于编写或修改测试,以至于他们会忽略任何这样做的障碍,而不是将障碍作为借口。(恰恰相反,在我的经验中,即使你尽可能简单,我也知道许多开发人员不会费心编写测试。)


    我最近开始用Python编程,所以我还没有机会找到最佳实践。但是,我编写了一个模块,可以找到所有测试并运行它们。

    所以,我有:

    1
    2
    3
    4
    app/
     appfile.py
    test/
     appfileTest.py

    我要看看随着我向更大的项目的进展情况。


    推荐阅读