Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

控制测试的运行方式

Controlling How Tests Are Run

就像 cargo run 会编译你的代码并运行生成的二进制文件一样,cargo test 会在测试模式下编译你的代码并运行生成的测试二进制文件。由 cargo test 生成的二进制文件的默认行为是并行运行所有测试并捕获测试运行期间生成的输出,从而防止显示这些输出,使得阅读与测试结果相关的输出更加容易。但是,你可以指定命令行选项来更改此默认行为。

Just as cargo run compiles your code and then runs the resultant binary, cargo test compiles your code in test mode and runs the resultant test binary. The default behavior of the binary produced by cargo test is to run all the tests in parallel and capture output generated during test runs, preventing the output from being displayed and making it easier to read the output related to the test results. You can, however, specify command line options to change this default behavior.

一些命令行选项传递给 cargo test,另一些则传递给生成的测试二进制文件。为了区分这两类参数,你先列出传递给 cargo test 的参数,然后是分隔符 --,接着是传递给测试二进制文件的参数。运行 cargo test --help 会显示你可以与 cargo test 一起使用的选项,而运行 cargo test -- --help 会显示你可以在分隔符之后使用的选项。这些选项在 《rustc 手册》中的“测试”部分 也有详细记录。

Some command line options go to cargo test, and some go to the resultant test binary. To separate these two types of arguments, you list the arguments that go to cargo test followed by the separator -- and then the ones that go to the test binary. Running cargo test --help displays the options you can use with cargo test, and running cargo test -- --help displays the options you can use after the separator. These options are also documented in the “Tests” section of The rustc Book.

并行或连续运行测试

Running Tests in Parallel or Consecutively

当你运行多个测试时,默认情况下它们使用线程并行运行,这意味着它们可以更快地完成运行,你也能更早地得到反馈。由于测试是同时运行的,你必须确保你的测试不相互依赖,也不依赖于任何共享状态,包括共享环境(如当前工作目录或环境变量)。

When you run multiple tests, by default they run in parallel using threads, meaning they finish running more quickly and you get feedback sooner. Because the tests are running at the same time, you must make sure your tests don’t depend on each other or on any shared state, including a shared environment, such as the current working directory or environment variables.

例如,假设你的每个测试都运行一些代码,这些代码会在磁盘上创建一个名为 test-output.txt 的文件并向该文件写入一些数据。然后,每个测试读取该文件中的数据并断言该文件包含一个特定的值,而这个值在每个测试中都是不同的。由于测试同时运行,一个测试可能会在另一个测试写入和读取文件之间的时间段内重写该文件。那么第二个测试就会失败,这不是因为代码不正确,而是因为测试在并行运行时相互干扰。一种解决方案是确保每个测试写入不同的文件;另一种解决方案是一个接一个地运行测试。

For example, say each of your tests runs some code that creates a file on disk named test-output.txt and writes some data to that file. Then, each test reads the data in that file and asserts that the file contains a particular value, which is different in each test. Because the tests run at the same time, one test might overwrite the file in the time between when another test is writing and reading the file. The second test will then fail, not because the code is incorrect but because the tests have interfered with each other while running in parallel. One solution is to make sure each test writes to a different file; another solution is to run the tests one at a time.

如果你不想并行运行测试,或者如果你想对所使用的线程数进行更细粒度的控制,你可以向测试二进制文件发送 --test-threads 标志和你想要使用的线程数。请看以下示例:

If you don’t want to run the tests in parallel or if you want more fine-grained control over the number of threads used, you can send the --test-threads flag and the number of threads you want to use to the test binary. Take a look at the following example:

$ cargo test -- --test-threads=1

我们将测试线程数设置为 1,告诉程序不要使用任何并行性。使用一个线程运行测试会比并行运行花费更长的时间,但如果测试共享状态,它们就不会相互干扰。

We set the number of test threads to 1, telling the program not to use any parallelism. Running the tests using one thread will take longer than running them in parallel, but the tests won’t interfere with each other if they share state.

显示函数输出

Showing Function Output

默认情况下,如果测试通过,Rust 的测试库会捕获打印到标准输出的所有内容。例如,如果我们在测试中调用 println! 且测试通过了,我们就不会在终端看到 println! 的输出;我们只会看到指示测试通过的那一行。如果测试失败,我们会在失败消息的其余部分看到打印到标准输出的所有内容。

By default, if a test passes, Rust’s test library captures anything printed to standard output. For example, if we call println! in a test and the test passes, we won’t see the println! output in the terminal; we’ll see only the line that indicates the test passed. If a test fails, we’ll see whatever was printed to standard output with the rest of the failure message.

