ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 11.01.2024
Просмотров: 1165
Скачиваний: 5
ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
СОДЕРЖАНИЕ
аварийным. Использование макроса assert!
помогает проверить, что код функционирует как ожидалось.
В главе 5, листинга 5-15, мы использовали структуру
Rectangle и метод can_hold
,
который повторён в листинге 11-5. Давайте поместим этот код в файл src/lib.rs и напишем несколько тестов для него используя assert!
макрос.
Файл: src/lib.rs
Листинг 11-5: Использование структуры
Rectangle
и её метода
can_hold
из главы 5
Метод can_hold возвращает логическое значение, что означает, что она является идеальным вариантом использования в макросе assert!
. В листинге 11-6 мы пишем тест, который выполняет метод can_hold путём создания экземпляра
Rectangle шириной 8 и высотой 7 и убеждаемся, что он может содержать другой экземпляр
Rectangle имеющий ширину 5 и высоту 1.
Файл: src/lib.rs
Листинг 11-6: Теста для метода
can_hold
, который проверяет что больший прямоугольник действительно
может содержать меньший
#[derive(Debug)]
struct
Rectangle
{ width: u32
, height: u32
,
} impl
Rectangle { fn can_hold
(&
self
, other: &Rectangle) -> bool
{ self
.width > other.width && self
.height > other.height
}
}
#[cfg(test)]
mod tests { use super::*;
#[test]
fn larger_can_hold_smaller
() { let larger = Rectangle { width:
8
, height:
7
,
}; let smaller = Rectangle { width:
5
, height:
1
,
}; assert!
(larger.can_hold(&smaller));
}
}
помогает проверить, что код функционирует как ожидалось.
В главе 5, листинга 5-15, мы использовали структуру
Rectangle и метод can_hold
,
который повторён в листинге 11-5. Давайте поместим этот код в файл src/lib.rs и напишем несколько тестов для него используя assert!
макрос.
Файл: src/lib.rs
Листинг 11-5: Использование структуры
Rectangle
и её метода
can_hold
из главы 5
Метод can_hold возвращает логическое значение, что означает, что она является идеальным вариантом использования в макросе assert!
. В листинге 11-6 мы пишем тест, который выполняет метод can_hold путём создания экземпляра
Rectangle шириной 8 и высотой 7 и убеждаемся, что он может содержать другой экземпляр
Rectangle имеющий ширину 5 и высоту 1.
Файл: src/lib.rs
Листинг 11-6: Теста для метода
can_hold
, который проверяет что больший прямоугольник действительно
может содержать меньший
#[derive(Debug)]
struct
Rectangle
{ width: u32
, height: u32
,
} impl
Rectangle { fn can_hold
(&
self
, other: &Rectangle) -> bool
{ self
.width > other.width && self
.height > other.height
}
}
#[cfg(test)]
mod tests { use super::*;
#[test]
fn larger_can_hold_smaller
() { let larger = Rectangle { width:
8
, height:
7
,
}; let smaller = Rectangle { width:
5
, height:
1
,
}; assert!
(larger.can_hold(&smaller));
}
}
Также, в модуле tests обратите внимание на новую добавленную строку use super::*;
Модуль tests является обычным и подчиняется тем же правилам видимости, которые мы обсуждали в главе 7
"Пути для ссылки на элементы внутри дерева модуля"
. Так как этот модуль tests является внутренним, нужно подключить тестируемый код из внешнего модуля в область видимости внутреннего модуля с тестами. Для этого используется глобальное подключение, так что все что определено во внешнем модуле становится доступным внутри tests модуля.
Мы назвали наш тест larger_can_hold_smaller и создали два нужных экземпляра
Rectangle
. Затем вызвали макрос assert!
и передали результат вызова larger.can_hold(&smaller)
в него. Это выражение должно возвращать true
, поэтому наш тест должен пройти. Давайте выясним!
Тест проходит. Теперь добавим другой тест, в этот раз мы попытаемся убедиться, что меньший прямоугольник не может содержать больший прямоугольник:
Файл: src/lib.rs
$
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 1 test test tests::larger_can_hold_smaller ... ok test result: ok. 1 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
Поскольку правильный результат функции 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
==
, но не значения, которые привели к результату 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'
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
С своей работе макросы 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