Материал: Язык программирования Rust

Внимание! Если размещение файла нарушает Ваши авторские права, то обязательно сообщите нам

Поскольку правильный результат функции can_hold в этом случае false
, то мы должны инвертировать этот результат, прежде чем передадим его в assert!
макро. Как результат, наш тест пройдёт, если can_hold вернёт false
:
Два теста работают. Теперь проверим, как отреагируют тесты, если мы добавим ошибку в код. Давайте изменим реализацию метода can_hold заменив одно из логических выражений знак сравнения с "больше чем" на противоположный "меньше чем" при сравнении ширины:
#[cfg(test)]
mod tests { use super::*;
#[test]
fn larger_can_hold_smaller
() {
// --snip--
}
#[test]
fn smaller_cannot_hold_larger
() { let larger = Rectangle { width:
8
, height:
7
,
}; let smaller = Rectangle { width:
5
, height:
1
,
}; assert!
(!smaller.can_hold(&larger));
}
}
$
cargo test
Compiling rectangle v0.1.0 (file:///projects/rectangle)
Finished test [unoptimized + debuginfo] target(s) in 0.66s
Running unittests src/lib.rs (target/debug/deps/rectangle-6584c4561e48942e) running 2 tests test tests::larger_can_hold_smaller ... ok test tests::smaller_cannot_hold_larger ... ok test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests rectangle running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

Запуск тестов теперь производит следующее:
Наши тесты нашли ошибку! Так как в тесте larger.width равно 8 и smaller.width равно
5 сравнение ширины в методе can_hold возвращает результат false
, поскольку число 8
не меньше чем 5.
Проверка на равенство с помощью макросов assert_eq! и assert_ne!
Общим способом проверки функциональности является использование сравнения результата тестируемого кода и ожидаемого значения, чтобы убедиться в их равенстве.
Для этого можно использовать макрос assert!
, передавая ему выражение с использованием оператора
==
. Важно также знать, что кроме этого стандартная библиотека предлагает пару макросов assert_eq!
и assert_ne!
, чтобы сделать тестирование более удобным. Эти макросы сравнивают два аргумента на равенство или неравенство соответственно. Макросы также печатают два значения входных параметров, если тест завершился ошибкой, что позволяет легче увидеть почему тест ошибочен. Противоположно этому, макрос assert!
может только отобразить, что он
// --snip-- impl
Rectangle { fn can_hold
(&
self
, other: &Rectangle) -> bool
{ self
.width < other.width && self
.height > other.height
}
}
$
cargo test
Compiling rectangle v0.1.0 (file:///projects/rectangle)
Finished test [unoptimized + debuginfo] target(s) in 0.66s
Running unittests src/lib.rs (target/debug/deps/rectangle-6584c4561e48942e) running 2 tests test tests::larger_can_hold_smaller ... FAILED test tests::smaller_cannot_hold_larger ... ok failures:
---- tests::larger_can_hold_smaller stdout ---- thread 'main' panicked at 'assertion failed: larger.can_hold(&smaller)', src/lib.rs:28:9 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace failures: tests::larger_can_hold_smaller test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s error: test failed, to rerun pass '--lib'
вычислил значение false для выражения
==
, но не значения, которые привели к результату false
В листинге 11-7, мы напишем функцию add_two
, которая прибавляет к входному параметру
2
и возвращает значение. Затем, протестируем эту функцию с помощью макроса assert_eq!
:
Файл: src/lib.rs
Листинг 11-7: Тестирование функции
add_two
с помощью макроса
assert_eq!
Проверим, что тесты проходят!
Первый аргумент, который мы передаём в макрос assert_eq!
число
4
чей результат вызова равен add_two(2)
. Строка для этого теста - test tests::it_adds_two ... ok
, а текст ok означает, что наш тест пройден!
Давайте введём ошибку в код, чтобы увидеть, как она выглядит, когда тест, который использует assert_eq!
завершается ошибкой. Измените реализацию функции add_two
,
pub fn add_two
(a: i32
) -> i32
{ a +
2
}
#[cfg(test)]
mod tests { use super::*;
#[test]
fn it_adds_two
() { assert_eq!
(
4
, add_two(
2
));
}
}
$
cargo test
Compiling adder v0.1.0 (file:///projects/adder)
Finished test [unoptimized + debuginfo] target(s) in 0.58s
Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4) running 1 test test tests::it_adds_two ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests adder running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
чтобы добавлять
3
:
Попробуем выполнить данный тест ещё раз:
Наш тест нашёл ошибку! Тест it_adds_two не выполнился, отображается сообщение assertion failed:
(left == right)`` и показывает, что left было
4
, а right было
5
. Это сообщение полезно и помогает начать отладку: это означает left аргумент assert_eq!
имел значение
4
, но right аргумент для вызова add_two(2)
был со значением
5
Обратите внимание, что в некоторых языках (таких как Java) в библиотеках кода для тестирования принято именовать входные параметры проверочных функций как "ожидаемое" (
expected
) и "фактическое" (
actual
). В Rust приняты следующие обозначения left и right соответственно, а порядок в котором определяются ожидаемое значение и производимое тестируемым кодом значение не имеют значения.
Мы могли бы написать выражение в тесте как assert_eq!(add_two(2), 4)
, что приведёт к отображаемому сообщению об ошибке assertion failed:
(left == right)``, слева left было бы
5
, а справа right было бы
4
Макрос assert_ne!
сработает успешно, если входные параметры не равны друг другу и завершится с ошибкой, если значения равны. Этот макрос наиболее полезен в тех случаях, когда мы не знаем заранее, каким значение будет, но знаем точно, каким оно не
может быть. К примеру, если тестируется функция, которая гарантировано изменяет pub fn add_two
(a: i32
) -> i32
{ a +
3
}
$
cargo test
Compiling adder v0.1.0 (file:///projects/adder)
Finished test [unoptimized + debuginfo] target(s) in 0.61s
Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4) running 1 test test tests::it_adds_two ... FAILED failures:
---- tests::it_adds_two stdout ---- thread 'main' panicked at 'assertion failed: `(left == right)` left: `4`, right: `5`', src/lib.rs:11:9 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace failures: tests::it_adds_two test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s error: test failed, to rerun pass '--lib'
входные данные определённым образом, но способ изменения входного параметра зависит от дня недели, в который запускаются тесты, что лучший способ проверить правильность работы такой функции - это сравнить и убедиться, что выходное значение функции не должно быть равным входному значению.
С своей работе макросы assert_eq!
и assert_ne!
неявным образом используют операторы
==
и
!=
соответственно. Когда проверка не срабатывает, макросы печатают значения аргументов с помощью отладочного форматирования и это означает, что значения сравниваемых аргументов должны реализовать типажи
PartialEq и
Debug
Все примитивные и большая часть типов стандартной библиотеки Rust реализуют эти типажи. Для структур и перечислений, которые вы реализуете сами будет необходимо реализовать типаж
PartialEq для сравнения значений на равенство или неравенство.
Для печати отладочной информации в виде сообщений в строку вывода консоли необходимо реализовать типаж
Debug
. Так как оба типажа являются выводимыми типажами, как упоминалось в листинге 5-12 главы 5, то эти типажи можно реализовать добавив аннотацию
#[derive(PartialEq, Debug)]
к определению структуры или перечисления. Смотрите больше деталей в Appendix C
"Выводимые типажи"
про эти и другие выводимые типажи.
Создание сообщений об ошибках
Также можно добавить пользовательское сообщение для печати в сообщении об ошибке теста как дополнительный аргумент макросов assert!
, assert_eq!
, and assert_ne!
Любые аргументы, указанные после одного обязательного аргумента в assert!
или после двух обязательных аргументов в assert_eq!
и assert_ne!
передаются в макрос format!
(он обсуждается в разделе "Конкатенация с помощью оператора
+
или макроса format!
"
главы 8), так что вы можете передать форматированную строку, которая содержит символы
{}
для заполнителей и значения, заменяющие эти заполнители.
Пользовательские сообщения полезны для пояснения, что означает утверждение, когда тест не пройден. У вас будет лучшее представление о том, какая проблема в коде.
Например, есть функция, которая приветствует человека по имени и мы хотим протестировать эту функцию. Мы хотим чтобы передаваемое ей имя выводилось в консоль:
Файл: src/lib.rs

Требования к этой программе ещё не были согласованы и мы вполне уверены, что текст
Hello в начале приветствия ещё изменится. Мы решили, что не хотим обновлять тест при изменении требований, поэтому вместо проверки на точное равенство со значением возвращённым из greeting
, мы просто будем проверять, что вывод содержит текст из входного параметра.
Давайте внесём ошибку в этот код, изменив greeting так, чтобы оно не включало name и увидим, как выглядит сбой этого теста:
Запуск этого теста выводит следующее:
pub fn greeting
(name: &
str
) ->
String
{ format!
(
"Hello {}!"
, name)
}
#[cfg(test)]
mod tests { use super::*;
#[test]
fn greeting_contains_name
() { let result = greeting(
"Carol"
); assert!
(result.contains(
"Carol"
));
}
} pub fn greeting
(name: &
str
) ->
String
{
String
::from(
"Hello!"
)
}
$
cargo test
Compiling greeter v0.1.0 (file:///projects/greeter)
Finished test [unoptimized + debuginfo] target(s) in 0.91s
Running unittests src/lib.rs (target/debug/deps/greeter-170b942eb5bf5e3a) running 1 test test tests::greeting_contains_name ... FAILED failures:
---- tests::greeting_contains_name stdout ---- thread 'main' panicked at 'assertion failed: result.contains(\"Carol\")', src/lib.rs:12:9 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace failures: tests::greeting_contains_name test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s error: test failed, to rerun pass '--lib'

Сообщение содержит лишь информацию о том что сравнение не было успешным и в какой строке это произошло. В данном случае, более полезный текст сообщения был бы,
если бы также выводилось значение из функции greeting
. Изменим тестирующую функцию так, чтобы выводились пользовательское сообщение форматированное строкой с заменителем и фактическими данными из кода greeting
:
После того, как выполним тест ещё раз мы получим подробное сообщение об ошибке:
Мы можем увидеть значение, которое мы на самом деле получили в тестовом выводе,
что поможет нам отлаживать произошедшее, а не то, что мы ожидали.
Проверка с помощью макроса should_panic
В дополнение к проверке того, что наш код возвращает правильные, ожидаемые значения, важным также является проверить, что наш код обрабатывает ошибки,
которые мы ожидаем. Например, рассмотрим тип
Guess который мы создали в главе 9,
листинга 9-10. Другой код, который использует
Guess зависит от гарантии того, что
#[test]
fn greeting_contains_name
() { let result = greeting(
"Carol"
); assert!
( result.contains(
"Carol"
),
"Greeting did not contain name, value was `{}`"
, result
);
}
$
cargo test
Compiling greeter v0.1.0 (file:///projects/greeter)
Finished test [unoptimized + debuginfo] target(s) in 0.93s
Running unittests src/lib.rs (target/debug/deps/greeter-170b942eb5bf5e3a) running 1 test test tests::greeting_contains_name ... FAILED failures:
---- tests::greeting_contains_name stdout ---- thread 'main' panicked at 'Greeting did not contain name, value was `Hello!`', src/lib.rs:12:9 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace failures: tests::greeting_contains_name test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s error: test failed, to rerun pass '--lib'

Guess экземпляры будут содержать значения только от 1 до 100. Мы можем написать тест, который гарантирует, что попытка создать экземпляр
Guess со значением вне этого диапазона вызывает панику.
Реализуем это с помощью другого атрибута тест функции
#[should_panic]
. Этот атрибут сообщает системе тестирования, что тест проходит, когда метод генерирует ошибку. Если ошибка не генерируется - тест считается не пройденным.
Листинг 11-8 показывает тест, который проверяет, что условия ошибки
Guess::new произойдут, когда мы их ожидаем их.
Файл: src/lib.rs
1   ...   23   24   25   26   27   28   29   30   ...   62
Листинг 11-8: Проверка того, что условие вызовет макрос panic!Атрибут #[should_panic] следует после #[test] и до объявления текстовой функции.Посмотрим на вывод результата, когда тест проходит:pub structGuess { value: i32, } impl Guess { pub fn new(value: i32) -> Guess { if value < 1 || value > 100 { panic!("Guess value must be between 1 and 100, got {}.", value); } Guess { value } } } #[cfg(test)]mod tests { use super::*; #[test]#[should_panic]fn greater_than_100() { Guess::new(200); } }

Выглядит хорошо! Теперь давайте внесём ошибку в наш код, убрав условие о том, что функция new будет паниковать если значение больше 100:
Когда мы запустим тест в листинге 11-8, он потерпит неудачу:
$
cargo test
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
Finished test [unoptimized + debuginfo] target(s) in 0.58s
Running unittests src/lib.rs (target/debug/deps/guessing_game-
57d70c3acb738f4d) running 1 test test tests::greater_than_100 - should panic ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests guessing_game running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
// --snip-- impl
Guess { pub fn new
(value: i32
) -> Guess { if value <
1
{ panic!
(
"Guess value must be between 1 and 100, got {}."
, value);
}
Guess { value }
}
}

Мы получаем не очень полезное сообщение в этом случае, но когда мы смотрим на тестирующую функцию, мы видим, что она
#[should_panic]
. Аварийное выполнение,
которое мы получили означает, что код в тестирующей функции не вызвал паники.
Тесты, которые используют should_panic могут быть неточными, потому что они только указывают, что код вызвал панику. Тест с атрибутом should_panic пройдёт, даже если тест паникует по причине, отличной от той, которую мы ожидали. Чтобы сделать тесты с should_panic более точными, мы можем добавить необязательный параметр expected для атрибута should_panic
. Такая детализация теста позволит удостовериться, что сообщение об ошибке содержит предоставленный текст. Например, рассмотрим модифицированный код для
Guess в листинге 11-9, где new функция паникует с различными сообщениями в зависимости от того, является ли значение слишком маленьким или слишком большим.
Файл: src/lib.rs
$
cargo test
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
Finished test [unoptimized + debuginfo] target(s) in 0.62s
Running unittests src/lib.rs (target/debug/deps/guessing_game-
57d70c3acb738f4d) running 1 test test tests::greater_than_100 - should panic ... FAILED failures:
---- tests::greater_than_100 stdout ---- note: test did not panic as expected failures: tests::greater_than_100 test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s error: test failed, to rerun pass '--lib'

Листинг 11-9: Проверка того, что условие вызовет макрос
panic!
с сообщением
Этот тест пройдёт, потому что значение, которое мы поместили для should_panic в
параметр атрибута expected является подстрокой сообщения, с которым функция
Guess::new вызывает панику. Мы могли бы указать полное, ожидаемое сообщение для паники, в этом случае это будет
Guess value must be less than or equal to 100, got
200
. То что вы выберите для указания как ожидаемого параметра у should_panic зависит от того, какая часть сообщения о панике уникальна или динамична, насколько вы хотите, чтобы ваш тест был точным. В этом случае достаточно подстроки из сообщения паники, чтобы гарантировать выполнение кода в тестовой функции else if value >
100
Чтобы увидеть, что происходит, когда тест should_panic неуспешно завершается с сообщением expected
, давайте снова внесём ошибку в наш код, поменяв местами if value < 1
и else if value > 100
блоки:
// --snip-- impl
Guess { pub fn new
(value: i32
) -> Guess { if value <
1
{ panic!
(
"Guess value must be greater than or equal to 1, got {}."
, value
);
} else if value >
100
{ panic!
(
"Guess value must be less than or equal to 100, got {}."
, value
);
}
Guess { value }
}
}
#[cfg(test)]
mod tests { use super::*;
#[test]
#[should_panic(expected = "less than or equal to 100")]
fn greater_than_100
() {
Guess::new(
200
);
}
}
Источник: https://files.student-it.ru/previewfile/264058