编写测试#

为了帮助保持 Bokeh 的可维护性,所有添加或更新代码的 Pull Request 都应包含新的或更新的测试。虽然可能存在例外情况,但通常情况下,没有充分测试的 Pull Request 将不被视为可以合并。

遵循这些通用指南来决定要更新或添加哪种类型的测试

当您编辑 Bokeh 的 Python 代码时

检查您是否应该更新或添加 Python 测试

当您更改任何与 BokehJS 相关的内容时

检查您是否应该更新或添加 JavaScript 测试

当您修复 Bokeh issue 跟踪器中的 bug 时

检查您是否应该添加 回归测试

在编写任何测试之前,您应该确保通读 运行测试 中的相关章节。本关于编写测试的章节假设您知道如何运行您正在处理的测试。

编写 Python 测试#

如果您的更改全部或部分影响了 Bokeh 的 Python 代码,您应该添加或更新相关的单元测试和集成测试。

这些测试位于 tests 文件夹中。有关如何运行它们的更多信息,请参阅 运行 Python 测试

有关贡献 Bokeh 的 Python 代码和模型的通用信息,请参见 贡献 Python 代码

Python 单元测试#

Python 单元测试有助于维护 Bokeh Python 部分的基本功能。它们位于 tests/unit/bokeh 中。文件夹结构类似于 Bokeh 的 Python 模型 的结构。每个测试文件的名称都以 test_ 开头,后跟模块的名称。

编写 Python 单元测试时,请遵循以下通用指南

使用 as 导入被测模型

始终使用 import as 语法导入您正在测试的特定模型。使用模型的首字母来命名您的导入。例如

import bokeh.plotting.graph as bpg
使用绝对导入

Bokeh 的单元测试应尽可能可重定位和明确。因此,您应尽可能在测试文件中使用绝对导入 (from bokeh.embed import components)。不要使用相对导入 (from ..document import Document)。

使用 pytest (而不是 unittest)

所有新测试都应使用并假定 pytest 用于所有与测试相关的方面,例如测试运行、fixture 或参数化测试。请不要使用 Python 标准库的 unittest 模块。

编写 JavaScript 测试 (BokehJS)#

为了维护所有 BokehJS 组件的功能,Bokeh 包含各种用 TypeScript 编写的测试。如果您的更改全部或部分影响了 BokehJS 的 JavaScript 代码,您应该添加或更新相关的 BokehJS 测试。

Bokeh 的 JavaScript 测试使用自定义测试框架,该框架需要 Google Chrome 或 Chromium。您的系统上需要安装最新版本的这些浏览器之一才能使用这些测试。

MochaJasmine 等其他几个测试框架类似,BokehJS 测试框架使用 describe()it() 函数来设置测试。断言使用 expect()assert() 进行。当您需要缩小类型时,请使用 assert()

BokehJS 测试位于 bokehjs/test 中。有关如何运行它们的更多信息,请参阅 运行 JavaScript 测试

有关贡献 BokehJS 的通用信息,请参见 贡献 BokehJS 代码

BokehJS 单元测试#

BokehJS 单元测试有助于确保 BokehJS 的各个部分按预期运行。BokehJS 的单元测试位于 bokehjs/test/unit/ 文件夹和子文件夹中。

用于编写 Bokeh 测试框架测试的基本结构部分灵感来自 Chai “expect” 断言风格。有关一些通用想法,请参阅 Chai Assertion Library 的 API 文档

