Тестирование: cargo test

Тестирование встроено в язык: функции с #[test] и запуск через cargo test.

#[test] — атрибут, помечающий функцию как тест; cargo test находит и запускает все такие функции.

Тесты — часть языка

В Rust не нужна сторонняя библиотека для тестов — поддержка встроена. Тестовая функция помечается атрибутом #[test]. Она проходит, если завершилась без паники, и падает, если паника произошла (например, провалился макрос проверки).

fn add(a: i32, b: i32) -> i32 {
    a + b
}

#[test]
fn test_add() {
    assert_eq!(add(2, 3), 5);   // проверка равенства
    assert_eq!(add(-1, 1), 0);
}

#[test]
fn test_add_negative() {
    assert!(add(-2, -3) < 0);   // проверка условия
}

Макросы проверок

В теле теста используют макросы проверок. Если проверка не выполнена — тест паникует и считается проваленным.

МакросПроверяет
assert!(cond)что условие истинно
assert_eq!(a, b)что a == b
assert_ne!(a, b)что a != b

Запуск

Все тесты запускаются одной командой. Cargo соберёт тестовую версию и выполнит каждую функцию с #[test].

cargo test

Вывод:

running 2 tests
test test_add ... ok
test test_add_negative ... ok

test result: ok. 2 passed; 0 failed; 0 ignored

Модуль тестов

Принято собирать тесты в отдельный модуль с атрибутом #[cfg(test)] — он означает «компилировать только при тестировании», чтобы тесты не попадали в релизный бинарник. Внутри пишут use super::*;, чтобы видеть тестируемые функции из родительского модуля.

fn is_even(n: i32) -> bool {
    n % 2 == 0
}

#[cfg(test)] // компилируется только при cargo test
mod tests {
    use super::*; // подтягиваем is_even из внешнего модуля

    #[test]
    fn even_numbers() {
        assert!(is_even(4));
        assert!(!is_even(7));
    }
}

Проверка на панику

Иногда тест должен убедиться, что код паникует при некорректном вводе. Для этого добавляют атрибут #[should_panic].

fn checked_div(a: i32, b: i32) -> i32 {
    if b == 0 {
        panic!("деление на ноль");
    }
    a / b
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    #[should_panic]
    fn panics_on_zero() {
        checked_div(10, 0); // тест пройдёт, потому что здесь ожидается паника
    }
}

Итог

  • Тесты встроены в Rust: функция с #[test] проходит, если не паникует.
  • Проверки — assert!, assert_eq!, assert_ne!; запуск — cargo test.
  • Тесты держат в модуле #[cfg(test)]; #[should_panic] проверяет ожидаемую панику.
Проверьте себя
1. Что помечает атрибут #[test]?
AГлавную функцию
BФункцию как тест, которую найдёт и запустит cargo test
CПриватную функцию
DТочку входа
2. Когда тестовая функция считается пройденной?
AЕсли она что-то вернула
BЕсли она завершилась без паники
CЕсли напечатала ok
DВсегда
3. Зачем тесты помещают в модуль с #[cfg(test)]?
AЧтобы скрыть их
BЧтобы они компилировались только при тестировании и не попадали в релизный бинарник
CЧтобы ускорить тесты
DЭто обязательно для компиляции
Поддержать проект