Ржавчина на примере

Содержание
  1. Язык программирования Rust
  2. Организация тестов
  3. Модульные тесты
  4. Модуль тестов и аннотация #[cfg(test)]
  5. Тестирование приватных функций (private)
  6. Интеграционные тесты
  7. Каталог tests
  8. Подмодули в интеграционных тестах
  9. Интеграционные тесты для бинарных крейтов
  10. Итоги
  11. Язык программирования Rust
  12. Язык программирования Rust
  13. Контролирование хода выполнения тестов
  14. Выполнение тестов параллельно или последовательно
  15. Демонстрация результатов работы функции
  16. Запуск подмножества тестов по имени
  17. Запуск одного теста
  18. Использование фильтров для запуска нескольких тестов
  19. Игнорирование тестов
  20. The Rust Programming Language
  21. Rust By Example
  22. Unit testing
  23. Tests and ?
  24. Testing panics
  25. Running specific tests
  26. Ignoring tests
  27. The rustc book
  28. Tests
  29. Test attributes
  30. CLI arguments
  31. Filters
  32. Action options
  33. --list
  34. -h, --help
  35. Selection options
  36. --test
  37. --bench
  38. --exact
  39. --skip FILTER
  40. --ignored
  41. --include-ignored
  42. --exclude-should-panic
  43. Execution options
  44. --test-threads NUM_THREADS
  45. --force-run-in-process
  46. --ensure-time
  47. --shuffle
  48. --shuffle-seed SEED
  49. Output options
  50. -q, --quiet
  51. --nocapture
  52. --show-output
  53. --color COLOR
  54. --format FORMAT
  55. --logfile PATH
  56. --report-time
  57. Unstable options
  58. Benchmarks
  59. Custom test frameworks

Язык программирования Rust

Организация тестов

Как упоминалось в начале главы, тестирование является сложной дисциплиной и разные люди используют разную терминологию и организацию. Сообщество Rust думает о тестах с точки зрения двух основных категорий: модульные тесты и интеграционные тесты. Модульные тесты это небольшие и более сфокусированные на тестировании одного модуля в отдельности или могут тестироваться приватные интерфейсы. Интеграционные тесты являются полностью внешними по отношению к вашей библиотеке и используют код библиотеки так же, как любой другой внешний код, используя только общедоступные интерфейсы и потенциально выполняя тестирование нескольких модулей в одном тесте.

Написание обоих видов тестов важно для обеспечения того, чтобы кусочки вашей библиотеки по отдельности и вместе делали то, что вы ожидаете.

Модульные тесты

Целью модульных тестов является тестирование каждого блока кода, изолированное от остального функционала, чтобы можно было быстро понять, что работает некорректно или не так как ожидается. Мы разместим модульные тесты в папке src, в каждый тестируемый файл. Но в Rust принято создавать тестирующий модуль tests и код теста сохранять в файлы с таким же именем, как компоненты которые предстоит тестировать. Также необходимо добавить аннотацию cfg(test) к этому модулю.

Модуль тестов и аннотация #[cfg(test)]

Напомним, что когда мы генерировали новый проект adder в первом разделе этой главы, то Cargo сгенерировал для нас код ниже:

#[cfg(test)]
mod tests {
    #[test]
    fn it_works() {
        let result = 2 + 2;
        assert_eq!(result, 4);
    }
}

Тестирование приватных функций (private)

Сообщество программистов не имеет однозначного мнения по поводу тестировать или нет приватные функции. В некоторых языках весьма сложно или даже невозможно тестировать такие функции. Независимо от того, какой технологии тестирования вы придерживаетесь, в Rust приватные функции можно тестировать. Рассмотрим листинг 11-12 с приватной функцией internal_adder.

pub fn add_two(a: i32) -> i32 {
    internal_adder(a, 2)
}

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

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

    #[test]
    fn internal() {
        assert_eq!(4, internal_adder(2, 2));
    }
}

Листинг 11-12: Тестирование приватных функций

Обратите внимание, что функция internal_adder не помечена как pub. Тесты — это просто Rust код, а модуль tests — это ещё один модуль. Как мы обсуждали в разделе “Пути для ссылки на элемент в дереве модулей“, элементы в дочерних модулях могут использовать элементы из своих родительских модулей. В этом тесте мы помещаем все элементы родительского модуля test в область видимости с помощью use super::* и затем тест может вызывать internal_adder. Если вы считаете, что приватные функции не нужно тестировать, то Rust не заставит вас это сделать.

Интеграционные тесты

В Rust интеграционные тесты являются полностью внешними по отношению к вашей библиотеке. Они используют вашу библиотеку так же, как любой другой код, что означает, что они могут вызывать только функции, которые являются частью публичного API библиотеки. Их целью является проверка, много ли частей вашей библиотеки работают вместе правильно. У модулей кода правильно работающих самостоятельно, могут возникнуть проблемы при интеграции, поэтому тестовое покрытие интегрированного кода также важно. Для создания интеграционных тестов сначала нужен каталог tests .

Каталог tests

Мы создаём папку tests в корневой папке вашего проекта, рядом с папкой src. Cargo знает, что искать файлы с интеграционными тестами нужно в этой директории. После этого мы можем создать столько тестовых файлов, сколько захотим, и Cargo скомпилирует каждый из файлов в отдельный крейт.

Давайте создадим интеграционный тест. Рядом с кодом из листинга 11-12, который всё ещё в файле src/lib.rs, создайте каталог tests, создайте новый файл с именем tests/integration_test.rs. Структура директорий должна выглядеть так:

adder
├── Cargo.lock
├── Cargo.toml
├── src
│   └── lib.rs
└── tests
    └── integration_test.rs

Введите код из листинга 11-13 в файл tests/integration_test.rs file:

use adder;

