Написание не проходящего теста
Поскольку мы больше не нуждаемся в них, удалим println!
инструкции из src/lib.rs
и src/main.rs
, которые мы использовали для проверки поведения программы. Затем в src/lib.rs
мы добавим модуль tests
с тестовой функцией, как мы делали в главе 11. Тестовая функция определяет поведение, которое мы хотим, чтобы функция search
имела: она будет принимать запрос и текст для поиска, и возвращать только те строки из текста, которые содержат запрос. В листинге 12-15 показан этот тест, который еще не скомпилируется.
Имя файла: src/lib.rs
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn one_result() {
let query = "duct";
let contents = "\
Rust:
safe, fast, productive.
Pick three.";
assert_eq!(
vec!["safe, fast, productive."],
search(query, contents)
);
}
}
Листинг 12-15: Создание не проходящего теста для функции search
, которую мы хотели бы иметь
Этот тест ищет строку "duct"
. Текст, по которому мы ищем, состоит из трех строк, только одна из которых содержит "duct"
(заметьте, что обратный слэш после открытой двойной кавычки говорит Rust не вставлять символ новой строки в начале содержимого этого литерала строки). Мы утверждаем, что значение, возвращаемое функцией search
, содержит только ту строку, которую мы ожидаем.
Мы еще не можем запустить этот тест и увидеть, как он не проходит, потому что тест даже не компилируется: функция search
еще не существует! Согласно принципам TDD, мы добавим столько кода, чтобы тест скомпилировался и запустился, добавив определение функции search
, которая всегда возвращает пустой вектор, как показано в листинге 12-16. Затем тест должен скомпилироваться и не пройти, потому что пустой вектор не совпадает с вектором, содержащим строку "safe, fast, productive."
.
Имя файла: src/lib.rs
pub fn search<'a>(
query: &str,
contents: &'a str,
) -> Vec<&'a str> {
vec![]
}
Листинг 12-16: Определение достаточного количества функции search
, чтобы наш тест скомпилировался
Обратите внимание, что нам нужно определить явный срок жизни 'a
в сигнатуре функции search
и использовать этот срок жизни с аргументом contents
и возвращаемым значением. Напомним, что в главе 10 параметры сроков жизни определяют, какой аргументский срок жизни связан с сроком жизни возвращаемого значения. В этом случае мы указываем, что возвращаемый вектор должен содержать срезы строк, которые ссылаются на срезы аргумента contents
(а не аргумента query
).
Другими словами, мы говорим Rust, что данные, возвращаемые функцией search
, будут жить столько же времени, сколько и данные, переданные в функцию search
в аргументе contents
. Это важно! Данные, на которые ссылается срез, должны быть валидными, чтобы ссылка была валидной; если компилятор предполагает, что мы создаем срезы строк из query
вместо contents
, он будет неправильно выполнять свою проверку безопасности.
Если мы забываем аннотации сроков жизни и пытаемся скомпилировать эту функцию, мы получим эту ошибку:
error[E0106]: missing lifetime specifier
--> src/lib.rs:31:10
|
29 | query: &str,
| ----
30 | contents: &str,
| ----
31 | ) -> Vec<&str> {
| ^ expected named lifetime parameter
|
= help: this function's return type contains a borrowed value, but the
signature does not say whether it is borrowed from `query` or `contents`
help: consider introducing a named lifetime parameter
|
28 ~ pub fn search<'a>(
29 ~ query: &'a str,
30 ~ contents: &'a str,
31 ~ ) -> Vec<&'a str> {
|
Rust не может определить, какой из двух аргументов мы нуждаемся в, поэтому мы должны явно его указать. Поскольку contents
является аргументом, содержащим весь наш текст, и мы хотим вернуть части этого текста, которые совпадают, мы знаем, что contents
является аргументом, который должен быть связан с возвращаемым значением с использованием синтаксиса срока жизни.
В других языках программирования не требуется связывать аргументы с возвращаемыми значениями в сигнатуре, но с течением времени это будет становиться проще. Вы можете сравнить этот пример с примерами в разделе "Проверка ссылок с использованием сроков жизни".
Теперь давайте запустим тест:
$ cargo test
Compiling minigrep v0.1.0 (file:///projects/minigrep)
Finished test [unoptimized + debuginfo] target(s) in 0.97s
Running unittests src/lib.rs (target/debug/deps/minigrep-9cd200e5fac0fc94)
running 1 test
test tests::one_result... FAILED
failures:
---- tests::one_result stdout ----
thread 'tests::one_result' panicked at 'assertion failed: `(left == right)`
left: `["safe, fast, productive."]`,
right: `[]`', src/lib.rs:47:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
failures:
tests::one_result
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'
Отлично, тест не проходит, именно так, как мы ожидали. Давайте сделаем тест проходящим!