Юнит-тесты представляют важный компонент разработки, который помогает обеспечить корректность выполнения кода. В языке Rust имеется встроенная поддержка юнит-тестов. Рассмотрим основные моменты юнит-тестирования на Rust.
Прежде всего нам надо создать новый проект cargo, поскольку запуск тестов производится с помощью инфраструктуры пакетного менеджера cargo. Так, перейдем в консоли к папке, где мы хотим создать проект, и выполним следующую команду
cargo new testapp
Наш проект будет называться testapp
Тест по сути представляет собой функцию, к которой применяется атрибут #[test]. Этот атрибут сообщает компилятору Rust, что функция представляет тест, и ее следует включать в тестовые прогоны.
Где располагается тест? В данном случае в реальности нет каких-то четких требований. Например, мы можем помещать функциии/функционал и их тестыв один файл, можно разделять их. Можно выделять тесты в отделньые модули. Далее посмотрим на некоторые сценарии. Но сначала рассмотрим, что вообще представляет простейший тест. И для простоты пока весь определим в файле main.rs, который располагается в папке src:
fn main() {
println!("Hello, world!");
}
fn add(a: i32, b: i32) -> i32 { // функция для тестирования
a + b
}
// функция-тест
#[test]
fn test_add() {
// Arrange
let input_1 = 2;
let input_2 = 8;
// Act
let result = add(input_1, input_2);
// Assert
assert_eq!(result, 10, "The addition result is incorrect.");
}
Итак, здесь у нас есть функция add(), которая складывает два числа и возвращает их сумму. И к этой функции определен тест - функция test_add(). Обратите внимание, что эта функция
предваряется атрибутом #[test]. Внутри функции определяем два значения для параметров функции add и вызываем ее, сохраняя ее результат в переменную result.
Далее нам надо верифицировать результат. Для этого стандартная библиотека языка Rust предоставляет ряд макросов, которые в дальнейшем мы вкратце рассмотрим. В частности, макрос assert_eq!() позволяет проверить на равенство два значения. Его первые два параметра представляют значения, которые проверяются на равенство, а третий параметр представляет сообщение, которое выводится при неравенстве. Так, мы складываем числа 2 и 8 и ожидаем, что результат будет равен 10. Если результат будет равен 10, то тест будет пройден. Если же результат не будет равен 10, то тест не будет пройден, а на консоль будет выводиться сообщение "The addition result is incorrect.".
Данный тест соответствует шаблону "Arrange, Act, Assert" (AAA) (иными словами "Настройка функции, Вызов функции, Подтверждение результата), где этап "Arrange" включает в себя настройку необходимых входных данных и условий, этап "Act" выполняет тестируемую операцию, а этап "Assert" проверяет, что мы получили тот результат, который ожидали.
Теперь перейдем в консоли в папку проекта и запустим тест. Для запуска юнит-тестов в Rust в общем случае применяется команда cargo test:
eugene@Eugene:~/Documents/rust/testapp$ cargo test
Compiling testapp v0.1.0 (/home/eugene/Documents/rust/testapp)
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.11s
Running unittests src/main.rs (target/debug/deps/testapp-484a4e49d8e25663)
running 1 test
test test_add ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
eugene@Eugene:~/Documents/rust/testapp$
После выполнения тестов нам отобразится отчет c результатами. В этом отчете мы сможем увидеть, какие тесты были успешно пройдены, а какие нет. Кроме того, мы можем увидеть некоторую дополнительную информацию, в частности, время выполнения.
Так, средняя часть сводки говорит нам, сколько тестов и какие именно тесты запускаются:
running 1 test
test test_add ... ok
Так, мы видим, что был запущен 1 тест, который называется "test_add" - название нашей функции-теста.
И в последней части сводки мы види результат тестирования:
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Если тест успешно пройден, как в нашем случае, то он получает статус "passed". И в нашем случае мы видим в своде "1 passed".
Если тест НЕ пройден, то он получает статус "failed". В нашем случае таких тестов нет, на что указывает запись "0 failed".
Возможна ситуация, когда у нас есть тесты, но мы их не хотим запускать - такие функции тестов предваряются атрибутом #[ignore] и при тестировании получают статус "ignored".
В нашем случае таких также нет, на что указывает запись "0 ignored"
Но теперь изменим код следующим образом:
fn main() {
println!("Hello, world!");
}
fn add(a: i32, b: i32) -> i32 { // функция для тестирования
a - b // некорректное поведение
}
// функция-тест
#[test]
fn test_add() {
let input_1 = 2;
let input_2 = 8;
let result = add(input_1, input_2);
assert_eq!(result, 10, "The addition result is incorrect.");
}
Теперь наша функция add вместо сложения выполняет вычитание. Однако в функции-тесте test_add() мы по-прежнему ожидаем, что функция add() будет выполнять сложение. Также запустим тест:
eugene@Eugene:~/Documents/rust/testapp$ cargo test
Compiling testapp v0.1.0 (/home/eugene/Documents/rust/testapp)
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.08s
Running unittests src/main.rs (target/debug/deps/testapp-484a4e49d8e25663)
running 1 test
test test_add ... FAILED
failures:
---- test_add stdout ----
thread 'test_add' panicked at src/main.rs:20:5:
assertion `left == right` failed: The addition result is incorrect.
left: -6
right: 10
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
failures:
test_add
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
error: test failed, to rerun pass `--bin testapp`
eugene@Eugene:~/Documents/rust/testapp$
И теперь мы видим совсем другой результат: тест не пройден. То есть наша функция add работает некорректно. Мы видим в сводке, более детальную информацию и наше сообщение об ошибке:
assertion `left == right` failed: The addition result is incorrect. left: -6 right: 10
То есть условие неверно, ожидали получить 10, а получили -6. Соответственно нам надо вернуться к коду, исправить его, снова выполнить тест и повторять эти действия, пока тест не будет пройден.