← Table of contents

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 tq code for this chapter is in 1-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.