前言
在上一篇,我们讲了
-
Cargo项目创建
-
Cargo和依赖关系
在本篇中,会涉及
-
Cargo运行测试
-
Cargo运行实例
-
Cargo工作空间
Cargo运行测试
如题,Cargo支持运行测试和基准测试,有关这方面的深度介绍,还要放在下一章。
在本节中,我们将简要介绍如何使用Cargo运行测试。这里将为一个库编写测试,那么首先,用cargo new myexponent--lib来创建一个库。
可见:
库(library)的crate类似于二进制crate,不同的是,这里使用src/lib.rs以及一个简单的测试函数it_works(该函数用#[test]注释标记),替代src/main.rs和内部的主函数入口点。我们可以使用cargo测试,立即运行it_works测试函数,并查看它是否通过。
略微说明一下,一般称crate为库或者包,在Rust中,有library的crate,也有二进制的crate,希望读者不要被翻译中带来的麻烦所迷惑。
结果显示通过。
现在,让我们用Cargo进行测试驱动开发(Test Driven Development,TDD);在这个库里加一个指数函数pow function;为这个函数编写一个测试,最初测试失败,然后增加实现代码,直到能通过。
以下是src/lib.rs文件,已经有了pow函数,但没有任何实现:
// myexponent/src/lib.rsfn pow(base: i64, exponent: usize) -> i64 {
unimplemented!();
}
#[cfg(test)]
mod tests {
use super::pow;
#[test]
fn minus_two_raised_three_is_minus_eight() {
assert_eq!(pow(-2, 3), -8);
}
}
这里创建了一个单独的pow函数,在mod tests内部,有一个名为minus_two_raised_three_is_minus_eight的测试函数,执行一个判断两边是否相等的宏assert_eq !。
如果我们运行cargo test, pow调用的单元测试显然会失败,因为这里有一个unimplemented!() 宏调用
简言之,unimplemented!() 是一个很方便的宏,可以用来标记未完成或稍后运行的代码,但要通过编译。而在运行内部,会调用一个panic!的宏,返回尚未实现的信息"not yet implemented",可以用于实现trait(一般翻译为特性,是Rust中很重要的概念)的多种方法。
例如,你开始实现一个方法,但没有计划实现其他方法。在编译时,如果只将未实现的方法函数置为空的函数体,那么会出现编译错误。
对于这些方法,可以在内部放置一个unimplemented!() 宏调用,以保证通过编译和运行无误。
我们对这个代码潦草的修改一下:
// myexponent/src/lib.rs
pub fn pow(base: i64, exponent: usize) -> i64 {
let mut res = 1;
if exponent == 0 {
return 1;
}
for _ in 0..exponent {
res *= base as i64;
}
res
}
#[cfg(test)]
mod tests {
use super::pow;
#[test]
fn minus_two_raised_three_is_minus_eight() {
assert_eq!(pow(-2, 3), -8);
}
}
显然通过测试了,这还仅仅是基础,一切才刚刚开始。
Cargo运行实例
为了让用户可以快速上手你写的crate,最好给些例子。Cargo将此列为必要元素,也就是在root目录下可以添加一个名为examples的文件夹,里面可以写需要的各种实例文件。
那么我们先写一个:
use myexponent::pow;
fn main() {
println!("8 raised to 2 is {}", pow(8, 2));
}
在该代码导入了myexponent::pow,并调用。结果如下,有了这些,相信读者应该可以创建一下简单的东西了。
Cargo工作空间
随着时间的推移,项目内容会变得相当庞大。现在,将代码的公共部分分割为单独的crate,有助于高效的项目管理。Cargo工作空间就是做这个的,其可以保证在本地的目录中拥有可以共享相同Cargo.lock文件,和一个通用的目标或输出目录。
这里,我们将创建一个合并Cargo工作空间的新项目,该工作空间只是一个包含Cargo.toml的目录。它没有任何[package]部分,但有一个[workspace]部分在其中。
我们创建一个名为workspace_demo的新目录,并添加Cargo.toml文件
# worspace_demo/Cargo.toml
[workspace]
members = ["my_crate", "app"]
在[workspace]中,members键是workspace目录下的crate列表。现在,在workspace_demo目录中,我们将创建两个crate:my_crate和app。为了简单起见,在my_crate中设置一个公共API,只打印一条欢迎消息。
// workspace_demo/my_crate/src/lib.rs
pub fn greet() {
println!("Hi from my_crate");
}
在app crate中,写一个main函数,来调用my_crate中的函数。
// workspace_demo/app/src/main.rs
fn main() {
my_crate::greet();
}
还没有完,我们需要让Cargo知道我们的my_crate依赖项。由于my_crate是一个本地crate,需要在app的Cargo.toml文件中指定依赖项:
# workspace_demo/app/Cargo.toml
[package]
name = "app"
version = "0.1.0"
authors = ["wuciren"]
edition = "2018"
[dependencies]
my_crate = { path = "../my_crate" }
现在,当我们运行cargo build时,二进制文件将在workspace_demo目录的目标目录中生成。相应的,我们可以在workspace_demo目录中添加多个本地crate。如果想要添加来自crates.io的第三方依赖项,需要把放到所有对其有需要的crate种。然而,在构建过程中,Cargo确保在cargo.lock中只有该依赖项的单一版本,这确保了第三方依赖项不会被重新构建和复制。
结语
内容至此,读者应该有所感觉,好像可以自己构建一些软件包的壳子了,这一点直觉是正确的,虽然还不够,但已经稳步踏在将用Rust代码实现想法的路上了。
下一篇,我们会讲一下如何使用外部的工具来扩展和增强现有的内容和形式。
主要参考和建议读者进一步阅读的文献
https://doc.rust-lang.org/book
1.Rust编程之道,2019, 张汉东
2.The Complete Rust Programming Reference Guide,2019, Rahul Sharma,Vesa Kaihlavirta,Claus Matzinger
3.Hands-On Data Structures and Algorithms with Rust,2018,Claus Matzinger
4.Beginning Rust ,2018,Carlo Milanesi
5.Rust Cookbook,2017,Vigneshwer Dhinakaran