测试项目框架

测试项目框架是一组包和 UI 功能,可以轻松编写和运行针对 Julia 包的测试。

此框架的主要优点是,可以将测试代码构建为测试项目,然后可以轻松地分别运行这些项目。

Julia VS Code 扩展对测试项目框架提供了广泛的支持,但测试项目框架本身可以完全独立于 VS Code 使用。可以编写测试项目并使用命令行界面(或只是标准的 Pkg.test 功能)运行这些项目,而无需使用 VS Code。

编写测试项目

测试项目框架的核心功能是,可以将测试构建为 @testitem 块,然后分别运行这些测试,而无需一次运行所有测试。典型的 @testitem 可能如下所示

@testitem "First tests" begin
    x = foo("bar")

    @test length(x)==3
    @test x == "bar"
end

@testitem 始终有一个名称(此处为“第一次测试”),然后在 begin ... end 块中是一些代码。@testitem 中的代码本身必须可执行,即,它不能依赖于 @testitem 外部出现的代码,除非该代码以某种方式从 @testitem 内部显式导入或包含。对此有一个例外:@testitem 中的代码将在一个临时模块中运行,其中 using Testusing MYPACKAGENAME 已执行,因此可以从 Test 模块或正在开发的程序包导出的任何内容都可以直接使用。在上面的示例中,这适用于 foo 函数(大概在将要测试的程序包中定义)和 @test 宏。

@testitem 可以出现在程序包中的任何位置。它们不必在 test 文件夹中,也不必在 test/runtests.jl 包含的文件中。事实上,@testitem 甚至可以位于常规程序包代码中,例如在正在测试的代码旁边。在这种情况下,您只需要依赖 TestItems.jl 程序包即可访问 @testitem 宏。如果您有一个程序包 MyPackage,则文件 src/MyPackage.jl 的样子可能如下

module MyPackage

using TestItems

export foo

foo(x) = x

@testitem "First tests" begin
    x = foo("bar")

    @test length(x)==3
    @test x == "bar"
end

end

如果您不喜欢这种内联 @testitem 的样式,您也可以将 @testitem 块放入测试文件夹中的 Julia 文件中。

在 VS Code 中运行测试项

在 VS Code 中打开一个 Julia 程序包,并安装了 Julia 扩展 后,它会持续不断(每次按键后!)查找 Julia 文件中的任何和所有 @testitem。如果发现任何 @testitem,它们将出现在界面的各个位置。

您可以在 VS Code 的测试活动栏中找到所有检测到的 @testitem

Test activity bar

然后,测试活动区域为您提供运行单个 @testitem、查看结果等的选项。

VS Code 还将在文本编辑器本身的每个检测到的 @testitem 旁边放置一个小小的运行按钮

Test run button

除了所有允许您运行测试的这些 UI 元素之外,还有 UI 来显示测试结果。例如,当您运行测试并且其中一些测试失败时,该扩展将收集所有这些测试失败,然后以一种结构化的方式显示它们,直接显示在特定测试失败的代码位置

Test error detail

特别是当您使用大型测试文件运行了许多测试时,这样更容易找到失败的特定测试,无需再在 REPL 中查找文件和行信息了!

命令行测试运行

可以使用 TestItemRunner.jl 包来运行 @testitem,作为传统 Pkg.test 工作流程的一部分。

要为使用 @testitem 的包启用与 Pkg.test 的集成,只需要做两件事

  1. TestItemRunner.jl 作为测试依赖项添加到包
  2. 将以下代码放入包的 test/runtests.jl 文件
using TestItemRunner

@run_package_tests

标签

现在可以将标签添加到 @testitem。可以在 VS Code UI 和 TestItemRunner.jl 中使用标签,以筛选要运行的测试项。

添加标签的语法如下

@testitem "My testitem" tags=[:skipci, :important] begin
    x = foo("bar")

    @test length(x)==3
    @test x == "bar"
end

然后,可以使用相同的标签在 VS Code UI 中筛选测试列表

Test item tags

还可以在 test/runtests.jl 中使用标签,以筛选将通过传统 Pkg.test 入口点运行的测试列表

using TestItemRunner

@run_package_tests filter=ti->!(:skipci in ti.tags)

下面的 筛选 部分更全面地描述了 @run_package_tests 宏的新筛选关键字。

VS Code 中的并行测试执行

VS Code 扩展有一个设置,可控制并行测试执行要使用多少个 Julia 进程

Test num test processes

默认值为 1,因此必须更改它才能使用并行测试执行功能。值为 0 将使用与处理器数量相同的测试进程。

在配置了多个测试进程后,各个 @testitem 将并行运行。

这里有一个权衡:更多的测试进程意味着需要更多的内存,并且还需要额外的开销让所有进程启动并准备好实际运行 @testitem

管理测试进程

通过 VS Code 中此新测试 UI 启动的测试进程不会自动终止,即它们会一直存在,占用内存和其他资源。当然,这有很多好处,即一旦测试进程启动并运行,就可以非常快速地执行 @testitem,但在某些情况下,可能仍然希望简单地终止所有当前正在运行的测试进程。