#[test]
fn it_adds_two() {
    assert_eq!(4, adder::add_two(2));
}

Листинг 11-13: Интеграционная тест функция из крейта adder

Каждый файл в каталоге tests представляет собой отдельный крейт, поэтому нам нужно подключить нашу библиотеку в область видимости каждого тестового крейта. По этой причине мы добавляем use adder в верхней части кода, что не нужно нам делать в модульных тестах.

$ cargo test
   Compiling adder v0.1.0 (file:///projects/adder)
    Finished test [unoptimized + debuginfo] target(s) in 1.31s
     Running unittests src/lib.rs (target/debug/deps/adder-1082c4b063a8fbe6)

running 1 test
test tests::internal ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running tests/integration_test.rs (target/debug/deps/integration_test-1082c4b063a8fbe6)

running 1 test
test 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

Выходные данные представлены тремя разделами: модульные тесты, интеграционные тесты и тесты документации. Обратите внимание, что если какой-нибудь тест в одной из секций не пройдёт, последующие секции выполняться не будут. Например, если модульный тест провалился, не будет выведено результатов интеграционных и документационных тестов, потому что эти тесты будут выполняться только в том случае, если все модульные тесты завершатся успешно.

Первый раздел для модульных тестов такой же, как мы видели: одна строка для каждого модульного теста (один с именем internal, который мы добавили в листинге 11-12), а затем сводная строка для модульных тестов.

Раздел интеграционных тестов начинается со строки Running tests/integration_test.rs. Далее идёт строка для каждой тестовой функции в этом интеграционном тесте и итоговая строка для результатов интеграционного теста непосредственно перед началом раздела Doc-tests adder.

Каждый файл интеграционного теста имеет свой собственный раздел, поэтому, если мы добавим больше файлов в каталог tests, то здесь будет больше разделов интеграционного теста.

Мы всё ещё можем запустить определённую функцию в интеграционных тестах, указав имя тест функции в качестве аргумента в cargo test. Чтобы запустить все тесты в конкретном файле интеграционных тестов, используйте аргумент --test сопровождаемый именем файла у команды cargo test:

$ cargo test --test integration_test
   Compiling adder v0.1.0 (file:///projects/adder)
    Finished test [unoptimized + debuginfo] target(s) in 0.64s
     Running tests/integration_test.rs (target/debug/deps/integration_test-82e7799c1bc62298)

running 1 test
test it_adds_two ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

Эта команда запускает только тесты в файле tests/integration_test.rs.

Подмодули в интеграционных тестах

По мере добавления большего количества интеграционных тестов, можно создать более одного файла в каталоге tests, чтобы легче организовывать их; например, вы можете сгруппировать функции тестирования по функциональности, которую они проверяют. Как упоминалось ранее, каждый файл в каталоге tests скомпилирован как отдельный крейт, что полезно для создания отдельных областей видимости, чтобы более точно имитировать то, как конечные пользователи будут использовать ваш крейт. Однако это означает, что файлы в каталоге tests ведут себя не так, как файлы в src, как вы узнали в Главе 7 относительно того как разделить код на модули и файлы.

Различное поведение файлов в каталоге tests наиболее заметно, когда у вас есть набор вспомогательных функций, которые будут полезны в нескольких интеграционных тестовых файлах. Представим, что вы пытаетесь выполнить действия, описанные в разделе «Разделение модулей в разные файлы» главы 7, чтобы извлечь их в общий модуль. Например, вы создали файл tests/common.rs и поместили в него функцию setup, содержащую некоторый код, который вы будете вызывать из разных тестовых функций в нескольких тестовых файлах

pub fn setup() {
    // setup code specific to your library's tests would go here
}

Когда мы снова запустим тесты, мы увидим новый раздел в результатах тестов для файла common.rs, хотя этот файл не содержит никаких тестовых функций, более того, мы даже не вызывали функцию setup откуда либо:

$ cargo test
   Compiling adder v0.1.0 (file:///projects/adder)
    Finished test [unoptimized + debuginfo] target(s) in 0.89s
     Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)

running 1 test
test tests::internal ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running tests/common.rs (target/debug/deps/common-92948b65e88960b4)

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running tests/integration_test.rs (target/debug/deps/integration_test-92948b65e88960b4)

running 1 test
test 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

Упоминание файла common и появление в результатах выполнения тестов сообщения типа running 0 tests – это не то, чего мы хотели. Мы только хотели выделить некоторый общий код, который будет использоваться другими файлами интеграционных тестов.

Чтобы модуль common больше не появлялся в результатах выполнения тестов, вместо файла tests/common.rs мы создадим файл tests/common/mod.rs. Директория проекта теперь выглядит следующим образом:

├── Cargo.lock
├── Cargo.toml
├── src
│   └── lib.rs
└── tests
    ├── common
    │   └── mod.rs
    └── integration_test.rs

Здесь используется более раннее соглашение об именовании файлов, которое Rust также понимает. Мы говорили об этом в разделе “Альтернативные пути к файлам” главы 7. Именование файла таким образом говорит, что Rust не должен рассматривать модуль common как файл интеграционных тестов. Когда мы перемещаем код функции setup в файл tests/common/mod.rs и удаляем файл tests/common.rs, дополнительный раздел больше не будет отображаться в результатах тестов. Файлы в подкаталогах каталога tests не компилируются как отдельные крейты или не появляются в результатах выполнения тестов.

После того, как мы создали файл tests/common/mod.rs, мы можем использовать его в любых файлах интеграционных тестов как обычный модуль. Вот пример вызова функции setup из теста it_adds_two в файле tests/integration_test.rs:

use adder;

mod common;

#[test]
fn it_adds_two() {
    common::setup();
    assert_eq!(4, adder::add_two(2));
}

Обратите внимание, что объявление mod common; совпадает с объявлением модуля, которое продемонстрировано в листинге 7-21. Затем в тестовой функции мы можем вызвать функцию common::setup().

Интеграционные тесты для бинарных крейтов

Если наш проект является бинарным крейтом, который содержит только src/main.rs и не содержит src/lib.rs, мы не сможем создать интеграционные тесты в папке tests и подключить функции определённые в файле src/main.rs в область видимости с помощью оператора use. Только библиотечные крейты могут предоставлять функции, которые можно использовать в других крейтах; бинарные крейты предназначены только для самостоятельного запуска.

Это одна из причин, почему проекты на Rust, которые генерируют исполняемые модули, обычно имеют простой файл src/main.rs, который в свою очередь вызывает логику, которая находится в файле src/lib.rs. Используя такую структуру, интеграционные тесты могут проверить библиотечный крейт, используя оператор use для подключения важной функционала. Если этот важный функционал работает, то и небольшое количество кода в файле src/main.rs также будет работать, а значит этот небольшой объём кода не нуждается в проверке.

Рустест:  Советы по эффективной подготовке к экзамену: организуйте и преуспейте

Итоги

Средства тестирования языка Rust предоставляют способ задать ожидаемое поведение кода, чтобы убедиться, что он всё ещё соответствует вашим ожиданиям даже после внесения изменений. Модульные тесты проверяют различные части библиотеки по отдельности и могут тестировать приватные детали реализации. Интеграционные тесты проверяют, что части библиотеки работают корректно сообща. Эти тесты используют для тестирования кода открытый API библиотеки, таким же образом, как его будет использовать внешний код. Хотя система типов Rust и правила владения помогают предотвратить некоторые виды ошибок, тесты по-прежнему важны для уменьшения количества логических ошибок, связанных с поведением вашего кода.

Давайте объединим знания, полученные в этой и предыдущей главах, чтобы поработать над проектом!

Язык программирования Rust

Язык программирования Rust

Контролирование хода выполнения тестов

Подобно тому, как cargo run выполняет компиляцию вашего кода, а затем запускает полученный двоичный файл, cargo test компилирует ваш код в режиме тестирования и запускает полученный бинарник с тестами. Двоичный файл, создаваемый cargo test, по умолчанию запускает все тесты параллельно и перехватывает вывод, генерируемый во время выполнения тестов, предотвращая их вывод на экран для облегчения чтения вывода, относящегося к результатам тестирования. Однако вы можете указать параметры командной строки, чтобы изменить это поведение по умолчанию.

Часть параметров командной строки передаётся в cargo test, а часть – в итоговый двоичный файл с тестами. Чтобы разделить эти два типа аргументов, нужно сначала указать аргументы, которые идут в cargo test, затем использовать разделитель --, а потом те, которые попадут в двоичный файл теста. Выполнение cargo test --help выводит опции, которые вы можете использовать с cargo test, а выполнение cargo test -- --help выводит опции, которые вы можете использовать за разделителем.

Выполнение тестов параллельно или последовательно

Когда вы запускаете несколько тестов, по умолчанию они выполняются параллельно с использованием потоков, что означает, что они завершатся быстрее, и вы быстрее получите результаты. Поскольку тесты выполняются параллельно, вы должны убедиться, что ваши тесты не зависят друг от друга или от какого-либо общего состояния, включая общее окружение, например, текущий рабочий каталог или переменные окружения.

Например, допустим, каждый из ваших тестов запускает код, который создаёт файл на диске с именем test-output.txt и записывает некоторые данные в этот файл. Затем каждый тест считывает данные из этого файла и утверждает, что файл содержит определённое значение, которое в каждом тесте разное. Поскольку все тесты выполняются одновременно, один из тестов может перезаписать файл в промежутке между записью и чтением файла другим тестом. Тогда второй тест потерпит неудачу, но не потому, что код неверен, а потому, что эти тесты мешали друг другу при параллельном выполнении. Одно из решений – убедиться, что каждый тест пишет в свой отдельный файл; другое решение – запускать тесты по одному.

Если вы не хотите запускать тесты параллельно или хотите более детальный контроль над количеством используемых потоков, можно установить флаг --test-threads и то количество потоков, которое вы хотите использовать для теста. Взгляните на следующий пример:

$ cargo test -- --test-threads=1

Мы устанавливаем количество тестовых потоков равным 1 , указывая программе не использовать параллелизм. Выполнение тестов с использованием одного потока займёт больше времени, чем их параллельное выполнение, но тесты не будут мешать друг другу, если они совместно используют состояние.

Демонстрация результатов работы функции

По умолчанию, если тест пройден, система управления запуска тестов блокирует вывод на печать, т.е. если вы вызовете макрос println! внутри кода теста и тест будет пройден, вы не увидите вывода на консоль результатов вызова println!. Если же тест не был пройден, все информационные сообщение, а также описание ошибки будет выведено на консоль.

Например, в коде (11-10) функция выводит значение параметра с поясняющим текстовым сообщением, а также возвращает целочисленное константное значение 10. Далее следует тест, который имеет правильный входной параметр и тест, который имеет ошибочный входной параметр:

fn prints_and_returns_10(a: i32) -> i32 {
    println!("I got the value {}", a);
    10
}

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

    #[test]
    fn this_test_will_pass() {
        let value = prints_and_returns_10(4);
        assert_eq!(10, value);
    }

    #[test]
    fn this_test_will_fail() {
        let value = prints_and_returns_10(8);
        assert_eq!(5, value);
    }
}

Листинг 11-10: Тест функции, которая использует макрос println!

Результат вывода на консоль команды cargo test:

$ cargo test
   Compiling silly-function v0.1.0 (file:///projects/silly-function)
    Finished test [unoptimized + debuginfo] target(s) in 0.58s
     Running unittests src/lib.rs (target/debug/deps/silly_function-160869f38cff9166)

running 2 tests
test tests::this_test_will_fail ... FAILED
test tests::this_test_will_pass ... ok

failures:

---- tests::this_test_will_fail stdout ----
I got the value 8
thread 'main' panicked at 'assertion failed: `(left == right)`
  left: `5`,
 right: `10`', src/lib.rs:19:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace


failures:
    tests::this_test_will_fail

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`

Обратите внимание, что нигде в этом выводе мы не видим сообщения I got the value 4 , которое печатается при выполнении пройденного теста. Этот вывод был записан. Результат неудачного теста, I got the value 8 , появляется в разделе итоговых результатов теста, который также показывает причину неудачного теста.

Если мы хотим видеть напечатанные результаты прохождения тестов, мы можем сказать Rust, чтобы он также показывал результаты успешных тестов с помощью --show-output.

$ cargo test -- --show-output

Когда мы снова запускаем тесты из Листинга 11-10 с флагом --show-output , мы видим следующий результат:

$ cargo test -- --show-output
   Compiling silly-function v0.1.0 (file:///projects/silly-function)
    Finished test [unoptimized + debuginfo] target(s) in 0.60s
     Running unittests src/lib.rs (target/debug/deps/silly_function-160869f38cff9166)

running 2 tests
test tests::this_test_will_fail ... FAILED
test tests::this_test_will_pass ... ok

successes:

---- tests::this_test_will_pass stdout ----
I got the value 4


successes:
    tests::this_test_will_pass

failures:

---- tests::this_test_will_fail stdout ----
I got the value 8
thread 'main' panicked at 'assertion failed: `(left == right)`
  left: `5`,
 right: `10`', src/lib.rs:19:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace


failures:
    tests::this_test_will_fail

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`

Запуск подмножества тестов по имени

Бывают случаи, когда в запуске всех тестов нет необходимости и нужно запустить только несколько тестов. Если вы работаете над функцией и хотите запустить тесты, которые исследуют её работу – это было бы удобно. Вы можете это сделать, используя команду cargo test, передав в качестве аргумента имена тестов.

Для демонстрации, как запустить группу тестов, мы создадим группу тестов для функции add_two function, как показано в Листинге 11-11, и постараемся выбрать какие из них запускать.

pub fn add_two(a: i32) -> i32 {
    a + 2
}

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

    #[test]
    fn add_two_and_two() {
        assert_eq!(4, add_two(2));
    }

    #[test]
    fn add_three_and_two() {
        assert_eq!(5, add_two(3));
    }

    #[test]
    fn one_hundred() {
        assert_eq!(102, add_two(100));
    }
}

Листинг 11-11: Три теста с различными именами

Если вы выполните команду cargo test без уточняющих аргументов, все тесты выполнятся параллельно:

$ cargo test
   Compiling adder v0.1.0 (file:///projects/adder)
    Finished test [unoptimized + debuginfo] target(s) in 0.62s
     Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)

running 3 tests
test tests::add_three_and_two ... ok
test tests::add_two_and_two ... ok
test tests::one_hundred ... ok

test result: ok. 3 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

Запуск одного теста

Мы можем запустить один тест с помощью указания его имени в команде cargo test:

$ cargo test one_hundred
   Compiling adder v0.1.0 (file:///projects/adder)
    Finished test [unoptimized + debuginfo] target(s) in 0.69s
     Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)

running 1 test
test tests::one_hundred ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 2 filtered out; finished in 0.00s

Был запущен только тест с названием one_hundred; два других теста не соответствовали этому названию. Результаты теста с помощью вывода 2 filtered out дают нам понять, что у нас было больше тестов, но они не были запущены.

Таким образом мы не можем указать имена нескольких тестов; будет использоваться только первое значение, указанное для cargo test . Но есть способ запустить несколько тестов.

Использование фильтров для запуска нескольких тестов

Мы можем указать часть имени теста, и будет запущен любой тест, имя которого соответствует этому значению. Например, поскольку имена двух наших тестов содержат add, мы можем запустить эти два, запустив cargo test add:

$ cargo test add
   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 2 tests
test tests::add_three_and_two ... ok
test tests::add_two_and_two ... ok

test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 1 filtered out; finished in 0.00s

Эта команда запускала все тесты с add в имени и отфильтровывала тест с именем one_hundred . Также обратите внимание, что модуль, в котором появляется тест, становится частью имени теста, поэтому мы можем запускать все тесты в модуле, фильтруя имя модуля.

Игнорирование тестов

Бывает, что некоторые тесты требуют продолжительного времени для своего исполнения, и вы хотите исключить их из исполнения при запуске cargo test. Вместо перечисления в командной строке всех тестов, которые вы хотите запускать, вы можете аннотировать тесты, требующие много времени для прогона, атрибутом ignore, чтобы исключить их, как показано здесь:

#[test]
fn it_works() {
    assert_eq!(2 + 2, 4);
}

#[test]
#[ignore]
fn expensive_test() {
    // code that takes an hour to run
}
$ cargo test
   Compiling adder v0.1.0 (file:///projects/adder)
    Finished test [unoptimized + debuginfo] target(s) in 0.60s
     Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)

running 2 tests
test expensive_test ... ignored
test it_works ... ok

test result: ok. 1 passed; 0 failed; 1 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

Функция expensive_test помечена как ignored. Если вы хотите выполнить только проигнорированные тесты, вы можете воспользоваться командой cargo test -- --ignored:

$ cargo test -- --ignored
   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 expensive_test ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 1 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

Управляя тем, какие тесты запускать, вы можете быть уверены, что результаты вашего cargo test будут быстрыми. Когда вы дойдёте до момента, где имеет смысл проверить результаты тестов ignored, и у вас есть время дождаться их результатов, вы можете запустить их с помощью cargo test -- --ignored. Если вы хотите запустить все тесты независимо от того, игнорируются они или нет, выполните cargo test -- --include-ignored.

The Rust Programming Language

Rust By Example

Unit testing

Tests are Rust functions that verify that the non-test code is functioning in
the expected manner. The bodies of test functions typically perform some setup,
run the code we want to test, then assert whether the results are what we
expect.

Tests fail when something in the test function panics. There are some
helper macros:

  • assert!(expression) – panics if expression evaluates to false.
  • assert_eq!(left, right) and assert_ne!(left, right) – testing left and
    right expressions for equality and inequality respectively.
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

// This is a really bad adding function, its purpose is to fail in this
// example.
#[allow(dead_code)]
fn bad_add(a: i32, b: i32) -> i32 {
    a - b
}

#[cfg(test)]
mod tests {
    // Note this useful idiom: importing names from outer (for mod tests) scope.
    use super::*;

    #[test]
    fn test_add() {
        assert_eq!(add(1, 2), 3);
    }

    #[test]
    fn test_bad_add() {
        // This assert would fire and test will fail.
        // Please note, that private functions can be tested too!
        assert_eq!(bad_add(1, 2), 3);
    }
}

Tests can be run with cargo test.

$ cargo test

running 2 tests
test tests::test_bad_add ... FAILED
test tests::test_add ... ok

failures:

---- tests::test_bad_add stdout ----
        thread 'tests::test_bad_add' panicked at 'assertion failed: `(left == right)`
  left: `-1`,
 right: `3`', src/lib.rs:21:8
note: Run with `RUST_BACKTRACE=1` for a backtrace.


failures:
    tests::test_bad_add

test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out

Tests and ?

None of the previous unit test examples had a return type. But in Rust 2018,
your unit tests can return Result<()>, which lets you use ? in them! This
can make them much more concise.

fn sqrt(number: f64) -> Result<f64, String> {
    if number >= 0.0 {
        Ok(number.powf(0.5))
    } else {
        Err("negative floats don't have square roots".to_owned())
    }
}

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

    #[test]
    fn test_sqrt() -> Result<(), String> {
        let x = 4.0;
        assert_eq!(sqrt(x)?.powf(2.0), x);
        Ok(())
    }
}

