Тестирование приложений

Синтаксис тестов

Создание модуля тестирования

#![allow(unused)]
fn main() {
#[cfg(test)] // обязательный атрибут если пишем тест в этом же крейте, который тестируем
mod tests {//это такой же модуль как и все другие
    use super::*; //обязательно для получения доступа к головным функциям

    #[test]//обязательно перед функцией тестирования
    fn it_works() {
        let result = add(2, 2);
        assert_eq!(result, 4);
    }
}

}

Запуск тестирования

cargo test #полный тест
cargo test it_works #перечисление функций теста
cargo test it #тест по контексту, запустит все функции которые имеют в названии it
cargo test -- --ignored #тест функций с пометкой ignore

Функциии проверки

assert!

assert! Возвращает true если выражение вернет true

#![allow(unused)]
fn main() {
assert!(larger.can_hold(&smaller));
}

assert_eq! и assert_ne!

assert_eq! Возвращает true если условие выполняется, assert_eq! возвращает true если не выполняется

#![allow(unused)]
fn main() {
assert_eq!(result, 4);
}

Сообщение об ошибках в тесте

Второй параметр в макросе assert!

#![allow(unused)]
fn main() {
    #[test]
    fn greeting_contains_name() {
        let result = greeting("Carol");
        assert!(
            result.contains("Carol"),
            "Greeting did not contain name, value was `{result}`" //это сообщение будет выведено в случае ошибки
        );
    }
}

При ошибке в протоколе будут выведены все сообщения println!

В теле основной функции все println! будут выведены в протокол

Макрос should_panic

#![allow(unused)]
fn main() {
    #[test]
    #[should_panic]
    fn greater_than_100() {
        Guess::new(200);
    }

}

Перехватывает все panic! в программе. Если panic случился,то тест прошел. Не проверяет какой был panic.

expected

Будет искать по контексту, что написал panic, если значения совпадают,то тест прошел.

#![allow(unused)]
fn main() {
    #[test]
    #[should_panic(expected = "less than or equal to 100")]
    fn greater_than_100() {
        Guess::new(200);
    }

}

Использование Result<T, E> в тестах

#![allow(unused)]
fn main() {
    #[test]
    fn it_works() -> Result<(), String> {
        let result = add(2, 2);

        if result == 4 {
            Ok(())
        } else {
            Err(String::from("two plus two does not equal four"))
        }
    }

}

Управление тестами

Не будет выполнять параллельно

cargo test -- --test-threads=1

Выполнит только в обном потоке. Не будет параллельности.

Напечатает все успешные тесты

cargo test -- --show-output

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

#![allow(unused)]
fn main() {
    #[test]
    #[ignore]
    fn expensive_test() {
        // code that takes an hour to run
    }

}

для вызова только проигнорированных тестов

cargo test -- --ignored

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

Внутри тестируемого крейта

Перед модулем обязательный атрибут #[cfg(test)]

#![allow(unused)]
fn main() {
pub fn add(left: u64, right: u64) -> u64 {
    left + right
}

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

    #[test]
    fn it_works() {
        let result = add(2, 2);
        assert_eq!(result, 4);
    }
}

}

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

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

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

  1. Внутри проекта создать директорию tests
  2. Создать несколько файлов с тестами
  3. В заголовке каждого крейта теста указать use adder::add_two; — ссылку на тестируемый модуль как внешний
  4. Модуль test создавать не нужно
#![allow(unused)]
fn main() {
use adder::add_two;

#[test]
fn it_adds_two() {
    let result = add_two(2);
    assert_eq!(result, 4);
}

}

Создание общих функций для тестов

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

#![allow(unused)]
fn main() {
pub fn setup() {
    // setup code specific to your library's tests would go here
}

}

Файлы в подкаталогах каталога tests не компилируются как отдельные крейты или не появляются в результатах выполнения тестов.

Пример вызова в тесте общей функции

#![allow(unused)]
fn main() {
use adder::add_two;

mod common;

#[test]
fn it_adds_two() {
    common::setup();

    let result = add_two(2);
    assert_eq!(result, 4);
}

}

Только библиотечные крейты могут предоставлять функции, которые можно использовать в других крейтах; бинарные крейты предназначены только для самостоятельного запуска.

Структура блочного тестового модуля

tests
├── helpers
│   ├── mod.rs
│   ├── test_module1.rs
│   ├── test_module2.rs
│   └── utils.rs
├── integration_test.rs
└── mod.rs

tests/mod.rs

#![allow(unused)]
fn main() {
// Объявляем модули (он один, если несколько папок, то перечисляем)
mod helpers;
//mod integration_test;

// Реэкспортируем функции из библиотеки если нужно
pub use test_learn::library_function;

}

tests/helpers/mod.rs

#![allow(unused)]
fn main() {
// Объявляем подмодули в папке helpers
mod test_module1;
mod test_module2;
//mod utils;

}

Тестовые модули

#![allow(unused)]
fn main() {
use test_learn::module1;

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

    #[test]
    fn test_add() {
        assert_eq!(module1::add(2, 3), 5);
        assert_eq!(module1::add(-1, 1), 0);
        assert_eq!(module1::add(0, 0), 0);
    }

   #[test]
   fn test_multiply() {
       assert_eq!(module1::multiply(2, 3), 6);
       assert_eq!(module1::multiply(5, 0), 0);
       assert_eq!(module1::multiply(-2, 3), -6);
   }

   #[test]
   fn test_edge_cases() {
       assert_eq!(module1::add(i32::MAX, 0), i32::MAX);
   }
}
}

Вывод тестов

cargo test 
   Compiling test_learn v0.1.0 (/home/edge/data/rs/test_learn)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.05s
     Running unittests src/lib.rs (target/debug/deps/test_learn-f7d50bd57ebc5cb3)

running 2 tests
test module1::tests::test_add ... ok
test module2::tests::test_calculator_new ... ok

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

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

running 2 tests
test test_integration ... ok
test test_library_function ... ok

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

     Running tests/mod.rs (target/debug/deps/mod-26cc964da696d58a)

running 6 tests
test helpers::test_module1::tests::test_add ... ok
test helpers::test_module1::tests::test_edge_cases ... ok
test helpers::test_module2::tests::test_calculator_sequence ... ok
test helpers::test_module2::tests::test_calculator_add ... ok
test helpers::test_module1::tests::test_multiply ... ok
test helpers::test_module2::tests::test_calculator_new ... ok

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

   Doc-tests test_learn

running 0 tests

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


cargo-test finished at Mon Nov  3 15:02:08, duration 0.10 s