为此,所有测试进程都会显示在 Julia 工作区中,以及可能正在运行的任何 REPL 或 Notebook 进程。并且可以通过单击 停止测试进程 按钮通过此 UI 终止 Julia 测试进程。在此屏幕截图中,有四个测试进程正在运行

TestItemRunner.jl 中支持过滤

可以通过 @run_package_tests 宏传递一个通用过滤器函数来选择要执行哪些 @testitem。上面章节用标签来选择要运行的测试,但也可以根据 @testitem 定义所在的的文件名或 @testitem 的名称进行筛选。

其实现方式是,您可以向 @run_package_tests 宏传递一个过滤器函数。该过滤器函数将针对在项目中检测到的每个 @testitem 调用一次,并且函数必须返回 true (如果应运行此测试项)或返回 false(如果不运行)。@run_package_tests 会将一个包含三个字段的命名元组传递到过滤器函数,其中包含特定测试项的元信息,即字段 filename(定义 @testitem 的文件的完整路径)、name(您定义的 @testitem 的名称)和 tags(一个 Symbol 的向量)。有了这些信息,您可以编写任意复杂的筛选条件。例如,在这里我过滤掉了任何带有 :skipci 标记的 @testitem,并且只运行在一个特定文件中定义的测试

@run_package_tests filter=ti->( !(:skipci in ti.tags) && endswith(ti.filename, "test_foo.jl") )

默认导入选项

当您编写一个 @testitem 时,默认情况下,将通过一个不可见的导入语句导入正在测试的包和 Test 包。在某些情况下可能不需要此项,所以可以通过 default_imports 选项(接受 Bool 值)对每个 @testitem 级别控制此行为。要禁用这些默认导入,您需要编写

@testitem "Another test for foo" default_imports=false begin
    using MyPackage, Test

    x = foo("bar")

    @test x != "bar"
end

请注意,现在我们如何需要手动将行 using MyPackage, Test 添加到我们的 @testitem,以便可以访问 foo 函数和 @test 宏。

@testitem 之间共享代码

默认情况下,@testitem 不会在彼此之间共享任何代码,也没有彼此之间的依赖关系。这些属性使单独运行 @testitem 变得可行,但有时希望在多个 @testitem 之间共享通用代码。测试项框架为此目的提供了两个宏:@testsnippet@testmodule。这两个宏可以出现在包中的任何 .jl 文件中。

测试片段

@testsnippet 是一段代码,创建各个 @testitem 后可在这些 @testitem 运行自己的代码前运行此代码。如果 @testitem 依赖于某个特定的 @testsnippet,那么该代码段将在 @testitem 每次运行时运行一遍。

@testsnippet 的定义可能如下所示

@testsnippet MySnippet begin
    foo = "Hello world"
end

@testitem 可以使用 setup 关键字以这种方式利用此代码段

@testitem "My test item" setup=[MySnippet] begin
    @test foo == "Hello world"
end

测试模块

@testmodule 定义了一个 Julia 模块,该模块可通过 @testitem 访问。此类模块仅在每个 Julia 测试进程中运行一次。例如,如果有两个 @testitem 依赖于 @testmodule,那么该 @testmodule 将仅运行一次评估,然后整个模块将可供两个 @testitem 使用。

@testmodule 的定义可能如下所示

@testmodule MyModule begin
    foo = "Hello world"
end

@testitem 可以再次使用 setup 关键字来利用此模块。与 @testsnippet 不同,@testmodule 的内容在常规 Julia module 中运行,因此需要为在其中定义的任何名称添加测试模块名称前缀才能访问其中的内容。利用刚才定义的 @testmodule@testitem 如下所示

@testitem "My test item" setup=[MyModule] begin
    @test MyModule.foo == "Hello world"
end

请注意,我们在此处使用表达式 MyModule.foo 访问 foo

@testitem 调试

通过 调试测试 命令启动 @testitem,可在调试器中运行 @testitem。可以在 VS Code UI 中的多处访问此命令。在测试主测试视图中,此命令在此处可用

Testitem debugging 1

还可以右键单击文本编辑器中的运行测试图标以选择调试选项

Testitem debugging 2

在调试器中运行测试项时,可以在被测代码或 @testitem 本身中设置断点,然后利用 Julia VS Code调试器中的所有常规功能。

代码覆盖率

在 Julia 1.11 及更高版本中,可以以代码覆盖率模式运行测试项,并直接在 VS Code 中显示代码覆盖率结果。

若要以代码覆盖率模式运行测试项,可以使用命令 在覆盖率下运行测试 启动这些测试。此命令同时存在于主测试视图

Testitem coverage 1

和文本编辑器的上下文菜单中

Testitem coverage 2

然后以多种方式在 VS Code UI 中显示覆盖率结果。例如,摘要视图显示每个文件的覆盖率

Testitem coverage 2

可以在文本编辑器中查看详细的行覆盖率信息

Testitem coverage 2

覆盖率结果也显示在 VS Code UI 常规资源管理器部分的内联中。