Testing panics

pub fn divide_non_zero_result(a: u32, b: u32) -> u32 {
    if b == 0 {
        panic!("Divide-by-zero error");
    } else if a < b {
        panic!("Divide result is zero");
    }
    a / b
}

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

    #[test]
    fn test_divide() {
        assert_eq!(divide_non_zero_result(10, 2), 5);
    }

    #[test]
    #[should_panic]
    fn test_any_panic() {
        divide_non_zero_result(1, 0);
    }

    #[test]
    #[should_panic(expected = "Divide result is zero")]
    fn test_specific_panic() {
        divide_non_zero_result(1, 10);
    }
}

Running these tests gives us:

$ cargo test

running 3 tests
test tests::test_any_panic ... ok
test tests::test_divide ... ok
test tests::test_specific_panic ... ok

test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

   Doc-tests tmp-test-should-panic

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

Running specific tests

To run specific tests one may specify the test name to cargo test command.

$ cargo test test_any_panic
running 1 test
test tests::test_any_panic ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 2 filtered out

   Doc-tests tmp-test-should-panic

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

To run multiple tests one may specify part of a test name that matches all the
tests that should be run.

$ cargo test panic
running 2 tests
test tests::test_any_panic ... ok
test tests::test_specific_panic ... ok

test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 1 filtered out

   Doc-tests tmp-test-should-panic

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

