1.11 The First Proof
Making and verifying are not the same thing. A craftsman who has just finished a piece knows one thing: it looks done. Whether it actually is done is a different question, and the answer is not visible to the eye. The task can create itself, complete itself, and answer a question about its state. Faith is convenient: it requires no tools and works instantly. The cost of convenience shows up exactly when everything starts going wrong.
What You Need
make ci checks the form: formatting without complaints, clippy without warnings. It does
not ask about behaviour. Code that looks correct can behave incorrectly - the gate will not
notice.
A way is needed to ask questions about behaviour and demand answers on every run.
The Build
Tests live in a special block at the end of the file:
#[cfg(test)]
mod tests {
use super::*;
}
#[cfg(test)] is a configuration attribute: this block is compiled only when running
cargo test. It does not end up in a normal build. use super::* brings the types from
the parent file into scope - without it, Task and Status are unknown inside the block.
A test is an ordinary function with the #[test] attribute. The name describes the
behaviour being checked, not the method:
#[test]
fn new_task_starts_as_todo() {
let task = Task::new(1, "Buy coffee");
assert!(!task.is_done());
}
assert! checks a condition: if it is false, the test fails. Add a second test:
#[test]
fn complete_marks_task_done() {
let mut task = Task::new(1, "Buy coffee");
task.complete();
assert!(task.is_done());
}
When two values need to be compared, assert_eq! will show both on failure:
#[test]
fn task_id_is_preserved() {
let task = Task::new(42, "Buy coffee");
assert_eq!(task.id, 42);
}
assert_eq!(left, right) - if left != right, the test fails and prints both values.
assert! does not do this: it only knows “false.”
The Result
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_task_starts_as_todo() {
let task = Task::new(1, "Buy coffee");
assert!(!task.is_done());
}
#[test]
fn complete_marks_task_done() {
let mut task = Task::new(1, "Buy coffee");
task.complete();
assert!(task.is_done());
}
#[test]
fn task_id_is_preserved() {
let task = Task::new(42, "Buy coffee");
assert_eq!(task.id, 42);
}
}
running 3 tests
test tests::complete_marks_task_done ... ok
test tests::new_task_starts_as_todo ... ok
test tests::task_id_is_preserved ... ok
test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
make ci passes. The gate now asks about behaviour, not only form.
The complete
tqcode for this chapter is in1-a-task/11-the-first-proof/.
Lore: What Is Not Tested
Tests check behaviour through return values and state. list returns () and only prints -
to verify its output, stdout would need to be captured. That is possible, but the tools for
it appear later.