Я пытаюсь составить общее решение для предоставления приспособлений для модульного тестирования кода Rust. Я придумал макрос, который позволяет пользователю определять методы setup и teardown . Вот мое решение:

struct FooTestFixture {
    pub name : String
}

impl FooTestFixture {
    fn setup() -> FooTestFixture {
        FooTestFixture { name: String::from("Initialised") }
    }
}

fn teardown(fixture : &mut FooTestFixture) {
    fixture.name = "".to_string();
}

macro_rules! unit_test {
    ($name:ident $fixt:ident $expr:expr) => (
        #[test]
        fn $name() {
            let mut $fixt : FooTestFixture = FooTestFixture::setup();
            $expr;

            teardown(&mut $fixt);
        }
    )
}

unit_test! (heap_foo_fixture_should_be_initialised_using_macro f {
    assert_eq!(f.name, "Initialised");
});

Это работает. Единственная проблема в том, что макрос unit_test не является универсальным и привязан к имени фикстуры FooTestFixture . Это означает, что каждый тестовый модуль должен переопределять этот макрос для каждого тестового устройства, что не идеально. Что я хотел бы сделать, так это ввести переменную типа и использовать этот тип в раскрытии макроса. Углубляясь в макросы, я обнаружил, что есть элемент ty, который представляет тип, и подумал, что смогу сделать это ...

macro_rules! unit_test {
    ($name:ident $fixt:ident $ftype:ty $expr:expr) => (
        #[test]
        fn $name() {
            let mut $fixt : $ftype = $ftype::setup();
            $expr;

            teardown(&mut $fixt);
        }
    )
}

unit_test! (heap_foo_fixture_should_be_initialised_using_macro FooTestFixture f {
    assert_eq!(f.name, "Initialised");
});

Однако это не работает и приводит к следующей ошибке:

src \ tests \ heap_fixture_with_new.rs: 48: 40: 48:50 ошибка: $ftype:ty за которым следует $expr:expr, что не разрешено для фрагментов ty src \ tests \ heap_fixture_with_new.rs: 48 ($ name: identity $ fixt: ident $ ftype: ty $ expr: expr) = & gt; (

Как видите, в определении макроса я заменил ссылки на FooTestFixture на $ ftype.

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

4
Plastikfan 5 Май 2016 в 22:31

2 ответа

Лучший ответ

Что ж, я понял, что в конце концов мне не нужен ty. Я могу просто указать тип как параметр ident, чтобы работало следующее:

macro_rules! unit_test {
    ($name:ident $fixt:ident $ftype:ident $expr:expr) => (
        #[test]
        fn $name() {
            let mut $fixt = $ftype::setup();
            $expr;

            teardown(&mut $fixt);
        }
    )
}

unit_test! (foo_fixture_should_be_initialised_using_generic_macro f FooTestFixture {
    assert_eq!(f.name, "Initialised");
});
2
malbarbo 5 Май 2016 в 23:38

За ty не может сразу идти expr. Это должно быть , за которым следует определенный набор токенов :

  • =>
  • ,
  • =
  • |
  • ;
  • :
  • >
  • [
  • {
  • as
  • where

Аналогичное ограничение существует после expr, stmt, path и pat. Это было введено в RFC 550 для будущего -устойчивое возможное изменение синтаксиса Rust.

Чтобы исправить это, вам нужно изменить шаблон макроса, например

macro_rules! unit_test {
    ($name:ident $fixt:ident<$ftype:ty> $expr:expr) => (
//                          ^         ^ followed by '>' is OK

unit_test! (test_name fixture_name<FooTestFixture> f {    
//                                ^              ^
3
Tey' 4 Сен 2018 в 23:18