Ignoring tests

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

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

    #[test]
    fn test_add() {
        assert_eq!(add(2, 2), 4);
    }

    #[test]
    fn test_add_hundred() {
        assert_eq!(add(100, 2), 102);
        assert_eq!(add(2, 100), 102);
    }

    #[test]
    #[ignore]
    fn ignored_test() {
        assert_eq!(add(0, 0), 0);
    }
}
$ cargo test
running 3 tests
test tests::ignored_test ... ignored
test tests::test_add ... ok
test tests::test_add_hundred ... ok

test result: ok. 2 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out

   Doc-tests tmp-ignore

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

$ cargo test -- --ignored
running 1 test
test tests::ignored_test ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

   Doc-tests tmp-ignore

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

The rustc book

Tests

rustc has a built-in facility for building and running tests for a crate.
More information about writing and running tests may be found in the Testing
Chapter
of the Rust Programming Language book.

#[test]
fn it_works() {
    assert_eq!(2 + 2, 4);
}

Tests “pass” if they return without an error. They “fail” if they panic, or
return a type such as Result that implements the Termination trait
with a non-zero value.

  • The crate will be built as a bin crate type, forcing it to be an
    executable.
  • Links the executable with libtest, the test harness that is part of the
    standard library, which handles running the tests.
  • Synthesizes a main function which will process command-line arguments
    and run the tests. This new main function will replace any existing main
    function as the entry point of the executable, though the existing main
    will still be compiled.
  • Enables the test cfg option, which allows your code to use conditional
    compilation to detect if it is being built as a test.
  • Enables building of functions annotated with the test
    and bench attributes, which will be run by the test
    harness.