expect() 与以下元素一起使用,为 BokehJS 测试框架创建断言

  • tobe:用于提高断言的可读性并连接元素的 token

  • not:否定以下断言

  • throw:断言抛出错误。接受以下可选参数:error_type(按 Error 过滤)和 pattern(按正则表达式或字符串过滤)。

  • equal:断言(深层)值相等。需要一个操作数进行比较。

  • similar:基于与 equal 相同的值相等性,断言在定义的容差范围内的相似性。需要一个操作数进行比较,以及一个可选的 number 作为 tolerance

  • identical:断言严格相等 (===)。需要一个操作数进行比较。

  • instanceof:断言被测元素是给定构造函数的实例。需要一个 Constructor 进行测试。

  • undefined:断言与 undefined 严格相等 (===)

  • null:断言与 null 严格相等 (===)

  • true:断言与 true 严格相等 (===)

  • false:断言与 false 严格相等 (===)

  • NaN:断言被测元素是 NaN

  • empty:断言长度为 0(例如,空字符串或不包含任何可检索值的可迭代对象)

  • below:断言被测元素低于 (<) 某个值。需要一个 number 进行比较。

  • above:断言被测元素低于 (>) 某个值。需要一个 number 进行比较。

例如

expect(m.name).to.be.null
expect(grid0).to.be.instanceof(Column)
expect(h.msgid).to.not.be.equal(h2.msgid)

BokehJS 可视化测试#

BokehJS 使用可视化回归测试作为集成测试。这些基线比较测试有助于确保 Bokeh 的可视化输出与设计预期的输出一致。任何导致 BokehJS 生成的可视化输出发生更改的与 BokehJS 相关的 pull request 都应包含可视化基线比较测试。

在后台,BokehJS 测试框架运行一个无头浏览器并截取浏览器输出的屏幕截图。然后,测试框架将可视化输出与每个测试的单独基线文件进行比较。

test:integration 中的每个测试都包含两种类型的基线比较

文本基线比较

对于每个测试,测试框架将可视化输出中某些元素的像素位置与基线数据中的像素位置进行比较。此基线数据以纯文本形式存储在每个测试各自的 .blf 文件中。

可视化基线比较

对于每个测试,测试框架都会对屏幕截图和基线图像进行逐像素比较。这些基线图像存储为 .png 文件。与文本基线比较相比,可视化基线比较是平台相关的。即使字体渲染方面的细微差异也会导致逐像素比较失败。

可视化基线比较测试位于 bokehjs/test/integration/ 文件夹和子文件夹中。Bokeh 的 CI 在 Linux、macOS 和 Windows 环境中运行这些测试。每个环境的基线文件都位于 bokehjs/test/baselines/ 文件夹中。

