前言
通过前面18篇教程,我们已经获取了设置开发环境的知识,我们学会了rustup
、VS Code
、rust-analyzer
,我们已经走过了作为新手的迷茫期,现在要开始学习如何建立一个真正的项目
正文
建立workspace
建议你使用cargo
的workspace
,Rust
和JavaScript
都是逻辑功能模块化后会更好管理,我们现在就会开始一个可执行库,我们以一个library
开始,以一个命令行程序来做封装,这种结构会更容易测试和拓展
首先,在一个空文件夹中创建一个workspace
,在Cargo.toml
中添加如下内容:
[workspace]
members = ["crates/*"]
members
列出了workspace
中所有的crate
,这里配置的意思是包含了crates
文件夹下的所有内容
建立library
我们用cargo new
创建一个新的library
:
$ cargo new --lib crates/my-lib
binary crate
和library crate
之间的差异很小,默认情况下binary crate
有一个main.rs
而library crate
是lib.rs
,cargo new --lib
命令还会将Cargo.lock
添加到.gitignore
中
Cargo book 建议将
Cargo.lock
在产品端保留(二进制、服务器、微服务等),但是在library
中省略
lib.rs
的默认内容比较简单:
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
let result = 2 + 2;
assert_eq!(result, 4);
}
}
在这里会看到关于单元测试的内容,Rust
有内置的单元测试,不需要额外的配置单元测试框架,也不需要配置如何运行测试
这意味着单元测试就在你的源码里,
Rust
也有集成测试,就放在和src
文件夹同级的tests
文件夹里,但这只能测试公共接口,如果希望是私有代码,你只能在源码里写测试
单元测试
library
模板引出了两个新的属性:#[cfg()]
和#[test]
[#cfg()]
用于条件编译,在一个模块的前面指定#[cfg(test)]
就意味着Rust
会忽略这个模块的编译,除非有test
标记
#[cfg(test)]
mod tests {
}
[#test]
会将一个函数编辑成单元测试,当你运行cargo test
命令的时候,Rust
会将其一一执行
$ cargo test
running 1 test
test tests::it_works ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests my-lib
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Rust
提供的断言很基础,你可以通过assert!()
、assert_eq!()
或assert_ne!()
来做判断
编写测试用例是确定API
功能的很好开始,虽然严格的TDD
有点极端,但在编写API
之前设计好测试会强迫你思考API
的实现。
WebAssembly
现在比较火,让我们建立一个wasm runner
,我们将lib.rs
改成下面这个样子:
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn loads_wasm_file() {
let result = Module::from_path("./tests/test.wasm");
assert!(result.is_ok());
}
}
添加use super::*
让我们可以不需要前缀就能使用父模块的所有内容
现在模块结构还不存在,但是我们预估需要一个从本地加载文件的方法,由于加载可能失败,所以返回值是Result
,尽管不知道不知道结果如何,但预期的结果肯定希望是Ok
我们可以运行cargo test
,但是会遇到编译错误,这是因为有些功能还未实现
error[E0433]: failed to resolve: use of undeclared type `Module`
--> crates/wasm-runner/src/lib.rs:6:22
|
6 | let result = Module::from_file("./tests/test.wasm");
| ^^^^^^ use of undeclared type `Module`
For more information about this error, try `rustc --explain E0433`.
我们需要添加Module
结构体以及from_file
函数,我们在测试中给该函数传递了一个&str
,但实现的时候我们希望参数可以是任何代表路径的东西
use std::path::Path;
struct Module {}
impl Module {
fn from_file<T: AsRef<Path>>(path: T) -> Result<Self, ???> {
Ok(Self{})
}
}
现在我们需要指出返回的错误类型,因为我们是从文件系统加载的,而这些方法会返回io::Error
类型的错误,所以我们现在也可以这么设置
现在我们的代码已经可以运行了,虽然还没做任何有用的事情
use std::path::Path;
struct Module {}
impl Module {
fn from_file<T: AsRef<Path>>(path: T) -> Result<Self, std::io::Error> {
Ok(Self {})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn executes_wasm_file() {
let result = Module::from_file("./tests/test.wasm");
assert!(result.is_ok());
}
}
创建命令行程序
使用cargo new crates/[your cli name]
命令创建一个命令行程序,这里我们用到的是$ cargo new crates/cli
,然后在Cargo.toml
中添加依赖:
[dependencies]
my-lib = { path = "../my-lib" }
现在我们可以从my_lib
命名空间导入代码了
Rust
有一个规则,允许在crate
名字中使用连字符,但不允许在Rust
标识符中使用,所以如果你的crate
名字用到了连字符,那么使用的时候以下划线代替
use my_lib::Module;
当输入上述代码的时候,VS Code
会给出错误提示
这是因为Module
没有明确指明是public
的,所以我们不能引入它。现在我们给struct Module
和fn from_file
i添加pub
pub struct Module {}
impl Module {
pub fn from_file<T: AsRef<Path>>(path: T) -> Result<Self, std::io::Error> {
Ok(Self {})
}
}
现在我们可以在命令行程序中引入Module
并且调用Module::from_file
方法了
use my_lib::Module;
fn main() {
match Module::from_file("./module.wasm") {
Ok(_) => {
println!("Module loaded");
}
Err(e) => {
println!("Module failed to load: {}", e);
}
}
}
运行命令行程序
我们可以在./crates/cli
文件夹里通过cargo run
命令运行我们的命令行程序,不过cargo
也可以在任何的子crate
中庸-p
标记运行命令,在我们的项目根路径上,我们通过cargo run -p cli
命令去运行cli crate
中默认的二进制文件
$ cargo run -p cli
Module loaded
虽然还有不少事情要做,但截止到目前我们已经打好了一个坚实的基础
相关阅读
- Rust by Example: Unit testing
- Rust Book, 11.01: How to Write Tests
- Rust Book, 14.03: Cargo Workspaces
- How to Structure Unit Tests in Rust
总结
打下坚实的基础很重要,初学的时候你经常会不知所措,不晓得最佳实践是什么,这会动摇你的信心。但当你跨越了这些时刻,你会感到一切都是那么自然,甚至会忘记初学时的苦恼
更多
- 写给前端看的Rust教程(1)从nvm到rust
- 写给前端看的Rust教程(2)从npm到cargo
- 写给前端看的Rust教程(3)配置Visual Studio Code
- 写给前端看的Rust教程(4)Hello World
- 写给前端看的Rust教程(5)借用&所有权
- 写给前端看的Rust教程(6)String 第一部分
- 写给前端看的Rust教程(7)语言篇[上]
- 写给前端看的Rust教程(8)语言篇[中]
- 写给前端看的Rust教程(9)语言篇[下]
- 写给前端看的Rust教程(10)从 Mixins 到 Traits
- 写给前端看的Rust教程(11)Module
- 写给前端看的Rust教程(12)String 第二部分
- 写给前端看的Rust教程(13)Results & Options
- 写给前端看的Rust教程(14)Errors
- 写给前端看的Rust教程(15)闭包
- 写给前端看的Rust教程(16)生命周期
- 写给前端看的Rust教程(17)迭代
- 写给前端看的Rust教程(18)异步
- 写给前端看的Rust教程(19)实战 第一部分
- 写给前端看的Rust教程(20)实战 第二部分
- 写给前端看的Rust教程(21)实战 第三部分