编写测试#

为了帮助保持 Bokeh 的可维护性,所有添加或更新代码的 Pull Request 应该包含新的或更新的测试。虽然例外情况是可能的,但没有充分测试的 Pull Request 通常不会被认为已准备好合并。

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

当您编辑 Bokeh 的 Python 代码时

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

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

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

当您修复 Bokeh 的问题跟踪器中的错误时

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

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

编写 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 用于所有与测试相关的方面,例如测试运行、夹具或参数化测试。请勿使用 Python 标准库的 unittest 模块。

Python 集成测试#

Bokeh 的 Python 侧重集成测试有助于确保 Bokeh 的 Python 代码与 BokehJS 的 TypeScript 代码按预期工作。

Python 集成测试使用 SeleniumChromeDriver。测试脚本位于 tests/integration 中。文件夹结构类似于 Bokeh 的 Python 模型的结构。

Python 集成测试使用 pytest 夹具来处理 Web 驱动程序配置和与 Selenium 的交互。根据您要测试对象的上下文,从 bokeh_model_pagesingle_plot_pagebokeh_server_page 中选择。有关更多详细信息,请参阅 tests/support/plugins/project.py

在添加或更新 Python 集成测试时,请遵循以下指南

使代码尽可能简单

尝试只包含对测试至关重要的内容。将测试重点放在一项特定功能上。如果可能,编写多个小型测试,而不是一个复杂的测试。

尽可能使用 bokeh.models API

尝试使用 Bokeh 的 低级 bokeh.models 接口,而不是更高级的 bokeh.plotting 接口

编写 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 断言库的 API 文档

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

  • tobe:用于提高断言可读性和连接元素的标记

  • 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 相关拉取请求都应包含视觉基线比较测试。

在后台,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 生成新的基线图像之前,请 变基 您的分支以确保所有测试都是最新的。

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

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

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

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

    4. 将下载的工件文件解压缩到本地 Bokeh 代码库的根文件夹中。

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

    6. 如果您没有发现任何意外的差异,请提交 bokehjs/test/baselines/linuxbokehjs/test/baselines/macosbokehjs/test/baselines/windows 文件夹中所有新创建或修改的 *.blf*.png 文件。

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

注意

确保仅将 CI 为您的特定拉取请求创建的基线文件推送到 CI。不要在拉取请求中包含任何本地创建的基线文件。

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

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

BokehJS 回归测试#

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

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

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

例如

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 以明确包含或排除特定示例。

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