← Оглавление

3.7 Доказательство на своём месте

Тест, живущий отдельно от того, что он проверяет, остаётся тестом. Это принято называть организацией. Правильное слово - случайным соседством.

Что нужно

Четырнадцать тестов живут в src/main.rs - потому что когда они появились, других файлов не было. Теперь есть. Тест задачи должен жить рядом с задачей, тест хранилища

Есть и практическая причина: тест как подмодуль видит приватные детали своего родителя. Тест, живущий в main.rs, видит только то, что открыто наружу. Тест, живущий внутри модуля - всё.

Переезд

В M3.6 модуль задачи - это файл src/task.rs. Чтобы дать ему подмодуль с тестами, файл становится директорией: src/task.rs переименовывается в src/task/mod.rs. Для компилятора и main.rs ничего не меняется - mod task; работает с обоими вариантами одинаково.

В конец src/task/mod.rs добавляется одна строка:

// src/task/mod.rs - добавить в конец
#[cfg(test)]
mod tests;

#[cfg(test)] - атрибут условной компиляции: модуль tests включается только при cargo test. mod tests; указывает компилятору на src/task/tests.rs.

Создаём src/task/tests.rs. Внутри - тело модуля tests:

// 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]");
}

По этому принципу оставшиеся семь тестов задачи переезжают туда же - каждый без изменений.

super здесь - модуль task. use super::* приносит все имена из task/mod.rs - и публичные, и приватные: дочерний модуль видит всё что есть у родителя. Если в task/mod.rs уже написано use std::fmt или use crate::error::TqError - в tests.rs они доступны без повторного импорта. Именно поэтому тесты держат внутри, а не снаружи.

src/store.rs и src/error.rs переименовываются так же - в src/store/mod.rs и src/error/mod.rs. Каждый получает #[cfg(test)] mod tests; в конце.

В src/store/tests.rs нужен дополнительный импорт: TqError живёт в другом модуле и use super::* его не принесёт:

// 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);
}

Оставшиеся два store-теста переезжают туда же.

Из src/main.rs весь блок #[cfg(test)] mod tests { ... } удаляется.

Результат

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

Четырнадцать тестов - те же, что и были. Только теперь каждый знает, где живёт.

make ci проходит.

Полный код tq для этой главы - 3-a-voice/07-the-proof-moves-in/.