编写测试#

为了帮助保持Bokeh的可维护性,所有Pull Requests在添加或更新代码时都应包含新的或更新的测试。虽然可能有例外,但如果没有足够的测试,Pull Request通常不会被考虑合并。

遵循以下一般准则来决定更新或添加哪些类型的测试:

When you edit Bokeh’s Python code

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

When you change anything related to BokehJS

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

When you fix a bug from Bokeh’s issue tracker:

检查你是否应该添加一个 回归测试

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

编写Python测试#

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

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

有关为Bokeh的Python代码和模型做出贡献的通用信息可在贡献Python代码中找到。

Python 单元测试#

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

编写Python单元测试时,请遵循以下一般准则:

Import the model under test with as

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

import bokeh.plotting.graph as bpg
Use absolute imports

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

Use pytest (not unittest)

所有新测试应使用并假设pytest来处理所有与测试相关的方面,例如测试运行、夹具或参数化测试。请不要使用Python标准库中的unittest模块。

Python集成测试#

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

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

Python集成测试使用pytest fixtures来处理web驱动配置和与Selenium的交互。根据你想测试对象的上下文,选择bokeh_model_pagesingle_plot_pagebokeh_server_page。更多详情请参见tests/support/plugins/project.py

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

Keep your code as simple as possible

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

Use the bokeh.models API whenever possible

尝试使用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做出贡献的一般信息可在 Contributing to 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 中的每个测试包含两种类型的基线比较:

Textual baseline comparison

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

Visual baseline comparison

对于每个测试,测试框架会对截图和基线图像进行逐像素比较。这些基线图像存储为.png文件。与文本基线比较不同,视觉基线比较是平台相关的。例如,即使是字体渲染的微小差异,也会导致逐像素比较失败。

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

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

  1. Create or update visual testing scripts:

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

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

    在编写测试时,用_util中的display()函数替换标准的BokehJS show()函数。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. Run tests locally:

    运行 node make tests 在您的系统上测试您的更改。如果只想运行集成测试,请使用 node make test:integration

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

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

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

    使用BokehJS devtools server 来查看你的本地测试结果。你也可以选择使用任何PNG查看器来检查生成的PNG文件。调整你的测试代码,直到测试的视觉输出符合你的预期。

  3. Generate CI baselines and commit test:

    在将您的视觉测试推送到Bokeh的GitHub仓库之前,最后一步是使用Bokeh的CI生成并提交基线文件。

    基线文件是平台相关的。这就是为什么如果你上传由CI创建的基线文件,而不是本地创建的文件,CI才能可靠地工作。

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

    按照以下步骤生成必要的基线文件并将其上传到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. 如果您没有检测到任何意外的差异,请提交所有新的或修改的 *.blf*.png 文件,这些文件来自文件夹 bokehjs/test/baselines/linux, bokehjs/test/baselines/macos, 和 bokehjs/test/baselines/windows

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

注意

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

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

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

BokehJS 回归测试#

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

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

将您的测试函数添加到最外层的describe()函数中,该函数将"Bug"作为其description参数传递。将错误的issue编号添加到您的测试的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以明确包含或排除特定示例。

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