Введение
Тесты превращают ожидание в проверяемое свидетельство. Целевой тест проверяет одно поведение, сравнивает фактический результат с ожидаемым и делает будущие изменения безопаснее, автоматически обнаруживая регрессии.
Вы добавите тесты в подготовленную библиотеку для расчёта стоимости доставки. Рабочие функции намеренно сделаны небольшими, чтобы вы могли сосредоточиться на том, где находятся тесты Rust, как утверждения описывают ожидания, какие граничные случаи важны и как Cargo может запускать по одной цели тестирования за раз.
Добавьте целевой модульный тест
На этом шаге вы разместите модульный тест рядом с кодом библиотеки и запустите только тест с указанным именем.
Проект находится в /home/labex/project/shipping-quote. Перейдите в него:
cd /home/labex/project/shipping-quote
Перед редактированием просмотрите короткий исходный код библиотеки. Здесь команда sed -n '1,220p' используется только для чтения: параметр -n отключает автоматический вывод, а 1,220p явно выводит строки с 1 по 220. Этого диапазона достаточно, чтобы показать весь файл без открытия редактора:
sed -n '1,220p' src/lib.rs
Синтаксис вида #[...] называется атрибутом — это метаданные, прикреплённые к следующему элементу Rust. Атрибут #[cfg(test)] указывает Rust компилировать следующий тестовый модуль только при сборке тестов. Модуль — это именованный контейнер для связанных элементов; в следующем Guided Lab модули рассматриваются как общий инструмент организации кода.
Внутри mod tests слово пути super означает родительский модуль, а * — все доступные имена из этого родительского модуля. Поэтому use super::*; делает функции библиотеки доступными внутри тестового модуля по коротким именам. Наконец, функция с атрибутом #[test] становится тестом, который Cargo может обнаружить и запустить.
Откройте исходный файл в Nano:
nano src/lib.rs
Замените // FIRST_UNIT_TEST следующим небольшим тестом:
#[test]
fn light_parcel_costs_five() {
assert_eq!(shipping_cost(3), 5);
}
Макрос assert_eq! сравнивает два выражения. Если они равны, тест продолжается; если нет, тест завершается с ошибкой и выводит оба значения. В данном случае тест фиксирует, что доставка посылки массой три килограмма стоит пять единиц.
Сохраните файл с помощью Ctrl+O, нажмите Enter, а затем выйдите с помощью Ctrl+X. Передайте имя теста после cargo test, чтобы запустить только тесты, имена которых содержат этот текст:
cargo test light_parcel_costs_five
Cargo скомпилирует тестовую сборку и выведет результат указанного модульного теста:
test tests::light_parcel_costs_five ... ok
test result: ok. 1 passed; 0 failed; ...
Префикс tests:: показывает, что тест находится внутри локального тестового модуля библиотеки, а ok подтверждает успешное выполнение утверждения.
Проверьте граничные случаи
На этом шаге вы добавите тесты для границ, в которых меняется поведение доставки.
Такое обычное значение, как 3, проверяет одну точку внутри диапазона для лёгкой посылки, но ошибки часто скрываются на границах. В подготовленном match запись 1..=5 — это шаблон диапазона, который соответствует любому значению от одного до пяти включительно, включая обе границы. Здесь обозначение включительного диапазона, знакомое по циклам, используется в новом контексте — в шаблоне. Функция обрабатывает нулевую массу особым образом и сохраняет тариф в пять единиц ровно до пяти килограммов включительно. Проверка этих значений фиксирует обе границы.
Снова откройте библиотеку:
nano src/lib.rs
Замените // EDGE_UNIT_TESTS двумя небольшими тестами:
#[test]
fn empty_parcel_is_free() {
assert_eq!(shipping_cost(0), 0);
}
#[test]
fn five_kilograms_is_still_light() {
assert_eq!(shipping_cost(5), 5);
}
Имя каждого теста описывает одно правило, а сам тест содержит одно целевое утверждение. Благодаря этому легко определить причину сбоя. Сохраните файл и выйдите из Nano.
Фильтр tests:: соответствует полным именам тестов в локальном модульном тестовом модуле. Используйте его, чтобы запустить все три модульных теста, пока не включая отдельную цель интеграционного тестирования:
cargo test tests::
Надёжным подтверждением будут три успешно пройденных модульных теста:
test tests::empty_parcel_is_free ... ok
test tests::five_kilograms_is_still_light ... ok
test tests::light_parcel_costs_five ... ok
test result: ok. 3 passed; 0 failed; ...
Вместе эти примеры проверяют обычное значение и две важные границы правила для лёгкой посылки.
Завершите интеграционный тест
На этом шаге вы протестируете библиотеку через её открытый интерфейс из отдельного файла.
Модульные тесты внутри src/lib.rs находятся рядом с реализацией и могут использовать внутренние элементы родительского модуля. Интеграционные тесты находятся в каталоге верхнего уровня tests/, компилируются как отдельные тестовые программы и используют только публичный интерфейс библиотеки, как внешний вызывающий код. Имя пакета shipping-quote становится именем крейта Rust shipping_quote с символом подчёркивания в исходном коде. В следующем Lab закрепляются термины «пакет», «крейt», «модуль» и «публичная видимость».
Просмотрите подготовленный шаблон интеграционного теста:
sed -n '1,160p' tests/order_workflow.rs
Он импортирует только публичную функцию order_total, вызывает её для заказа стоимостью 40 единиц и посылки массой три килограмма, а затем оставляет одно утверждение для вас. Откройте файл:
nano tests/order_workflow.rs
Замените строку с утверждением-заполнителем, заканчивающуюся комментарием // INTEGRATION_ASSERTION, на:
assert_eq!(total, 45);
Ожидаемая сумма складывается из промежуточной суммы в 40 единиц и стоимости доставки лёгкой посылки в пять единиц. Сохраните файл и выйдите из Nano.
Параметр --test order_workflow выбирает цель интеграционного тестирования, исходный файл которой находится в tests/order_workflow.rs:
cargo test --test order_workflow
Цель выведет результат теста публичного сценария:
test order_total_includes_shipping ... ok
test result: ok. 1 passed; 0 failed; ...
В конце запустите весь набор тестов пакета без фильтра:
cargo test
Cargo запустит три модульных теста и интеграционный тест. Отдельные сводки результатов нормальны, поскольку это разные тестовые бинарные файлы; у каждого именованного теста должно отображаться ok, а в каждой сводке должно быть ноль ошибок.
Итоги
Вы добавили локальные модульные тесты, проверили границы правил, завершили интеграционный тест через публичный API библиотеки и использовали фильтры Cargo для запуска одного теста, одного модуля, одной интеграционной цели или полного набора. Эти приёмы дают быстрое подтверждение во время разработки и обеспечивают более широкую защиту от регрессий перед передачей результата.


