在 Travis CI 上进行广泛的 Python 测试

分享
分享

假设您有一个您正在维护的开源 Python 项目或软件包。您可能希望在当前广泛使用的主要 Python 版本上对其进行测试。您绝对应该这样做。在某些情况下,您可能还需要在不同的操作系统上进行测试。在这篇文章中,我将讨论这两种情况,并建议一种方法来做到这一点。

还没有使用 Travis CI?立即注册!

为了本文的目的,我将假设您

  1. 在 GitHub 上托管您的开源项目。(如果是一个私有项目,本指南也适用,但请访问 travis-ci.com)
  2. 使用pytest来测试您的代码。
  3. 检查代码覆盖率。
  4. 您希望将覆盖率统计信息提交到免费服务codecov.io,以便在您的仓库中获得一个不错的动态测试覆盖率徽章

仅仅因为它是一个不错的简单流程,也是我使用的流程。然而,这里介绍的方法可以轻松地适应其他流程。

下面的帖子将引导您完成各个阶段,并说明最终的 .travis.yml 文件的结构的理由,该文件涵盖了对不同 Python 版本和操作系统进行彻底的测试。如果您对所有这些不感兴趣,您可以直接查看我为其创建的 GitHub gist中的最终结果。

使用 Travis 进行基本的 Python 测试

使用Travis CI服务是一个很好的方法,它可以测试您的代码在多个 Python 版本上,该服务(除其他功能外)还提供基于云的持续测试,适用于开源(即公共)GitHub 项目。

要使用Travis CI,只需执行以下三个步骤

  1. 使用 GitHub 注册 Travis,允许 Travis 访问您的项目。
  2. 在您的仓库页面中,为要测试的仓库启用 Travis。
  3. 在仓库的根目录中放置一个.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 项目。
  • 第二项详细说明了我们要构建的 Python 版本:2.7、3.5 和 3.6。
  • 第三项按顺序详细说明了我们要运行的预安装命令集。我使用了四个命令:(1) 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 安装的软件包,因此我们pip install“此”文件夹(所以是.),并带有可选的测试依赖项。我软件包的测试依赖项通常是pytestcoveragepytest-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
  • 最后一项详细说明了在测试脚本成功完成之后要运行的命令。在本例中,我们只想将测试覆盖率结果报告给codecov.io,因此我们运行了相应软件包的命令。它将获取我们的pytest运行生成的覆盖率报告并将其发布到那里。如果您想要任何构建的覆盖率结果(无论是失败还是成功),只需将此行作为script项的第二个项目即可。

Travis 构建

使用此配置,对仓库的每次提交都将在 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 构建矩阵,并且这些矩阵在稍后将被证明至关重要,因此请与我一起完成这些步骤。

构建矩阵和 Python 3.7

在 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 软件包。您是一位负责任的开源贡献者。为您欢呼。

但是,如果您的 Python 项目不是纯血统的,而是一个包含一些专门的 C++ 代码的麻瓜呢?或者,也许您的代码是纯 Python 的,但它以非平凡的方式与操作系统进行交互(例如,写入文件、处理线程或进程等),这在不同的操作系统之间会有所不同呢?

如果是这样,如果您希望您的项目支持所有三个主要操作系统,那么您绝对应该在所有三个主要操作系统上测试您的代码(并可能还构建它)。

对我自己来说,在两个项目中出现了这种需求,这两个项目都是纯 Python 的

  1. 第一个是Cachier,它是一个提供持久、无陈旧、本地和跨机器缓存的软件包,用于 Python 函数。当写入文件时,我必须锁定文件以实现多线程安全性,事实证明我使用的内置解决方案(使用fcntl)在 Windows 用户第一次尝试使用我的软件包时就失败了。
  2. 第二个是Skift,它实现了针对 Python fastTextscikit-learn 包装器。该实现需要以不同的编码写入和读取文件,事实证明,在某些情况下,这在不同的操作系统上的行为会有所不同。

我最终确定了一个解决方案,即扩展 Travis 构建矩阵以包括操作系统和主要 Python 版本的特定组合,每个组合都将在自己的作业中运行,在完全独立的环境中运行。

同样,当将这种方法与使用tox进行比较时,我会说主要优势在于

  1. 将复杂性和责任从您转移到 Travis。
  2. 获得更准确的真实环境表示:直接在操作系统级别安装单个版本的纯 Python,而不是通过 tox 安装。这是大多数小型开源 Python 项目的用户安装您代码的方式。
  3. 一个作业 == 一个操作系统版本和一个 Python 版本。您可以立即看到,如果构建失败是因为您的测试在特定 Python 版本(例如,所有操作系统上的 2.7)上失败,或者是在特定操作系统(所有 Python 版本上的 Windows)上失败,或者是在特定组合上失败。这从作业视图中非常明显地显示出来

希望我已经说服您这是一种有效的跨操作系统测试方法,因此我们可以转到具体细节。我们将从在 macOS 上进行测试开始,最后介绍 Windows。

在 macOS 上测试 Python 项目

在撰写本文时,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_2
  • xcode9.4 — 预装了 Python 3.6.5
  • xcode10.2 — 预装了 Python 3.7.3

不幸的是,我还没有找到预装了 Python 3.5 的 Travis Xcode 镜像(如果您找到,请告诉我!)。

所以您已经获得了正确的 Xcode 标签。然而,您仍然需要调整一些构建命令。例如,对于 Python 3 版本,我们需要显式地调用 pip3python3 来安装和调用(分别)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 的工作完成了。来块饼干吧。

在 Windows 上测试 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

如您所见,通用的 scriptafter_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-devpython 键下)来针对不同 Python 版本的开发分支测试您的代码。一个重要的开发分支可以测试的是 3.8-dev,以便为将来做好准备。您可能还想允许使用开发分支的所有任务失败。

基于 Python 版本或 OS 的逻辑

我提供的解决方案将大多数针对 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 免费试用版.

© 版权所有 2024,保留所有权利
© 版权所有 2024,保留所有权利
© 版权所有 2024,保留所有权利