ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

The Rust Programming Language - 第11章 测试 - 11.3 测试的组织结构

2021-11-20 13:00:50  阅读:246  来源: 互联网

标签:11 tests ok Language rs adder 测试 test


11 测试

编写自动化测试

程序的正确性代码如我们期望的那样运行,Rust也在语言本身包含了编写软件测试的支持

本章我们会讲到编写测试时用到的注解和宏,运行测试的默认行为和选项,以及如何将测试组织成单元测试和集成测试

11.3 测试的组织结构

测试是一个复杂的概念,且不同的开发者也采取不同的技术和组织。Rust社区通常根据测试的两个分类来解决问题:集成测试和单元测试,从概念上讲,单元测试测试是针对于局部的小范围的功能模块,而集成测试就大而全的多了

总之,为了保证库能够按照预期运行,我们这两个方面都得顾及到

单元测试

单元测试与其要测试的代码共存放于src目录下的相同的文件中。规范是在每个文件中创建包含测试函数的tests模块,并使用cfg(test)标注模块

测试模块和#[cfg(test)]

注解#[cfg(test)]告诉Rust只在执行cargo test时才编译和运行测试代码,而在运行cargo build时不用这么做。当我们在构建库的时候希望节省时间就可以这么做,并且由于它们没有包含测试,所以也能减少编译时产生的文件的大小,与之对应的集成测试因为位于另一个文件中,所以它们并不需要#[cfg(test)]注解

然而单元测试位于与源码相同的文件中,所以我们需要使用#[cfg(test)]来指定它们不应该被包含到编译结果中

#[cfg(test)]
mod tests {
    #[test]
    fn it_works() {
        assert_eq!(2+2,4);
    }
}

来看一段我们非常熟悉的代码,cfg属性告诉Rust其之后的项只应被包含特定配置的选项中,此例配置选项是test,即Rust所提供的用于编译和运行测试的配置选项。通过使用cfg属性,Cargo只会在我们主动使用cargo test时运行测试代码时才编译测试代码。这包括测试模块中,可能存在的帮助函数,以及标注为#[test]的函数

测试私有函数

Rust允许我们测试私有函数

pub fn add_two(a:i32)->i32 {
    internal_adder(a,2)
}
fn internal_adder(a:i32,b:i32) -> i32 {
    a+b
}
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn internal(){
        assert_eq!(4,internal_adder(2, 2))
    }
}

注意:internal_adder函数并没有标记为pub,不过因为测试也不过是Rust代码同时tests也仅仅是另一个模块,我们完全可以在测试中导入和调用internal_adder.当然如果你并不认为应该测试私有函数,Rust也不会强迫你这么做

集成测试

在Rust中,集成测试对于你需要测试的库来说完全是外部的。同其他使用库的代码一样使用库文件,也就是说它们只能调用一部分库中共有的API,集成测试的目的是测试库的多个部分能否一起正常工作。一些单独能正确的运行的代码单元集成在一起也可能会出现问题,所以集成测试的覆盖率也是很重要的。为了创建集成测试,你需要先创建一个tests目录

tests目录

为了编写集成测试,我们需要在项目根目录创建一个tests目录,与src同级。cargo 知道如何去寻找这个目录中的集成测试文件,接着可以随意在这个目录中创建任意多的测试文件,cargo 会将每一个文件当作单独的crate来编译

让我们来创建一个集成测试

adder/tests/integration_test.rs

use adder;

#[test]
fn it_adds_two() {
     assert_eq!(4,adder::add_two(2))
}

与单元测试不同,我们需要在文件顶部添加use adder,这是因为每一个tests目录中的测试文件都是完全独立的crate,所以需要在每一个文件中导入库

并不需要将tests/integration_test.rs中任何代码标注为#[cfg(test)].test 文件夹在cargo 中是一个特殊的文件夹,cargo 只会在运行cargo test时编译这个目录中的文件,现在我们运行试一试

xxx@MacBook-Pro-10 src % cargo test 
    Finished test [unoptimized + debuginfo] target(s) in 0.00s
     Running unittests (/Users/xxx/adder/target/debug/deps/adder-547053d5d968a94b)

