假设您有一个您正在维护的开源 Python 项目或软件包。您可能希望在当前广泛使用的主要 Python 版本上对其进行测试。您绝对应该这样做。在某些情况下,您可能还需要在不同的操作系统上进行测试。在这篇文章中,我将讨论这两种情况,并建议一种方法来做到这一点。
还没有使用 Travis CI?立即注册!
为了本文的目的,我将假设您
pytest
来测试您的代码。仅仅因为它是一个不错的简单流程,也是我使用的流程。然而,这里介绍的方法可以轻松地适应其他流程。
下面的帖子将引导您完成各个阶段,并说明最终的 .travis.yml 文件的结构的理由,该文件涵盖了对不同 Python 版本和操作系统进行彻底的测试。如果您对所有这些不感兴趣,您可以直接查看我为其创建的 GitHub gist中的最终结果。
使用Travis CI服务是一个很好的方法,它可以测试您的代码在多个 Python 版本上,该服务(除其他功能外)还提供基于云的持续测试,适用于开源(即公共)GitHub 项目。
要使用Travis CI,只需执行以下三个步骤
.travis.yml
文件;此文件将告诉 Travis 如何构建和测试此特定项目。.travis.yml
文件假设您有一个小型 Python 软件包,它有一个简单的setup.py
文件。并且您有以下非常基本的.travis.yml
,它通过多个 Python 版本运行您的测试
language: python
python:
- 2.7
- 3.5
- 3.6
before_install:
- python --version
- pip install -U pip
- pip install -U pytest
- pip install codecov
install:
- pip install ".[test]" . # install package + test dependencies
script: pytest # run tests
after_success:
- codecov # submit coverage
让我们仔细看一下上面文件中的每个部分
python --version
查看我正在运行的确切 Python 版本。(2) pip install -U pip
,因为您始终需要使用最新的 pip。某些构建如果没有此命令将失败(例如,对于我来说,Python 2.7)。(3) pip install -U pytest
- 我已经决定始终这样做,因为这再次可以避免失败。对于我当前的一些项目,如果我只pip install pytest
而没有-U
来更新,则 Python 3.6 会失败。(4) pip install codecov
- 由于我只在 Travis 上使用它,而不是在本地使用它,因此这不是我软件包的额外[test]
依赖项的一部分。pip install
“此”文件夹(所以是.
),并带有可选的测试依赖项。我软件包的测试依赖项通常是pytest
、coverage
和pytest-cov
,它将两者整合起来。pytest
。我假设您在pytest.ini
文件中详细说明了pytest
的所有不错的 CLI 参数。这将有助于在我们开始添加内容时保持.travis.yml
文件更简洁。以下是pytest.ini
的示例 [pytest]
testpaths =
tests
skift
norecursedirs=dist build .tox scripts
addopts =
--doctest-modules
--cov=skift
-r a
-v
pytest
运行生成的覆盖率报告并将其发布到那里。如果您想要任何构建的覆盖率结果(无论是失败还是成功),只需将此行作为script
项的第二个项目即可。使用此配置,对仓库的每次提交都将在 Travis CI 上触发相应项目的良好构建,每个构建都由每个 Python 版本组成(都在 Linux 机器上),如下所示
您还可以进入每个作业的日志,并查看每个命令的结果(无论是实时还是事后)
另一种在多个 Python 版本上测试 Python 代码的方法是使用tox,这是一个用于自动化和标准化 Python 测试的强大工具。我主张使用上述方法,因为它的一个作业 == 一个 Python 版本方法意味着构建完成得更快(因为最多三个作业在 Travis 上并行运行),并且您可以立即了解哪个版本出现了问题,而无需深入研究日志。当然,这只是我的个人偏好。
在多版本 Python 测试中,第一个复杂之处出现在我们尝试使用可能并非都在同一个 Ubuntu 发行版上可用的 Python 版本时。例如,Ubuntu 14.04,dist: trusty
,不支持 Python 3.7
。
为了确保兼容性,即使您在当前默认情况下运行 Linux 构建,最好还是将适用于您的项目的 Ubuntu 发行版固定下来。在本例中,我们将将其固定到 Ubuntu 16.04(绰号为xenial
),这是 Travis CI 目前用于 Linux 构建的默认值。
您可以通过在.travis.yml
文件中添加dist: xenial
项,明确使用 Ubuntu 16.04,它支持我们想要的所有 Python 版本。
但是,如果您更喜欢在 Ubuntu 14.04 上测试较低版本,并且仅对 Python 3.7 使用xenial
呢?
虽然这可能是一个可以忽略不计的极端情况,但它将允许我逐步介绍 Travis 构建矩阵,并且这些矩阵在稍后将被证明至关重要,因此请与我一起完成这些步骤。
在 Travis 中,有两种方法可以指定多个并行作业。第一种方法是为多个影响构建环境的项提供多个选项;所有可能组合的构建矩阵将自动创建并运行。例如,以下配置将生成一个构建矩阵,该矩阵将扩展为 4 个独立的(2 * 2)作业
language: python
python:
- 2.7
- 3.5
env:
- PARALLELIZE=true
- PARALLELIZE=false
第二种方法是在matrix.include
中指定您想要的配置的精确组合。继续上面的示例,如果 Python 2.7 不支持并行化,您可能更喜欢指定三个特定的作业
language: python
matrix:
include:
- python: 2.7
PARALLELIZE=false
- python: 3.5
PARALLELIZE=false
- python: 3.5
PARALLELIZE=true
或者,在本例中,要在xenial
上运行一个 Python 3.7 作业,请添加一个单独的作业项
酷。因此,我们看到了两种在 Travis 上测试 Python 3.7(Pythons 中的特殊雪花)的方法,并且稍微了解了一下 Travis 构建矩阵。让我们继续前进。
因此,您正在测试您在每个重要的主要 Python 版本上的简单纯 Python 软件包。您是一位负责任的开源贡献者。为您欢呼。
但是,如果您的 Python 项目不是纯血统的,而是一个包含一些专门的 C++ 代码的麻瓜呢?或者,也许您的代码是纯 Python 的,但它以非平凡的方式与操作系统进行交互(例如,写入文件、处理线程或进程等),这在不同的操作系统之间会有所不同呢?
如果是这样,如果您希望您的项目支持所有三个主要操作系统,那么您绝对应该在所有三个主要操作系统上测试您的代码(并可能还构建它)。
对我自己来说,在两个项目中出现了这种需求,这两个项目都是纯 Python 的
fcntl
)在 Windows 用户第一次尝试使用我的软件包时就失败了。我最终确定了一个解决方案,即扩展 Travis 构建矩阵以包括操作系统和主要 Python 版本的特定组合,每个组合都将在自己的作业中运行,在完全独立的环境中运行。
同样,当将这种方法与使用tox
进行比较时,我会说主要优势在于
希望我已经说服您这是一种有效的跨操作系统测试方法,因此我们可以转到具体细节。我们将从在 macOS 上进行测试开始,最后介绍 Windows。
在撰写本文时,Python 构建在 macOS 环境中不可用。这并不意味着无法使用 Travis 在 macOS 上测试 Python,只是以下幼稚的方法将不起作用
matrix:
include:
- name: "Generic Python 3.5 on macOS"
os: osx
language: shell # 'language: python' is an error on Travis CI macOS
python: 3.5
无论您为python
键分配什么版本号,您都会获得一台安装了 Python 3.6.5 的 macOS 机器。这是因为请求一台带有os: osx
的机器会启动一台使用默认 Xcode 映像的机器,目前对于 Travis 来说,该映像是 Xcode 9.4.1。
目前获取带有特定 Python 版本的 macOS 机器的一种 hack 方式是请求一个特定的 Xcode 映像,使用osx_image
标签,您知道该映像预装了您想要使用的 Python 版本。
例如,要获得安装了 Python 3.7 的机器,您可以添加 osx_image: xcode10.2
的条目(您将获得 Python 3.7.3,具体来说)。不错,那么您怎么知道哪个 Xcode 镜像包含哪个 Python 版本呢?不幸的是,这种映射在 Travis 的网站或文档中都没有列出。
不过,幸运的是,我已经做了艰苦的工作,并找到了这些信息。这基本上等同于 积极地搜索 Travis 博客中有关 Xcode 镜像发布的帖子,以便找出每个镜像上的 Python 版本。我发现的最新主要 Python 版本发布版本如下:
xcode9.3
— 预装了 Python 2.7.14_2xcode9.4
— 预装了 Python 3.6.5xcode10.2
— 预装了 Python 3.7.3不幸的是,我还没有找到预装了 Python 3.5 的 Travis Xcode 镜像(如果您找到,请告诉我!)。
所以您已经获得了正确的 Xcode 标签。然而,您仍然需要调整一些构建命令。例如,对于 Python 3 版本,我们需要显式地调用 pip3
和 python3
来安装和调用(分别)Python 3 代码,因为 macOS 预装了 Python 2(python
命令指向的是这个)。
matrix:
include:
- name: "Python 3.6.5 on macOS 10.13"
os: osx
osx_image: xcode9.4 # Python 3.6.5 running on macOS 10.13
language: shell # 'language: python' is an error on Travis CI macOS
before_install:
- python3 --version
- pip3 install -U pip
- pip3 install -U pytest
- pip3 install codecov
script: python3 -m pytest
after_success: python 3 -m codecov
考虑到这一点,您可能会认为 Python 2 任务需要更少的自定义条目。不幸的是,因为我们使用的是 OS Python,所以 pip 安装命令需要在 Python 2 后面添加 --user
标志。此外,由于它们的 CLI 命令不会被安装,因此我们也需要通过 python
命令来调用它们的命令。
matrix:
include:
- name: "Python 2.7.14 on macOS 10.13"
os: osx
osx_image: xcode9.3 # Python 2.7.14_2 running on macOS 10.13
language: shell # 'language: python' errors on Travis CI macOS
before_install:
- python --version
- pip install pytest --user
- pip install codecov --user
install: pip install ".[test]" --user
script: python -m pytest # pytest command won't be found
after_success: python -m codecov # codecov command won't be found
好,我们在 macOS 上测试 Python 的工作完成了。来块饼干吧。
Travis 对 Windows 构建的支持处于早期访问阶段。目前,只支持 Windows Server(版本 1803)。这个版本没有预装 Python,但预装了 Chocolatey,这是一个 Windows 包管理器,我们将用它来安装 Python。
由于我们使用 Chocolatey 来安装 Python,因此我们只能使用它提供的版本。对于 Python 3,这些版本是 3.5.4、3.6.8 和 3.7.4。对于 Python 2,目前默认安装的是 2.7.16 版本。
以下是一个简单的任务条目变体,用于获得一个 Windows-Python 任务,其中包含 Chocolatey 安装命令 choco
和一个环境变量设置
matrix:
include:
- name: "Python 3.5.4 on Windows"
os: windows # Windows 10.0.17134 N/A Build 17134
language: shell # 'language: python' is an error on Travis CI Windows
before_install:
- choco install python --version 3.5.4
- python --version
- python -m pip install --upgrade pip
- pip3 install --upgrade pytest
- pip3 install codecov
env: PATH=/c/Python35:/c/Python35/Scripts:$PATH
如您所见,通用的 script
和 after_success
阶段运行良好。您可以查看 最终文件 以查看每个版本(包括 Python 2.7)所需的细微变化。
到目前为止,我们已经涵盖了几乎所有常见的操作系统和重要 Python 版本组合。结合我们在上面所述的各个部分,我们可以创建出一个并不很短的 .travis.yml
文件,它为 Python 项目提供了全面的测试,您可以在我创建的 Github gist 中找到。
但是,在结束本文之前,我想补充几点说明。
在某些情况下,您可能希望在特定操作系统版本组合上持续测试您的代码,而您预计这些组合会失败,例如当某些测试在 Windows 上失败时,但您正准备在不久的将来添加 Windows 支持。在这种情况下,最好不要让整个构建由于此类任务而失败,这样您就不会收到烦人的构建失败通知(而且因为这样您就可以炫耀您仓库中漂亮闪亮的“构建:通过”徽章)。
您可以通过在矩阵条目下添加一个 allow_failures
条目来实现这一点,该条目详细说明了允许哪些任务失败的键值对。例如,要让 macOS 上的 Python 3.7 允许失败,请使用
matrix:
- allow_failures:
- os: osx
osx_image: xcode 10.2
设置 - os: windows
将允许所有 Windows 构建失败。
此外,如果您已经在使用 allow_failures
逻辑,您可能希望利用 fast_finish
功能。设置 fast_finish: true
将确定整个构建状态(通过或失败) — 一旦所有不允许失败的任务完成,其余任务将继续运行。这通常对于小型开源项目来说并不重要,但它很好用,尤其是在某些奇特的 OS 或 Python 版本上的任务被允许失败 **并且** 占用大量时间的情况下。
您可以通过添加相应的条目(例如 3.7-dev
在 python
键下)来针对不同 Python 版本的开发分支测试您的代码。一个重要的开发分支可以测试的是 3.8-dev
,以便为将来做好准备。您可能还想允许使用开发分支的所有任务失败。
我提供的解决方案将大多数针对 macOS 和 Windows 构建的特殊代码放在构建矩阵中。
但是,如果您有一些特定于 Python 版本的安装或测试代码,但应该在所有 OS 上运行,您可以根据相应 Travis 环境变量的值对命令进行条件判断
if [ "$TRAVIS_PYTHON_VERSION" == "2.7" ]; then pip install . ancient_testing_packge; else pip install ".[test]"; fi
要在同一 OS 上的所有任务中执行相同操作,请使用
if ["$TRAVIS_OS_NAME" == "linux"]; then pip install . special_linux_packge; else pip install ".[test]"; fi
当然,如果是这种情况,您可能应该考虑在您的 setup.py
文件中更干净地处理这个问题,通过根据 Python 版本或 OS(使用 Python 代码推断它)动态地构建 extras_require
来实现 test
。
感谢您阅读本文。希望您觉得它有用。🙂
再次强调,您可以在专门的 Github gist 中查看完整的 .travis.yml
文件。
Shay Palachy 是一位数据科学顾问,也是 DataHack 非营利组织 的联合创始人。
还不是 Travis CI 用户?立即注册一个帐户!!
想要为您的构建定制更多功能?试用 Travis CI 免费试用版.