After the executable is created, you can run it to execute the tests and
receive a report on what passes and fails. If you are using Cargo to manage
your project, it has a built-in cargo test command which handles all of
this automatically. An example of the output looks like this:

running 4 tests
test it_works ... ok
test check_valid_args ... ok
test invalid_characters ... ok
test walks_the_dog ... ok

test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

Note: Tests must be built with the unwind panic
strategy
. This is because all tests run in the same
process, and they are intended to catch panics, which is not possible with
the abort strategy. See the unstable -Z panic-abort-tests option for
experimental support of the abort strategy by spawning tests in separate
processes.

Test attributes

  • #[test] — Indicates a function is a test to be run.
  • #[bench] — Indicates a function is a benchmark to be
    run. Benchmarks are currently unstable and only available in the nightly
    channel, see the unstable docs for more details.
  • #[should_panic] — Indicates that the test
    function will only pass if the function panics.
  • #[ignore] — Indicates that the test function will be
    compiled, but not run by default. See the --ignored and
    --include-ignored options to run these tests.

CLI arguments

The libtest harness has several command-line arguments to control its
behavior.

Note: When running with cargo test, the libtest CLI arguments must be
passed after the -- argument to differentiate between flags for Cargo and
those for the harness. For example: cargo test -- --nocapture

