2

假设我有两个实现相同基本 API 的类,并且我想测试它们是否是“随机等效的” 1,至少在它们的方法的一个子集上。

例如,我编写了自己的“列表”类foo:list,而不是煞费苦心地为它编写一堆单元测试,我想将其std::list作为参考进行比较。也就是说,对 的任何操作序列都foo::list应该产生与 的相同序列相同的结果std::list

我可以列出操作的名称,但希望不会比这更多的样板。可以应用于其他“行为等效”类对的通用解决方案是理想的。


1 “随机等价”是指在许多系列操作中没有观察到差异,这显然没有完全证明等价。

4

1 回答 1

0

简而言之

构造一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以下是 lambda(通用 lambda 需要 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::vectorfoo::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
}

我可以在这上面多花几个小时,但我希望你能明白。我在这上面花了更多时间。

注意:我实际上并没有运行此代码,因此将其全部视为伪代码以了解这个想法,例如我可能错过了分号或类似的东西。

于 2019-11-08T18:39:07.527 回答