3.7 The Proof Moves In
A test that lives separately from what it tests is still a test. This is usually called organisation. The accurate word is accidental proximity.
What You Need
Fourteen tests live in src/main.rs - because when they were written, there were no other
files. Now there are. A task test should live next to the task; a store test next to the
store.
There is also a practical reason: a test as a submodule sees the private details of its
parent. A test living in main.rs sees only what is exposed to the outside. A test living
inside its module sees everything.
The Move
In chapter 3.6, the task module is the file src/task.rs. To give it a submodule with
tests, the file becomes a directory: src/task.rs is renamed to src/task/mod.rs. For
the compiler and main.rs, nothing changes - mod task; works the same way with both.
Add one line at the end of src/task/mod.rs:
// src/task/mod.rs - add at the end
#[cfg(test)]
mod tests;
#[cfg(test)] is a conditional compilation attribute: the tests module is included only
when running cargo test. mod tests; points the compiler to src/task/tests.rs.
Create src/task/tests.rs. Inside - the body of the tests module:
// src/task/tests.rs - NEW
use super::*;
#[test]
fn new_task_starts_as_todo() {
let task = Task::new(1, "Buy coffee").unwrap();
assert!(!task.is_done());
}
#[test]
fn task_display_todo() {
let task = Task::new(1, "Buy coffee").unwrap();
assert_eq!(format!("{}", task), "#1: Buy coffee [Todo]");
}
The remaining seven task tests move there by the same principle - each unchanged.
super here is the task module. use super::* brings all names from task/mod.rs into
scope - both public and private: a child module sees everything the parent has. If
task/mod.rs already has use std::fmt or use crate::error::TqError, those are
available in tests.rs without repeating the import. That is exactly why tests live
inside, not outside.
src/store.rs and src/error.rs are renamed the same way - to src/store/mod.rs and
src/error/mod.rs. Each gets #[cfg(test)] mod tests; at the end.
src/store/tests.rs needs one extra import: TqError lives in a different module and
use super::* will not bring it:
// src/store/tests.rs - NEW
use super::*;
use crate::error::TqError;
#[test]
fn store_assigns_sequential_ids() {
let mut store = TaskStore::new();
store.add("A").unwrap();
store.add("B").unwrap();
assert_eq!(store.all()[0].id, 1);
assert_eq!(store.all()[1].id, 2);
}
The remaining two store tests move there as well.
The entire #[cfg(test)] mod tests { ... } block is deleted from src/main.rs.
The Result
running 14 tests
test error::tests::error_display_empty_title ... ok
test error::tests::error_display_not_found ... ok
test store::tests::store_assigns_sequential_ids ... ok
test store::tests::store_get_mut_completes_task ... ok
test store::tests::store_get_returns_not_found ... ok
test task::tests::complete_marks_task_done ... ok
test task::tests::new_task_accepts_valid_title ... ok
test task::tests::new_task_rejects_empty_title ... ok
test task::tests::new_task_starts_as_todo ... ok
test task::tests::str_converts_into_task ... ok
test task::tests::task_display_done ... ok
test task::tests::task_display_todo ... ok
test task::tests::task_from_str_sets_title ... ok
test task::tests::task_id_is_preserved ... ok
test result: ok. 14 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
Fourteen tests - the same ones as before. Only now each one knows where it lives.
make ci passes.
The complete
tqcode for this chapter is in3-a-voice/07-the-proof-moves-in/.