Почему тестирование в Rust — это не страшно, а интересно?
Когда вы только начинаете изучать Rust, кажется, что компилятор уже делает за вас всю грязную работу. Он проверяет память, следит за заимствованиями и не даёт вам выстрелить себе в ногу. Но даже самый строгий компилятор не умеет проверять логику программы. Именно здесь на помощь приходит тестирование.
Многие новички боятся тестов. Им кажется, что это лишняя трата времени, что нужно писать кучу скучного кода. Но на самом деле тесты — это ваша страховка. Они помогают быстрее находить баги, уверенно рефакторить код и спать спокойно после релиза.
В этой статье мы разберём, с чего начать тестирование на Rust, если вы новичок. Никакой воды — только практика, живые примеры и советы, которые пригодятся в реальных проектах.
Что нужно знать перед стартом?
Прежде чем писать тесты, убедитесь, что у вас установлен Rust. Если нет — откройте терминал и выполните:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
Всё, что нам понадобится, уже встроено в экосистему. Никаких дополнительных фреймворков ставить не нужно. Rust предлагает мощный встроенный тестовый раннер, который запускается одной командой.
Создадим новый проект для экспериментов:
cargo new rust_test_guide
cd rust_test_guide
Отлично! Теперь у нас есть чистый проект, в котором мы будем учиться писать тесты.
Первый тест: разбираемся на примере
Откройте файл src/lib.rs. По умолчанию он пустой, но мы добавим в него простую функцию и тест к ней.
Давайте напишем функцию, которая проверяет, является ли число чётным:
pub fn is_even(num: i32) -> bool {
num % 2 == 0
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_is_even() {
assert_eq!(is_even(4), true);
assert_eq!(is_even(7), false);
}
}
Разберём код по частям:
#[cfg(test)]— этот атрибут говорит компилятору: «Собирай этот модуль только при запуске тестов». В финальную сборку он не попадёт.mod tests— мы создаём модуль для тестов. Это стандартная практика.use super::*;— импортируем всё, что объявлено выше, чтобы видеть нашу функциюis_even.#[test]— маркер, который превращает обычную функцию в тест.assert_eq!— макрос, который проверяет равенство двух значений. Если они не равны — тест падает.
Запускаем тест командой:
cargo test
Если всё сделано правильно, вы увидите зелёный вывод:
running 1 test
test tests::test_is_even ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
Поздравляю, вы написали свой первый тест на Rust! Теперь давайте разберёмся, как писать более осмысленные проверки.
Основные макросы для тестирования
В тестах вы будете постоянно использовать три макроса. Запомните их:
assert!(expression)— проверяет, что выражение истинно. Если ложно — тест проваливается.assert_eq!(left, right)— проверяет равенство двух значений. Удобно для сравнения результатов.assert_ne!(left, right)— проверяет, что значения не равны.
Пример использования всех трёх макросов:
pub fn add_two(x: i32) -> i32 {
x + 2
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_add_two() {
assert!(add_two(3) == 5);
assert_eq!(add_two(0), 2);
assert_ne!(add_two(10), 13);
}
}
Обратите внимание: assert! принимает булево выражение, а assert_eq! и assert_ne! — два значения. Выбирайте тот, который делает код чище.
Обработка паник: тестируем ошибки
Иногда функция должна паниковать при неверных входных данных. Как проверить, что паника происходит? Используйте атрибут #[should_panic].
Допустим, у нас есть функция, которая делит числа, но паникует при делении на ноль:
pub fn divide(a: i32, b: i32) -> i32 {
if b == 0 {
panic!("Деление на ноль!");
}
a / b
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[should_panic(expected = "Деление на ноль!")]
fn test_divide_by_zero() {
divide(10, 0);
}
}
Параметр expected не обязателен, но он помогает уточнить, какую именно панику мы ждём. Если функция запаникует с другим сообщением — тест упадёт. Это полезно, когда в коде несколько мест, где может возникнуть паника.
Документационные тесты: убиваем двух зайцев
Одна из крутых фишек Rust — документационные тесты. Вы пишете примеры использования в комментариях, и они автоматически проверяются при запуске cargo test.
Смотрите, как это работает. Добавьте в lib.rs следующий код:
/// Возвращает квадрат числа.
///
/// # Примеры
///
/// ```
/// use rust_test_guide::square;
///
/// assert_eq!(square(3), 9);
/// assert_eq!(square(-2), 4);
/// ```
pub fn square(x: i32) -> i32 {
x * x
}
Обратите внимание на блок кода внутри комментария. Он обёрнут в тройные обратные кавычки ```. Rust воспринимает его как тест.
Запустите cargo test снова. Вы увидите, что количество тестов увеличилось:
running 2 tests
test tests::test_is_even ... ok
test src/lib.rs - square (line 4) ... ok
Документационные тесты — отличный способ убить двух зайцев: и документацию написать, и код проверить. Особенно это полезно для библиотек, которыми будут пользоваться другие разработчики.
Совет: если ваш пример не должен компилироваться (например, вы показываете ошибку), используйте ```ignore или ```compile_fail.