running 1 test
test tests::internal ... ok

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

     Running tests/integration_test.rs (/Users/xxx/adder/target/debug/deps/integration_test-e4f0ea9dc56bbe34)

running 1 test
test it_adds_two ... ok

test result: ok. 1 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 1 test
test tests::internal ... ok

第二部分集成测试

	Running tests/integration_test.rs (/Users/xxx/adder/target/debug/deps/integration_test-e4f0ea9dc56bbe34)

running 1 test
test it_adds_two ... ok

第三部分文档测试

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 --test integration_test

running 1 test
test it_adds_two ... ok

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

结果也如我们所料

集成测试中的子模块

随着集成测试的增加,你可能希望在test目录增加更多文件以便更好的组织它们。例如根据测试的功能来将测试分组,正如我们之前提到的,每一个tests目录中的文件都被编译为单独的crate

将每个集成测试文件都当作自己的crate来对待,这更有助于创建单独的作用域,这种单独的作用域能提供更类似与最终使用者使用crate的环境,然而,正如我们之前学习过如何将代码分为模块和文件的知识,test目录中的文件不能像src中的文件那样共享相同的行为

当你有一些在多个集成测试文件都会用到的帮助函数,当我们按照第七章“将模块移动到其他文件”部分的步骤将他们提取到一个通用的模块中时,tests目录中不同文件的行为就会显得很明显。如,如果我们可以创建一个tests/common.rs文件并创建一个名叫setup的函数,我们希望这个函数能被多个测试文件的测试函数调用

adder/tests/common.rs

pub fn setup() {
     //编写特定库测试所需的代码
}
running 1 test
test tests::internal ... ok

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

     Running tests/common.rs (/Users/qinjianquan/adder/target/debug/deps/common-ca82d1fedcca11db)

running 0 tests

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

     Running tests/integration_test.rs (/Users/qinjianquan/adder/target/debug/deps/integration_test-e4f0ea9dc56bbe34)

running 1 test
test it_adds_two ... ok

test result: ok. 1 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

我们看到结果中也有common了

但是我们并不想让common出现在输出中,我们将创建test/common/mod.rs,而不是创建tests/common.rs。这是一种命名规范,这样命名告诉Rust不要将common看作一个集成测试文件。将setup函数代码移动到test/common/mod.rs,并删除test/commo.rs之后输出中将不会出现这一部分

running 1 test
test tests::internal ... ok

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

     Running tests/integration_test.rs (/Users/xxx/adder/target/debug/deps/integration_test-e4f0ea9dc56bbe34)

running 1 test
test it_adds_two ... ok

test result: ok. 1 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

一旦拥有了tests/common/mod.rs,就可以将其作为模块以便在任何集成测试文件中使用,这里是一个tests/integration_test.rs中调用setup函数的 it_add_two测试的例子

use adder;

mod common;

#[test]
fn it_adds_two() {
     common::setup();
     assert_eq!(4,adder::add_two(2))
}
//
running 1 test
test it_adds_two ... ok

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

二进制crate的集成测试

如果项目是二进制crate并且只包含src/main.rs而没有lib.rs,这样就不可能在tests目录创建集成测试并使用extern crate导入src/main.rs中定义的函数,只有库crate才会向其他crate暴露了调用和使用的函数;二进制crate只意在单独运行

为什么Rust二进制项目的结构明确采用src/main.rs调用src/lib.rs中的逻辑方式?因为通过这种结,集成测试就可以通过extern crate测试库crate中的主要功能了,而如果这些重要的功能没有问题的话,src/main.rs中的少量代码也就会正常工作且不需要测试

总结:Rust的测试功能给我么能提供了一种方式,=:改变了函数的实现方式,也能继续以期望的方式运行。单元测试独立的验证库的不同部分,也能够测试私有函数实现细节。集成测试则检查多个部分是否能结合起来正确的工作,并像其他外部代码那样测试库的共有API。即使Rust的类型系统和所有权规则可以帮助你避免一些bug,但是使用测试功能检查代码也非常重要

下一章,让我们来动手实践一个项目

标签:11,tests,ok,Language,rs,adder,测试,test
来源: https://blog.csdn.net/weixin_51487151/article/details/121437860

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有