Filters

Positional arguments (those without a - prefix) are treated as filters which
will only run tests whose name matches one of those strings. The filter will
match any substring found in the full path of the test function. For example,
if the test function it_works is located in the module
utils::paths::tests, then any of the filters works, path, utils::, or
utils::paths::tests::it_works will match that test.

Action options

--list

Prints a list of all tests and benchmarks. Does not run any of the tests.
Filters can be used to list only matching tests.

-h, --help

Displays usage information and command-line options.

Selection options

--test

This is the default mode where all tests will be run as well as running all
benchmarks with only a single iteration (to ensure the benchmark works,
without taking the time to actually perform benchmarking). This can be
combined with the --bench flag to run both tests and perform full
benchmarking.

--bench

This runs in a mode where tests are ignored, and only runs benchmarks. This
can be combined with --test to run both benchmarks and tests.

--exact

This forces filters to match the full path of the test exactly.
For example, if the test it_works is in the module utils::paths::tests,
then only the string utils::paths::tests::it_works will match that test.

--skip FILTER

Skips any tests whose name contains the given FILTER string. This flag may
be passed multiple times.

--ignored

Runs only tests that are marked with the ignore
attribute
.

--include-ignored

Runs both ignored and non-ignored tests.

--exclude-should-panic

Excludes tests marked with the should_panic
attribute
.

Execution options

--test-threads NUM_THREADS

Sets the number of threads to use for running tests in parallel. By default,
uses the amount of concurrency available on the hardware as indicated by
available_parallelism.

This can also be specified with the RUST_TEST_THREADS environment variable.

--force-run-in-process

Forces the tests to run in a single process when using the abort panic
strategy
.

--ensure-time

--shuffle

Runs the tests in random order, as opposed to the default alphabetical order.

This may also be specified by setting the RUST_TEST_SHUFFLE environment
variable to anything but 0.

The random number generator seed that is output can be passed to
--shuffle-seed to run the tests in the same order
again.

Note that --shuffle does not affect whether the tests are run in parallel. To
run the tests in random order sequentially, use --shuffle --test-threads 1.

--shuffle-seed SEED

Like --shuffle, but seeds the random number generator with
SEED. Thus, calling the test harness with --shuffle-seed SEED twice runs
the tests in the same order both times.

SEED is any 64-bit unsigned integer, for example, one produced by
--shuffle.

This can also be specified with the RUST_TEST_SHUFFLE_SEED environment
variable.

Output options

-q, --quiet

Displays one character per test instead of one line per test. This is an alias
for --format=terse.

--nocapture

Does not capture the stdout and stderr of the test, and allows tests to print
to the console. Usually the output is captured, and only displayed if the test
fails.

This may also be specified by setting the RUST_TEST_NOCAPTURE environment
variable to anything but 0.

--show-output

Displays the stdout and stderr of successful tests after all tests have run.

Contrast this with --nocapture which allows tests to print
while they are running, which can cause interleaved output if there are
multiple tests running in parallel, --show-output ensures the output is
contiguous, but requires waiting for all tests to finish.

--color COLOR

Control when colored terminal output is used. Valid options:

  • auto: Colorize if stdout is a tty and --nocapture is not
    used. This is the default.
  • always: Always colorize the output.
  • never: Never colorize the output.

--format FORMAT

Controls the format of the output. Valid options:

  • pretty: This is the default format, with one line per test.
  • terse: Displays only a single character per test. --quiet
    is an alias for this option.
  • json: Emits JSON objects, one per line. ⚠️ 🚧 This option is
    unstable, and requires the -Z unstable-options flag.
    See tracking issue #49359
    for more information.

--logfile PATH

Writes the results of the tests to the given file.

--report-time

Unstable options

Some CLI options are added in an “unstable” state, where they are intended for
experimentation and testing to determine if the option works correctly, has
the right design, and is useful. The option may not work correctly, break, or
change at any time. To signal that you acknowledge that you are using an
unstable option, they require passing the -Z unstable-options command-line
flag.

Benchmarks

Custom test frameworks

35 лет назад пилот-любитель Матиас Руст перелетел из Хельсинки в Москву, преодолев на пути все советские системы ПВО. Видевшие его советские истребители не получали команд сбивать или сажать самолет, а несколько раз его принимали за свой. Руст приземлился на Большом Москворецком мосту, рядом с Красной площадью возле Кремля, и спокойно раздавал там автографы, пока за ним не пришли из КГБ. Подробности о мотивах, причинах успеха летчика и его дальнейшей судьбе — в материале «Газеты.Ru».