作为一个例子,示例 11-10 有一个愚蠢的函数,它打印其参数的值并返回 10,以及一个通过的测试和一个失败的测试。

As an example, Listing 11-10 has a silly function that prints the value of its parameter and returns 10, as well as a test that passes and a test that fails.

fn prints_and_returns_10(a: i32) -> i32 {
    println!("I got the value {a}");
    10
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn this_test_will_pass() {
        let value = prints_and_returns_10(4);
        assert_eq!(value, 10);
    }

    #[test]
    fn this_test_will_fail() {
        let value = prints_and_returns_10(8);
        assert_eq!(value, 5);
    }
}

当我们使用 cargo test 运行这些测试时,我们将看到以下输出:

When we run these tests with cargo test, we’ll see the following output:

$ cargo test
   Compiling silly-function v0.1.0 (file:///projects/silly-function)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.58s
     Running unittests src/lib.rs (target/debug/deps/silly_function-160869f38cff9166)

running 2 tests
test tests::this_test_will_fail ... FAILED
test tests::this_test_will_pass ... ok

failures:

---- tests::this_test_will_fail stdout ----
I got the value 8

thread 'tests::this_test_will_fail' panicked at src/lib.rs:19:9:
assertion `left == right` failed
  left: 10
 right: 5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace


failures:
    tests::this_test_will_fail

test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

error: test failed, to rerun pass `--lib`

请注意,在此输出中,我们没有在任何地方看到 I got the value 4,该行是在通过的测试运行时打印的。该输出已被捕获。来自失败测试的输出 I got the value 8 出现在测试摘要输出的部分,该部分还显示了测试失败的原因。

Note that nowhere in this output do we see I got the value 4, which is printed when the test that passes runs. That output has been captured. The output from the test that failed, I got the value 8, appears in the section of the test summary output, which also shows the cause of the test failure.

如果我们也想看到通过测试的打印值,我们可以告诉 Rust 也显示成功测试的输出,使用 --show-output

If we want to see printed values for passing tests as well, we can tell Rust to also show the output of successful tests with --show-output:

$ cargo test -- --show-output

当我们再次使用 --show-output 标志运行示例 11-10 中的测试时,我们看到以下输出:

When we run the tests in Listing 11-10 again with the --show-output flag, we see the following output:

$ cargo test -- --show-output
   Compiling silly-function v0.1.0 (file:///projects/silly-function)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.60s
     Running unittests src/lib.rs (target/debug/deps/silly_function-160869f38cff9166)

running 2 tests
test tests::this_test_will_fail ... FAILED
test tests::this_test_will_pass ... ok

successes:

---- tests::this_test_will_pass stdout ----
I got the value 4


successes:
    tests::this_test_will_pass

failures:

---- tests::this_test_will_fail stdout ----
I got the value 8

thread 'tests::this_test_will_fail' panicked at src/lib.rs:19:9:
assertion `left == right` failed
  left: 10
 right: 5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace


failures:
    tests::this_test_will_fail

test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

error: test failed, to rerun pass `--lib`

通过名称运行测试子集

Running a Subset of Tests by Name

运行完整的测试套件有时会花费很长时间。如果你正在开发特定区域的代码,你可能只想运行与该代码相关的测试。你可以通过将你想要运行的一个或多个测试名称作为参数传递给 cargo test 来选择要运行的测试。

Running a full test suite can sometimes take a long time. If you’re working on code in a particular area, you might want to run only the tests pertaining to that code. You can choose which tests to run by passing cargo test the name or names of the test(s) you want to run as an argument.

为了演示如何运行测试子集,我们首先为 add_two 函数创建三个测试,如示例 11-11 所示,并选择运行哪些测试。

To demonstrate how to run a subset of tests, we’ll first create three tests for our add_two function, as shown in Listing 11-11, and choose which ones to run.

pub fn add_two(a: u64) -> u64 {
    a + 2
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn add_two_and_two() {
        let result = add_two(2);
        assert_eq!(result, 4);
    }

    #[test]
    fn add_three_and_two() {
        let result = add_two(3);
        assert_eq!(result, 5);
    }

    #[test]
    fn one_hundred() {
        let result = add_two(100);
        assert_eq!(result, 102);
    }
}

正如我们之前看到的,如果我们不传递任何参数地运行测试,所有测试都将并行运行:

If we run the tests without passing any arguments, as we saw earlier, all the tests will run in parallel:

$ cargo test
   Compiling adder v0.1.0 (file:///projects/adder)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.62s
     Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)

running 3 tests
test tests::add_three_and_two ... ok
test tests::add_two_and_two ... ok
test tests::one_hundred ... ok

test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

   Doc-tests adder

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

运行单个测试

Running Single Tests

我们可以将任何测试函数的名称传递给 cargo test 以仅运行该测试:

We can pass the name of any test function to cargo test to run only that test:

$ cargo test one_hundred
   Compiling adder v0.1.0 (file:///projects/adder)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.69s
     Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)

running 1 test
test tests::one_hundred ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 2 filtered out; finished in 0.00s

只有名为 one_hundred 的测试运行了;另外两个测试与该名称不匹配。测试输出通过在末尾显示 2 filtered out 来告知我们还有更多测试未运行。

Only the test with the name one_hundred ran; the other two tests didn’t match that name. The test output lets us know we had more tests that didn’t run by displaying 2 filtered out at the end.

我们不能以这种方式指定多个测试的名称;只会使用传递给 cargo test 的第一个值。但有一种方法可以运行多个测试。

We can’t specify the names of multiple tests in this way; only the first value given to cargo test will be used. But there is a way to run multiple tests.

过滤以运行多个测试

Filtering to Run Multiple Tests

我们可以指定测试名称的一部分,任何名称与该值匹配的测试都会被运行。例如,由于我们的两个测试名称中包含 add,我们可以通过运行 cargo test add 来运行这两个测试:

We can specify part of a test name, and any test whose name matches that value will be run. For example, because two of our tests’ names contain add, we can run those two by running cargo test add:

$ cargo test add
   Compiling adder v0.1.0 (file:///projects/adder)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.61s
     Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)

running 2 tests
test tests::add_three_and_two ... ok
test tests::add_two_and_two ... ok

test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 1 filtered out; finished in 0.00s

此命令运行了名称中包含 add 的所有测试,并过滤掉了名为 one_hundred 的测试。还要注意,测试所在的模块也会成为测试名称的一部分,因此我们可以通过对模块名称进行过滤来运行一个模块中的所有测试。

This command ran all tests with add in the name and filtered out the test named one_hundred. Also note that the module in which a test appears becomes part of the test’s name, so we can run all the tests in a module by filtering on the module’s name.

除非特别请求,否则忽略某些测试

Ignoring Tests Unless Specifically Requested

有时,少数特定的测试可能会执行起来非常耗时,因此你可能希望在大多数 cargo test 运行时排除它们。与其将你想要运行的所有测试都列为参数,不如使用 ignore 属性标注耗时的测试来排除它们,如下所示:

Sometimes a few specific tests can be very time-consuming to execute, so you might want to exclude them during most runs of cargo test. Rather than listing as arguments all tests you do want to run, you can instead annotate the time-consuming tests using the ignore attribute to exclude them, as shown here:

文件名:src/lib.rs Filename: src/lib.rs

pub fn add(left: u64, right: u64) -> u64 {
    left + right
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn it_works() {
        let result = add(2, 2);
        assert_eq!(result, 4);
    }

    #[test]
    #[ignore]
    fn expensive_test() {
        // code that takes an hour to run
    }
}

#[test] 之后,我们将 #[ignore] 行添加到想要排除的测试中。现在当我们运行测试时,it_works 会运行,而 expensive_test 不会运行:

After #[test], we add the #[ignore] line to the test we want to exclude. Now when we run our tests, it_works runs, but expensive_test doesn’t:

$ cargo test
   Compiling adder v0.1.0 (file:///projects/adder)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.60s
     Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)

running 2 tests
test tests::expensive_test ... ignored
test tests::it_works ... ok

test result: ok. 1 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 0.00s

   Doc-tests adder

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

expensive_test 函数被列为 ignored。如果我们只想运行被忽略的测试,我们可以使用 cargo test -- --ignored

The expensive_test function is listed as ignored. If we want to run only the ignored tests, we can use cargo test -- --ignored:

$ cargo test -- --ignored
   Compiling adder v0.1.0 (file:///projects/adder)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.61s
     Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)

running 1 test
test tests::expensive_test ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 1 filtered out; finished in 0.00s

   Doc-tests adder

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

通过控制运行哪些测试,你可以确保你的 cargo test 结果能够快速返回。当你到了需要检查 ignored 测试结果且有时间等待结果的时候,你可以运行 cargo test -- --ignored。如果你想运行所有测试,无论它们是否被忽略,你可以运行 cargo test -- --include-ignored

By controlling which tests run, you can make sure your cargo test results will be returned quickly. When you’re at a point where it makes sense to check the results of the ignored tests and you have time to wait for the results, you can run cargo test -- --ignored instead. If you want to run all tests whether they’re ignored or not, you can run cargo test -- --include-ignored.