Rust Programming Cookbook
上QQ阅读APP看书,第一时间看更新

How to do it...

Follow these steps to learn more about creating a test suite for your Rust projects:

  1. Once created, a library project already contains a very simple test (probably to encourage you to write more). The cfg(test) and test attributes tell cargo (the test runner) how to deal with the module:
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}
  1. Before we add further tests, let's add a subject that needs testing. In this case, let's use something interesting: a singly linked list from our other book (Hands-On Data Structures and Algorithms with Rust) made generic. It consists of three parts. First is a node type:
#[derive(Clone)]
struct Node<T> where T: Sized + Clone {
value: T,
next: Link<T>,
}

impl<T> Node<T> where T: Sized + Clone {
fn new(value: T) -> Rc<RefCell<Node<T>>> {
Rc::new(RefCell::new(Node {
value: value,
next: None,
}))
}
}

Second, we have a Link type to make writing easier:

type Link<T> = Option<Rc<RefCell<Node<T>>>>;

The last type is the list complete with functions to add and remove nodes. First, we have the type definition:

#[derive(Clone)]
pub struct List<T> where T: Sized + Clone {
head: Link<T>,
tail: Link<T>,
pub length: usize,
}

In the impl block, we can then specify the operations for the type:

impl<T> List<T> where T: Sized + Clone {
pub fn new_empty() -> List<T> {
List { head: None, tail: None, length: 0 }
}

pub fn append(&mut self, value: T) {
let new = Node::new(value);
match self.tail.take() {
Some(old) => old.borrow_mut().next = Some(new.clone()),
None => self.head = Some(new.clone())
};
self.length += 1;
self.tail = Some(new);
}

pub fn pop(&mut self) -> Option<T> {
self.head.take().map(|head| {
if let Some(next) = head.borrow_mut().next.take() {
self.head = Some(next);
} else {
self.tail.take();
}
self.length -= 1;
Rc::try_unwrap(head)
.ok()
.expect("Something is terribly wrong")
.into_inner()
.value
})
}
}
  1. With the list ready to be tested, let's add some tests for each function, starting with a benchmark:

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

#[bench]
fn bench_list_append(b: &mut Bencher) {
let mut list = List::new_empty();
b.iter(|| {
list.append(10);
});
}

Add some more tests for basic list functionality inside the test module:

    #[test]
fn test_list_new_empty() {
let mut list: List<i32> = List::new_empty();
assert_eq!(list.length, 0);
assert_eq!(list.pop(), None);
}

#[test]
fn test_list_append() {
let mut list = List::new_empty();
list.append(1);
list.append(1);
list.append(1);
list.append(1);
list.append(1);
assert_eq!(list.length, 5);
}


#[test]
fn test_list_pop() {
let mut list = List::new_empty();
list.append(1);
list.append(1);
list.append(1);
list.append(1);
list.append(1);
assert_eq!(list.length, 5);
assert_eq!(list.pop(), Some(1));
assert_eq!(list.pop(), Some(1));
assert_eq!(list.pop(), Some(1));
assert_eq!(list.pop(), Some(1));
assert_eq!(list.pop(), Some(1));
assert_eq!(list.length, 0);
assert_eq!(list.pop(), None);
}
}
  1. It's also a good idea to have an integration test that tests the library from end to end. For that, Rust offers a special folder in the project called tests, which can house additional tests that treat the library as a black box. Create and open the tests/list_integration.rs file to add a test that inserts 10,000 items into our list:
use testing::List;

#[test]
fn test_list_insert_10k_items() {
let mut list = List::new_empty();
for _ in 0..10_000 {
list.append(100);
}
assert_eq!(list.length, 10_000);
}
  1. Great, now each function has one test. Try it out by running cargo +nightly test in the testing/ root directory. The result should look like this:
$ cargo test
Compiling testing v0.1.0 (Rust-Cookbook/Chapter01/testing)
Finished dev [unoptimized + debuginfo] target(s) in 0.93s
Running target/debug/deps/testing-a0355a7fb781369f

running 4 tests
test tests::test_list_new_empty ... ok
test tests::test_list_pop ... ok
test tests::test_list_append ... ok
test tests::bench_list_append ... ok

test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

Running target/debug/deps/list_integration-77544dc154f309b3

running 1 test
test test_list_insert_10k_items ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

Doc-tests testing

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
  1. To run the benchmark, issue cargo +nightly bench:
cargo +nightly bench
Compiling testing v0.1.0 (Rust-Cookbook/Chapter01/testing)
Finished release [optimized] target(s) in 0.81s
Running target/release/deps/testing-246b46f1969c54dd

running 4 tests
test tests::test_list_append ... ignored
test tests::test_list_new_empty ... ignored
test tests::test_list_pop ... ignored
test tests::bench_list_append ... bench: 78 ns/iter (+/- 238)

test result: ok. 0 passed; 0 failed; 3 ignored; 1 measured; 0 filtered out

Now, let's go behind the scenes to understand the code better.