28 мая 1987 года молодой западногерманский пилот-любитель Матиас Руст навсегда вошел в историю, сумев беспрепятственно пролететь от Хельсинки до Москвы сквозь глубокоэшелонированную систему ПВО советского государства, считавшуюся тогда самой надежной в мире. Он посадил свой легкомоторный самолет Cessna 172 Skyhawk на Большом Москворецком мосту, докатившись затем до Васильевского спуска к Храму Василия Блаженного. Его «подвиг» немедленно послужил источником мемов — вроде переименования Красной площади в «Шереметьево-3», о нем слагали песни перестроечные барды, им невольно восхищались даже те, кто называл его не иначе как «злостным и безответственным авиахулиганом». Он, возможно, ускорил перестройку, дав удачный повод генсеку Михаилу Горбачеву показательно уволить сотни высокопоставленных несговорчивых военных во главе с министром обороны Сергеем Соколовым, проведя тем самым самую масштабную чистку со времен сталинских репрессий 1930-х годов, но при этом пилот никогда не был принят самим Горбачевым, на встречу с которым он искренне надеялся.

«Мне необходимо было встретиться с Михаилом Горбачевым», — заявит он затем на суде в ответ на вопрос: «Зачем вы прилетели в Москву?»

Невероятное стечение обстоятельств, связанных с полетом Руста, вполне предсказуемым образом стало подпитывать разного рода теории заговора, в которых фигурировали и НАТО, и ЦРУ, и сам Горбачев, отдавший якобы соответствующие тайные указания председателю КГБ СССР Владимиру Крючкову, и даже чья-то подводная лодка, создавшая странное масляное пятно на месте мнимой аварии самолета Руста в Финском заливе. Однако время не оставило от этих предположений практически ничего, в полном соответствии с известным тезисом: «Никогда не приписывайте злому умыслу то, что вполне можно объяснить глупостью».

Спустя 35 лет этого бывшего отчаянного подростка — на момент посадки Матиасу Русту не исполнилось даже 19 лет — никто особенно не чествует, столкновения с законом лишили его былого морального авторитета; книга воспоминаний, написанная им в 2012 году, провалилась в продаже, новых откровений от него не ждут. Когда-то западная пресса восхищенно называла его «новым Красным Бароном» и «Дон Кихотом небес». В Эстонии на месте пересечения Рустом границы советского государства на днях даже открыли памятник в виде посадочной полосы, но это всего лишь частная инициатива.

В России Руста тоже вспоминали без симпатии. «Руст пробуравил спортивной «Сессной» воздушную границу СССР; хваленое ПВО ядерной сверхдержавы обнаружило полную беспомощность системы в целом; через брешь, пробитую Рустом, вышел воздух, и шарик начал неумолимо сдуваться», — писал пятнадцать лет назад Александр Архангельский.

Итальянская газета La Repubblica тогда же взяла интервью у отчаянного пилота, где он признавался: «С помощью маленькой «Ceссны» я решил осуществить мечту: прилететь с Запада прямо на Красную площадь. Как жест мира: полет в качестве символического моста между двумя мирами». Так или иначе, но встряску испытала не только советская система, но и западный мир. Для населяющих его обывателей Советский Союз из мощной «Империи Зла» (Evil Empire), как его называл Рональд Рейган после сбитого в 1984 году южнокорейского «Боинга» с 269 жертвами, внезапно превратился во вполне доброжелательного соседа, не решающегося сбить даже явного нарушителя, направлявшегося к сердцу столицы, — то ли из-за всеобщего развала, то ли просто из-за нерешительности военных, по рукам и ногам связанных инструкцией до последнего не применять оружие.

В другом интервью, взятом у Руста американскими научными журналистами в июле 2005-го, пилот также говорит о своей идее «построить воображаемый мост», прилетев в Москву, и о 20-страничном манифесте, который он планировал передать Горбачеву и в котором говорилось о том, как добиться мира во всем мире. «И как бы Рейган продолжал говорить, что это «Империя Зла», если я на маленьком самолете могу прилететь туда и остаться целым и невредимым?» — спрашивал Руст.

В ходе своего полета он видел советский МиГ-23 и пилота в нем, старшего лейтенанта Пучнина, готового немедленно применить оружие или, как сам старлей потом признавался, сжечь самолетик огнем из сопла, включив форсаж, — но МиГ был отозван, тревогу в очередной раз не объявили, а «Ceссну» в очередной раз потеряли.

Оказавшись над Москвой, Руст долго не мог найти Красную площадь, поскольку привык, что центры западных городов отмечают ряды сверкающих офисных башен. Но наконец он увидел характерную стену с башенками, окружающую Кремль. После этого он начал кружить, выбирая место для посадки.

«Сначала я думал, что, может быть, мне следует приземлиться внутри кремлевской стены, но потом понял, что, хотя места там предостаточно, КГБ может со мной сделать там нечто ужасное, — вспоминает он. — И если бы я приземлился за стеной, меня увидели бы от силы лишь несколько человек, они могли бы просто забрать меня и все потом отрицать. Приземлившись на открытой площади, я показался множеству людей, и в КГБ не могли арестовать меня незаметно и лгать потом об этом. Так что ради собственной безопасности я и отказался от этой идеи».

Кружа над Кремлем, Руст заметил подходящий шестиполосный мост через Москву-реку, ведущий на Красную площадь. Единственными препятствием были провода, натянутые с обоих концов моста и по его середине. Раст прикинул, что места там достаточно, чтобы пройти над первой линией проводов и затем, приземлившись, подрулить под другими проводами на площадь. Но выкатываясь из-под средней линии проводов, Руст заметил перед собой старую «Волгу». «Я двинулся влево, чтобы обойти ее, — рассказывал он, — и когда я это проделал, то увидел старика с таким выражением лица, как будто он не мог поверить в то, что наблюдает. Я понадеялся, что он не запаникует, не потеряет контроль над машиной и не врежется в меня».

Наконец «Сессна» прошла под последней линией проводов и выкатилась на площадь. Притормозив, Руст стал искать место для парковки. Он хотел поставить самолет на середину площади, перед Мавзолеем. Но Храм Василия Блаженного окружал небольшой забор с натянутой цепью, преграждавшей путь, поэтому «Сессна» так и осталась перед церковью.

