#test-cases #env-var #cargo-test #skip #macro #test-macro

skipif

根据编译时条件,将测试用例转换为空操作,并在其名称后附加"_SKIPPED"。

1 个不稳定版本

0.1.0 2024年7月14日

#290测试

Download history 104/week @ 2024-07-12 9/week @ 2024-07-19

每月113 次下载

Apache-2.0

20KB
273

skipif

skipif 提供了一个名为 skip_if 的属性宏。此宏允许用户指定如果满足指定条件,则应跳过Rust测试用例。skip_if 当前支持以下条件

  • missing_env(VAR1, VAR2, ...)。如果在 cargo test 进程中没有设置指定的环境变量,则应跳过测试用例。

这对于与外部系统集成(如Kubernetes、数据库、云API等)的测试很有用。

当满足一个或多个指定条件时,该宏将重写测试用例名称,在其后附加 _SKIPPED,以避免测试用例看起来像通常需要环境变量来指定外部集成配置的情况。

动机

编程语言的测试运行器通常提供了一种在运行时以编程方式标记测试用例为跳过的方法。一个很好的例子是Go语言,其中测试用例可以调用 testing.T.SkipNow 方法 来指定当前正在运行的测试用例应标记为跳过。在Go语言的情况下,由程序员决定测试用例是否应跳过任何原因,这就是他们这样做的方式。

在Rust中,我们有一个内建的#[ignore]属性宏,但它缺乏在运行时程序化地确定是否跳过特定测试的机制。因此,我们最好的办法是使用类似于skip_if的宏或者提前返回以指示成功或失败。例如

#[test]
fn my_supercool_test_succeeds() {
  if std::env::var("DATABASE_URL").is_err() {
    // variable is unset. oh well, i guess this test passes
    return;
  }
}

#[test]
fn my_supercool_test_fails() {
  // variable is unset. oh well, i guess this test fails
  assert!(std::env::var("DATABASE_URL").is_ok());
}

skip_if是如何工作的

这是一个使用skip_if的测试用例的样子

#[skipif::skip_if(missing_env(DATABASE_URL))]
#[test]
fn my_supercool_test() -> std::result::Result<(), ()> {
  assert!(false);
}

cargo test编译的宏展开阶段,该宏检查是否存在名为DATABASE_URL的环境变量。如果找到,它将展开成基本相同的测试函数。如果没有找到该变量,则发生以下情况

  • 测试名称会被追加_SKIPPED
  • 测试主体被移除,使得测试用例 trivially 通过
  • 测试函数输出签名被设置为-> ()

因此,我们最终得到一个看起来像的测试用例

#[test]
fn my_supercool_test_SKIPPED() {}

与上面显示的my_supercool_test_succeeds示例相比,优势在于我们得到的测试套件输出不是以下内容

test my_supercool_test ... ok

而是

test my_supercool_test_SKIPPED ... ok

结尾的_SKIPPED给开发者提供了必要的信息,表明测试用例不仅通过了,而且还被跳过了。

许多人可能更喜欢上面my_supercool_test_fails示例中的行为,这完全可以。skip_if是为那些不希望这种行为的人准备的。

注意事项

属性宏的顺序

属性宏的顺序在这里很重要。必须将[skipif::skip_if(...)]宏放在任何类似[test]的宏(例如[tokio::test][sqlx::test])之前。如果skip_if宏没有机会重命名测试函数,那么类似于[#test]的宏将捕获错误的函数名,以便在测试main中执行。

重新编译

这个宏会导致测试运行时根据测试编译时的条件产生不同的行为。但是,cargo编译步骤不会自动识别,例如,在missing_env条件下,与测试相关的环境变量已经更改。

以上面提到的my_supercool_test为例,想象以下事件序列

  1. DATABASE_URL未设置,然后你运行cargo test
  2. cargo test过程编译了测试二进制文件。
    • 在编译过程中,宏被展开,最终得到my_supercool_test_SKIPPED
  3. 执行 cargo test 进程会运行编译后的二进制文件。
  4. 测试用例 my_supercool_test_SKIPPED 无条件通过。
  5. 你意识到需要设置 DATABASE_URL,因此没有更改任何代码就进行了设置。然后你再次运行 cargo test
  6. 执行 cargo test 进程会运行编译后的二进制文件。
  7. 测试用例 my_supercool_test_SKIPPED 无条件通过。

但是等等,为什么第二次调用 cargo test 没有重新编译测试二进制文件呢?因为 cargo(或 rustc,我不确定)缓存了构建输出,并且只有当编译输入之一更改时才会重新编译。在 my_supercool_testmissing_env 条件的情况下,任意的环境变量不被视为编译输入。

然而,并非一切皆无!有一个解决方案可以通过 [cargo 构建脚本][builtscript] 来解决这个问题。具体来说,你可以使用来自构建脚本的 cargo:rerun-if-env-changed=<SUPERCOOL_ENVVAR> 指令来告诉 cargo<SUPERCOOL_ENVVAR> 视为编译输入。以下是一个为 my_supercool_test 解决问题的示例构建脚本。

fn main() {
  println!("cargo:rerun-if-env-changed=DATABASE_URL")
}

只需将其放入紧挨着 Cargo.tomlbuild.rs 中,当 DATABASE_URL 的值发生变化时,你将得到重新编译的测试用例。这种方法的一个缺点是,每当变量更改时,你的包中的所有其他内容也将重新构建 ¯_(ツ)_/¯。

类似的项目

依赖项

~320–780KB
~18K SLoC