Учитывая два класса, как я могу вероятностно проверить на эквивалентное поведение - PullRequest
2 голосов
/ 08 ноября 2019

Допустим, у меня есть два класса, которые реализуют один и тот же базовый API, и я хочу проверить, что они "стохастически эквивалентны" 1 , по крайней мере, над подмножеством их методов.

Например, я пишу свой собственный класс "list" foo:list и вместо того, чтобы кропотливо писать для него кучу модульных тестов, я хочу сравнить его с std::list в качестве ссылки. То есть любая последовательность операций на foo::list должна давать те же результаты, что и та же последовательность std::list.

Я в порядке, перечисляя названия операций, но, надеюсь, не намного более шаблонно, чем это,Универсальное решение, которое может применяться к другим парам «поведенчески эквивалентных» классов, является идеальным.


1 Под «стохастически эквивалентным» я подразумеваю, что никаких различий не наблюдается во многих серияхопераций, что явно не соответствует полному доказательству эквивалентности.

1 Ответ

0 голосов
/ 08 ноября 2019

Короче говоря

Создайте один foo::list и один std::list, а затем сравните их при выполнении операций над ними. На самом деле единственное отличие от обычного модульного теста состоит в том, что у вас есть два контейнера, и вместо непосредственного использования REQUIRE() для каждой операции с типом, который вы тестируете, вы выполняете операцию с типом, который вы тестируете, и эталонным типом, а затем сравниваетеих. Для этого мы предполагаем, что std::list или что-то без ошибок. Затем мы используем его в качестве нашей контрольной точки для , а не для . Другими словами, если операция завершается успешно с std::list и выполняется с foo::list, и они сравниваются равными, операция завершается успешно.

Пример

Вы знаете, что такое подмножество операцийчто вы можете использовать для сравнения состояния, а я нет, так что вот фиктивная функция сравнения

template <class T, class U>
bool compare_types(const T &t, const U &u)
{
    bool equivalent = true;
    //Generic comparisons here, like iterating over the elements to compare their values.
    //Of course update equal or just return false if the comparison fails.
    //If your types are not containers, perform whatever is needed to test equivalent state.
    return equivalent;
}

Как указывал Jarod42, это может стать более забавным и более общим, особенно если Op f нижелямбда (C ++ 14 необходим для общих лямбда-выражений):

template <class ValueT, class RefT, class TestT, class Op>
bool compare_op_with_value(RefT &t, TestT &u, Op f, const ValueT &value)
{
    if (!compare_types(t, u))
        return false;
    f(t, value);
    f(u, value);
    return compare_types(t, u);
}

Ваша функция может возвращать значение:

template <class ValueT, class RefT, class TestT, class Op>
bool compare_op_with_ret(RefT &t, TestT &u, Op f)
{
    if (!compare_types(t, u))
        return false;
    ValueT ret1 = f(t);
    ValueT ret2 = f(u);
    return ret1 == ret2 && compare_types(t, u);
}

... и т. д. для разыменованных типов возврата и т. д. Вам нужно будет написать новую функцию сравнения для каждого вида теста, но это довольно тривиально. Вам нужно будет добавить еще один параметр шаблона для возвращаемых типов, которые не совпадают (например, итератор).

Затем вам понадобится ваш тестовый пример (я добавил в std::vector как foo::list для описания). ..

TEMPLATE_TEST_CASE("StdFooCompare", "[list]", int)
{
    using std_type = std::list<TestType>;
    using foo_type = std::vector<TestType>;

    auto initializer = {0,1,2,3,4};
    std_type list1 = initializer;
    foo_type list2 = initializer;

    //testing insertion, using auto since insert() returns iterators
    auto insert_f = [](auto list, TestType value) -> auto {
        return list.insert(list.begin(), value);
    };
    REQUIRE(compare_op_with_value(list1, list2, insert_f, -1));

    //testing front(), being explicit about the return type rather than using auto
    auto front_f = [](auto list) -> TestType & {
        return list.front();
    };
    REQUIRE(compare_op_with_ret<TestType>(list1, list2, front_f));

    //continue testing along these lines
}

Я мог бы потратить еще пару часов на это, но я надеюсь, что вы поняли. Я потратил больше времени на это.

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

...