Руст заглушил двигатель, затем на мгновение закрыл глаза и глубоко вдохнул. «Я помню это огромное чувство облегчения, как будто сбросил с себя большой груз». Он посмотрел на кремлевскую башню с часами. Было 18:43, прошло почти пять с половиной часов с тех пор, как он покинул Хельсинки. Выйдя из самолета и прислонившись к нему, пилот стал ждать штурма, однако его не последовало. Люди на Красной площади казались ошеломленными и не понимали, что происходит. Некоторые из них могли полагать, что самолет Руста был личным транспортом Горбачева или что все это — часть съемок какой-нибудь кинокартины. Но как только толпа поняла, что Руст и его «Сессна» прибыли с Запада, совершив один из самых сенсационных перелетов, свидетелями которого они все оказались, к нему приблизились.

«Вокруг меня собралась большая толпа, — говорил Руст. — Люди улыбались и подходили, чтобы пожать мне руку или попросить автограф. Там был молодой русский парень, который говорил по-английски. Он спросил меня, откуда я родом. Я сказал ему, что прибыл с Запада и хочу поговорить с Горбачевым, чтобы передать мирное послание, которое поможет ему убедить всех на Западе в том, что страна изменилась». Атмосфера была праздничной. Одна женщина дала ему кусок хлеба в знак дружбы. По словам Руста, какой-то курсант сказал ему, что «он восхищается моей инициативой, но что я должен был подать заявление на получение визы и договориться о встрече с Горбачевым, однако при этом он согласился, что меня, скорее всего, не пустили бы».

Руст не заметил, когда через толпу двинулись агенты КГБ, опрашивая людей и изымая фотоаппараты. Более чем через час после приземления прибыли два грузовика с вооруженными солдатами и грубо растолкали толпу. Они также установили барьеры вокруг самолета.

Из черного седана вышли трое мужчин и представились. Младший, переводчик, вежливо попросил у Руста паспорт и спросил, нет ли у него оружия. Затем они решили осмотреть самолет. После еще нескольких вопросов пилота попросили сесть в машину. Атмосфера встречи, по словам Руста, оставалась очень дружелюбной. «Сессну» доставили в московский международный аэропорт «Шереметьево» и разобрали для осмотра, а Руста отправили в Лефортовскую тюрьму.

Оказывается, Руст даже и не знал, что прилетел в Россию в День пограничника. Многие полагали, что он выбрал этот день, считая, что граница в это время будет менее защищенной, или, возможно, чтобы максимально досадить военным.

«Я не знал об этом, — говорит Раст. — Я сказал им: «Я западный немец. Откуда мне знать о ваших праздниках? Это было просто удачное стечение обстоятельств».

Следователи показали ему также фотографии моста, на который он приземлился. На этих снимках через мост было протянуто множество линий проводов, через которые невозможно было пробиться. Они спросили, как же прилетевший смог их все обойти? Озадаченный, Руст объяснил, что во время посадки он видел только три линии проводов. В ходе дальнейшего разбирательства выяснилось, что утром в тот день бригада рабочих временно сняла большую часть проводов из-за ремонтных работ; их заменили буквально на следующий же день. «Говорили, что я, должно быть, родился в рубашке — это русское выражение, означающее «рожденный счастливчиком», — признавался Руст. Хотя, конечно, это обстоятельство также хорошо легло в «теорию заговора».

В одном немецком периодическом издании была опубликована статья, в которой говорилось, что Руст совершил свой полет на спор. Другое издание сообщало, что он это сделал, чтобы произвести впечатление на свою девушку. Еще в одном материале говорилось, что пилот собирался разбрасывать листовки с призывом освободить из тюрьмы Рудольфа Гесса — приближенного Гитлера, все еще сидевшего тогда в тюрьме в Западном Берлине. Советская газета «Правда» объясняла странности причастностью Руста к международному заговору, в ходе которого он якобы должен был быть сбит, что спровоцировало бы новый международный конфликт. Какими бы нелепыми ни были все эти слухи, каждое такое предположение проверялось следователями, которые, впрочем, все равно закончили свою работу чрезвычайно быстро — к 23 июня 1987 года.

4 сентября, после трехдневного судебного разбирательства, коллегия из трех судей признала Руста виновным по всем пунктам обвинения и приговорила его к четырем годам заключения. Но уже 3 августа 1988 года, через два месяца после того, как Рейган и Горбачев согласовали договор о ликвидации ядерного оружия средней дальности в Европе, Верховный Совет в качестве «жеста доброй воли» решил освободить Руста из тюрьмы.

После возвращения в Германию Руст был лишен лицензии пилота, а в ноябре 1989 года, проходя альтернативную службу в больнице, серьезно ранил ножницами медсестру за то, что она отказалась проявить к нему благосклонность. За это его приговорили к 2,5 годам лишения свободы, освободив спустя 15 месяцев. Затем он торговал обувью, жертвуя деньги детскому дому, а в 1994 году вновь поехал в Россию, пытаясь встретиться с Горбачевым и организовать свою фирму — но ни то, ни другое ему не удалось. В 1997 году обратился в индуизм и женился на индийской девушке по имени Гита, дочери торговца чаем из Бомбея, но позже с ней развелся. В апреле 2001 года Руст снова предстал перед судом — на этот раз его обвинили в краже кашемирового свитера в универмаге и приговорили к большому штрафу, который впоследствии уменьшили. Еще одно столкновение с законом у него случилось в 2005 году: он был признан виновным в мошенничестве, но отделался штрафом в размере €1500. На жизнь он зарабатывал также игрой в покер, преподаванием йоги и, по его собственным словам, работая в качестве аналитика инвестиционного банка в Цюрихе.

Оцените статью
Официальный сайт - edu.rustest.ru