按照以下步骤编写新的可视化测试或更新现有测试

  1. 创建或更新可视化测试脚本

    要为 BokehJS 测试框架编写可视化测试,请首先从测试框架的 _util 模块(位于 bokehjs/test/integration/ 中)导入 display()fig() 函数

    import {display, fig} from "./_util"
    

    编写测试时,请将标准的 BokehJS show() 函数替换为 _util 中的 display() 函数。display() 函数接受与 show() 相同的参数,但也会捕获可视化输出以进行比较。

    类似地,将标准的 BokehJS figure() 函数替换为 _util 中的 fig() 函数。fig() 函数期望一个 [width, height] 数组作为第一个参数,后跟与 figure() 相同的参数。但是,为了使可视化测试尽可能高效,您应该尽可能只使用 widthheight

    保持测试绘图的宽度和高度尽可能小,同时仍然能够用肉眼看到您要测试的细节。尽量将绘图上的元素数量保持在最低限度。

    遵循可视化测试的通用模式

    describe("Your Object", () => {
      it("should show certain behavior", async () => {
        const p = fig([width, height], {figure_attrs})
    
        ...
    
        await display(p)
      })
    })
    

    要更改可视化测试的灵敏度,您可以选择设置阈值。阈值表示在测试失败之前,测试图像可以与基线图像不同的像素量。要设置阈值,请使用 it.allowing(threshold)。例如

    describe("Your Object", () => {
      it.allowing(16)("should show certain behavior", async () => {
    

    在提交 TypeScript 文件之前,始终运行 node make lint

  2. 在本地运行测试

    运行 node make tests 以在您的系统上测试您的更改。要仅运行集成测试,请使用 node make test:integration

    如果您只想运行特定的测试,请使用 -k 参数并提供搜索字符串。搜索字符串区分大小写。BokehJS 测试框架会尝试将您的搜索字符串与代码的 describe()it() 函数中定义的字符串进行匹配。例如

    $ node make test:integration -k 'Legend annotation'
    

    首次运行新的或更新的可视化测试时,BokehJS 测试框架会通知您基线文件缺失或已过时。此时,它还会为您的操作系统生成所有缺失或过时的基线文件。基线文件将位于 bokehjs/test/baselines/ 的子文件夹中。

    使用 BokehJS devtools 服务器 来查看您的本地测试结果。或者,您可以使用任何 PNG 查看器来检查生成的 PNG 文件。调整您的测试代码,直到测试的可视化输出符合您的预期。

  3. 生成 CI 基线并提交测试

    在将您的可视化测试推送到 Bokeh 的 GitHub 存储库之前,作为最后一步,您需要使用 Bokeh 的 CI 生成并提交基线文件。

    基线文件是平台相关的。这就是为什么只有当您上传由 CI 创建的基线文件(而不是本地创建的文件)时,CI 才能可靠地工作。

    在使用 Bokeh 的 CI 生成新的基线图像之前,请 rebase 您的分支,以确保所有测试都是最新的。

    请按照以下步骤生成必要的基线文件并将它们上传到 Bokeh 的 CI

    1. 将您的更改推送到 GitHub 并等待 CI 完成。

    2. CI 预计会失败,因为基线图像要么缺失(如果您创建了新测试),要么已过时(如果您更新了现有测试)。

    3. 在 CI 运行完成后,转到 Bokeh 的 GitHubCI 页面。找到您 PR 的最新测试运行,并下载关联的 bokehjs-report artifact。

    4. 将下载的 artifact 文件解压缩到您本地 Bokeh 存储库的根文件夹中。

    5. 使用 devtools 服务器 来查看 CI 为每个平台创建的基线文件:首先,转到 /integration/report?platform=linux,然后转到 /integration/report?platform=macos,最后转到 /integration/report?platform=windows

    6. 如果您没有检测到任何意外的差异,请提交文件夹 bokehjs/test/baselines/linux 中所有新的或修改过的 *.blf*.png 文件。

    7. 再次将您的更改推送到 GitHub,并验证这次测试是否通过。

注意

请确保仅将 CI 为您的特定 pull request 创建的基线文件推送到 CI。请勿在您的 pull request 中包含任何本地创建的基线文件。

从 CI 下载并解压缩基线文件后,检查您本地的 bokehjs/test/baselines 目录中是否有任何不是您更改一部分的已修改文件。请确保仅提交您的 pull request 所必需的基线文件。在每次测试运行失败后,使用 git cleangit clean -f 重置 baselines 目录。

如果您在执行此处描述的步骤时遇到任何问题,请随时通过 Bokeh DiscourseBokeh 的贡献者 Slack 与我们联系。

BokehJS 回归测试#

此外,BokehJS 在其单元测试和集成测试中使用了回归测试。回归测试位于 bokehjs/test/unit/regressions.tsbokehjs/test/integration/regressions.ts

每当您修复与 BokehJS 相关的错误时,都应该添加回归测试。编写您的回归测试,使其在您修复的错误再次发生时失败。

将您的测试函数添加到最外层的 describe() 函数中,该函数将 "Bug" 作为其 description 参数传递给它。将 bug 的 issue number 添加到您的测试的 describe() 函数中,并在您的 it() 函数中提供已修复 bug 的简短描述。

例如

describe("in issue #9522", () => {
  it("disallows arrow to be positioned correctly in stacked layouts", async () => {

    ...

    await display(row([p1, p2]))
  })
})

使用示例测试#

Bokeh 的示例测试基于 examples 文件夹中找到的示例。

当您向这些文件夹之一添加新示例时,它们通常会自动包含在示例测试中。编辑 tests/examples.yaml 以显式包含或排除特定示例。

有关运行示例测试的更多信息,请参阅 运行示例测试