将我的Python单元测试安装在site-packages中合理吗?

15 投票
3 回答
5088 浏览
提问于 2025-04-17 13:20

我正在开发我的第一个Python分发包。关于Python打包的学习曲线似乎有点平稳下来,但我仍然在解决一些未解的问题。其中一个问题是,我是否应该让我的单元测试和代码一起安装。

我明白在源分发中包含测试是很重要的。现在我想知道的是,我是否应该实际配置它们以便安装?

我看到至少有一个流行的包似乎是故意这样做的(PyHamcrest),还有一个包似乎是意外这样做的(behave)。

所以我有几个问题:

  • 在什么情况下,把我的包的单元测试和包代码一起安装是有意义的呢?

  • 如果有,那具体的使用场景是什么?谁会使用它们,目的是什么?也就是说,谁会使用它们而不是直接下载源代码分发并运行 python setup.py test 呢?

  • 而且,他们会如何使用已安装的单元测试?是像 import test; test.run() 这样吗?

3 个回答

2

不过我不是专家,但我想分享一下我的看法。

如果我觉得某些东西可能会因为外部原因而出问题,我会把测试代码放在主代码旁边。比如说,位序、奇怪的时区、字符编码、24位整数,或者其他任何你可能遇到的奇怪情况,最好都有测试来验证。

谁不想下载源代码并运行测试呢?也许一些 debian 用户会觉得不方便,因为他们的包是从源代码中去掉的(我知道你在说 Python,但我想稍微宽泛一点),而你的库有时可能会因为系统中的一些奇怪问题而出错。

如果你的测试只是确保内部逻辑正常,那我建议不必附上这些测试,因为没有源代码的话,它们的价值不大,因为你永远不会去修改库的内部逻辑。

就我个人而言,我听说过一个事情,因为它被移到了某个 IBM 机器上,导致位序不同而出错。我不记得这是否与位操作有关,还是说有些东西是预先计算并静态缓存的。但有时候检查一下你加载的东西是否和你保存的一样是明智的。

编辑:也许换个说法会更好。我会在你觉得可能会有移植性问题的时候安装测试。我认为在你把东西部署到不同系统上时,检查一下总是好的。

14

我认为正确的答案是“不”,但你会发现有很多发行版会安装测试。测试不应该被安装,但应该包含在源代码发行版中。在我看来,在一个理想的世界里,测试已安装的软件包应该由包管理器(比如pip)来完成,而site-packages目录不应该被测试源代码污染。

我最近研究了这个话题,收集了来自不同来源的信息,发现了几种不同的方式来组织一个包含库源代码和测试的发行版的目录结构。大多数这些结构似乎已经过时,它们是在当时尝试解决旧发行系统功能不全时发明的。不幸的是,很多在线资源(旧的博客文章/文档)仍在宣传这些过时的方法,所以很容易通过在线搜索找到过时的发行版教程。

假设你有一个叫“my_lib”的库,你想要组织你的发行版源代码。我将展示两种流行且看似过时的组织方式,以及我发现的第三种方式,这种方式是最灵活的。第三种方法可能也过时,但这是我在写这个内容时知道的最好方法。;-)

方法 #1

那些(有意或无意)安装测试的发行版通常使用这种方法。

目录结构

+- my_lib
|  +- __init__.py
|  +- source1.py
|  +- source2.py
|  +- tests
|     +- __init__.py
|     +- test_1.py
|     +- test_2.py
+- setup.py

方法 #2

测试不会被安装,但应该通过MANIFEST.in文件包含在源代码发行版中。

目录结构

+- my_lib
|  +- __init__.py
|  +- source1.py
|  +- source2.py
+- tests
|  +- __init__.py
|  +- test_1.py
|  +- test_2.py
+- setup.py

方法 #3(我更喜欢这个。)

这与方法 #2类似,但有一点不同(就是src目录)。

目录结构

+- src
|  +- my_lib
|     +- __init__.py
|     +- source1.py
|     +- source2.py
+- tests
|  +- __init__.py
|  +- test_1.py
|  +- test_2.py
+- setup.py

setup()调用在setup.py中

from setuptools import setup, find_packages

setup(
    ...
    packages=find_packages('src'),
    package_dir={'': 'src'},
    ...
)

MANIFEST.in

recursive-include tests *.py

测试不会被安装,但会通过我们的MANIFEST.in包含在源代码发行版中。

在方法 #3中,你有一个src目录,通常只包含一个包,这个包是你库的根目录。把my_lib包放进src目录(这是一个目录而不是包,所以你不需要src/__init__.py)有以下好处:

  • 当你执行setup.py时,包含setup.py的目录会被隐式添加到Python路径中。这意味着在你的setup.py中,如果你的库包和setup.py在同一个目录下,你可能会不小心错误地导入库中的东西。通过把my_lib包放进src,我们可以避免这个问题。

  • 你可以轻松地使用分发的测试源来测试分发的库源和已安装的库:

    • 当你使用setup.py test运行测试时,package_dir={'': 'src'}这一部分确保你的测试可以看到你保存在src/my_lib中的my_lib库包。
    • 你也可以在没有setup.py的情况下运行测试。在最简单的情况下,你可以使用python -m unittest命令来做到这一点。在这种情况下,src目录不会成为Python路径的一部分,所以你可以使用这种方法来测试已安装的库版本,而不是src中的源代码。
11

经过研究这个问题,直到有更有经验的人能给出不同的看法,我的理解是:简单的答案是:“不,单元测试不应该被安装,只应该包含在源代码分发中。”

我发现的少数几个测试被安装的情况,都是意外的,而且犯这个错误比想象中要简单得多,而且你可能不会注意到。

这是怎么发生的:

  1. 在setup.py文件中使用了packages=find_packages()这个参数,这样可以自动找到包,而不需要一个个列出来。
  2. test文件夹变成一个包(通过添加__init__.py文件),这样测试就可以用相对路径来引用它们要测试的模块(比如from .. import pkg.mod)。
  3. setuptools会把test当作一个独立的包安装,和项目中的其他包一起。注意,这意味着你可以在Python解释器中执行import test,而且它会成功,这几乎肯定不是你想要的,尤其是因为很多其他人也用这个名字来命名他们的测试目录 :)

解决办法是使用设置:packages=find_packages(exclude=['test']),这样就可以防止你的测试目录被安装。

撰写回答