【问题标题】:How to write runnable tests of static_assert?如何编写 static_assert 的可运行测试?
【发布时间】:2013-07-01 16:02:08
【问题描述】:

我正在为包含static_asserts 的源代码库编写单元测试套件。 我想保证这些static_asserts 做的不多也不少于他们 在设计方面,我们希望这样做。所以我希望能够测试它们。

我当然可以添加导致static asserts 的接口的不可编译单元测试 被综合各种手段侵犯,评论或#if 0他们都出来了,我个人 向用户保证,如果其中任何一个未注释,那么他们将观察 该库无法编译。

但这太荒谬了。相反,我想拥有 在单元测试套件的上下文中,一些设备将取代 static_assert 具有等效引发的运行时异常,即 测试框架可以捕获并有效报告:此代码将具有static_asserted 在实际构建中。

我是否忽略了一些明显的原因,为什么这是一个愚蠢的想法?

如果没有,怎么办?宏观仪器是一种明显的方法,我不 排除它。但也可能,最好是,使用模板专业化或 SFINAE 方法?

【问题讨论】:

  • 我个人的直觉是静态断言应该在您运行任何测试之前捕获编程错误。我没有立即看到为什么他们应该接受测试的紧迫原因......
  • Kerrek 所说的。如果您的构建环境没有将“测试未构建”视为失败,那就有问题了。
  • 我认为没有比制作一个在测试模式下输出 static_assert 或运行时异常的宏更好的了。在元编程的情况下,测试你的编译断言可能是有意义的。
  • @KerrekSB, @Xeo 这是模板代码。如果按照设计,某些通用形式的表达式 E0,..,En 不应该编译,则只能通过编写某种尝试实例化 E0,..,En 并报告编译失败的回归测试来进行系统验证i>,对于每个Ei。在测试模式下“例外化”static_asserts 的 shim 比将相关测试单元制作成整个程序以使用 autoconf 或类似工具进行试验要简单得多。这样更有意义吗?
  • @a.lasram 是的,元编程就在其中。但是有条件地扩展为static assertthrow 的宏并不是一个即时的答案,因为您不能只将throw 放在与static_assert 相同的位置。 并且将希望异常的what()static_assert 的诊断相同。

标签: unit-testing c++11 static-assert


【解决方案1】:

由于我对这个问题的兴趣似乎是一个孤独的怪人,所以我已经摇摆不定 给自己一个答案,头文件基本上是这样的:

exceptionalized_static_assert.h

#ifndef TEST__EXCEPTIONALIZE_STATIC_ASSERT_H
#define TEST__EXCEPTIONALIZE_STATIC_ASSERT_H

/* Conditionally compilable apparatus for replacing `static_assert`
    with a runtime exception of type `exceptionalized_static_assert`
    within (portions of) a test suite.
*/
#if TEST__EXCEPTIONALIZE_STATIC_ASSERT == 1

#include <string>
#include <stdexcept>

namespace test {

struct exceptionalized_static_assert : std::logic_error
{
    exceptionalized_static_assert(char const *what)
    : std::logic_error(what){};
    virtual ~exceptionalized_static_assert() noexcept {}
};

template<bool Cond>
struct exceptionalize_static_assert;

template<>
struct exceptionalize_static_assert<true>
{
    explicit exceptionalize_static_assert(char const * reason) {
        (void)reason;
    }
};


template<>
struct exceptionalize_static_assert<false>
{
    explicit exceptionalize_static_assert(char const * reason) {
        std::string s("static_assert would fail with reason: ");
        s += reason;
        throw exceptionalized_static_assert(s.c_str());
    }
};

} // namespace test

// A macro redefinition of `static_assert`
#define static_assert(cond,gripe) \
    struct _1_test \
    : test::exceptionalize_static_assert<cond> \
    {   _1_test() : \
        test::exceptionalize_static_assert<cond>(gripe){}; \
    }; \
    _1_test _2_test

#endif // TEST__EXCEPTIONALIZE_STATIC_ASSERT == 1

#endif // EOF

此标头仅包含在测试套件中,然后它将使 可见 static_assert 的宏重定义仅在测试套件时可见 是用

构建的
`-DTEST__EXCEPTIONALIZE_STATIC_ASSERT=1`    

这个装置的使用可以用一个玩具模板库来勾勒出来:

my_template.h

#ifndef MY_TEMPLATE_H
#define MY_TEMPLATE_H

#include <type_traits>

template<typename T>
struct my_template
{
    static_assert(std::is_pod<T>::value,"T must be POD in my_template<T>");

    explicit my_template(T const & t = T())
    : _t(t){}
    // ...
    template<int U>
    static int increase(int i) {
        static_assert(U != 0,"I cannot be 0 in my_template<T>::increase<I>");
        return i + U;
    }
    template<int U>
    static constexpr int decrease(int i) {
        static_assert(U != 0,"I cannot be 0 in my_template<T>::decrease<I>");
        return i - U;
    }
    // ...
    T _t;
    // ...  
};

#endif // EOF

试着想象代码足够大和复杂,以至于你 不能随便调查一下并挑选出static_asserts 和 满足自己,你知道他们为什么在那里,他们满足 他们的设计目的。您信任回归测试。

这里是my_template.h 的玩具回归测试套件:

test.cpp

#include "exceptionalized_static_assert.h"
#include "my_template.h"
#include <iostream>

template<typename T, int I>
struct a_test_template
{
    a_test_template(){};
    my_template<T> _specimen;
    //...
    bool pass = true;
};

template<typename T, int I>
struct another_test_template
{
    another_test_template(int i) {
        my_template<T> specimen;
        auto j = specimen.template increase<I>(i);
        //...
        (void)j;
    }
    bool pass = true;
};

template<typename T, int I>
struct yet_another_test_template
{
    yet_another_test_template(int i) {
        my_template<T> specimen;
        auto j = specimen.template decrease<I>(i);
        //...
        (void)j;
    }
    bool pass = true;
};

using namespace std;

int main()
{
    unsigned tests = 0;
    unsigned passes = 0;

    cout << "Test: " << ++tests << endl;    
    a_test_template<int,0> t0;
    passes += t0.pass;
    cout << "Test: " << ++tests << endl;    
    another_test_template<int,1> t1(1);
    passes += t1.pass;
    cout << "Test: " << ++tests << endl;    
    yet_another_test_template<int,1> t2(1);
    passes += t2.pass;
#if TEST__EXCEPTIONALIZE_STATIC_ASSERT == 1
    try {
        // Cannot instantiate my_template<T> with non-POD T
        using type = a_test_template<int,0>;
        cout << "Test: " << ++tests << endl;
        a_test_template<type,0> specimen;

    }
    catch(test::exceptionalized_static_assert const & esa) {
        ++passes;
        cout << esa.what() << endl;
    }
    try {
        // Cannot call my_template<T>::increase<I> with I == 0
        cout << "Test: " << ++tests << endl;
        another_test_template<int,0>(1);
    }
    catch(test::exceptionalized_static_assert const & esa) {
        ++passes;
        cout << esa.what() << endl;
    }
    try {
        // Cannot call my_template<T>::decrease<I> with I == 0
        cout << "Test: " << ++tests << endl;
        yet_another_test_template<int,0>(1);
    }
    catch(test::exceptionalized_static_assert const & esa) {
        ++passes;
        cout << esa.what() << endl;
    }
#endif // TEST__EXCEPTIONALIZE_STATIC_ASSERT == 1
    cout << "Passed " << passes << " out of " << tests << " tests" << endl;
    cout << (passes == tests ? "*** Success :)" : "*** Failure :(") << endl; 
    return 0;
}

// EOF

您可以使用至少 gcc 6.1、clang 3.8 和选项编译 test.cpp -std=c++14,或 VC++ 19.10.24631.0 和选项 /std:c++latest。先这样做,不要定义TEST__EXCEPTIONALIZE_STATIC_ASSERT (或定义它= 0)。然后运行,输出应该是:

Test: 1
Test: 2
Test: 3
Passed 3 out of 3 tests
*** Success :)

如果你然后重复,但使用-DTEST__EXCEPTIONALIZE_STATIC_ASSERT=1编译,

Test: 1
Test: 2
Test: 3
Test: 4
static_assert would fail with reason: T must be POD in my_template<T>
Test: 5
static_assert would fail with reason: I cannot be 0 in my_template<T>::increase<I>
Test: 6
static_assert would fail with reason: I cannot be 0 in my_template<T>::decrease<I>
Passed 6 out of 6 tests
*** Success :)

很明显,静态断言中try/catch块的重复编码 测试用例是乏味的,但在一个真实而可敬的环境中 单元测试框架人们会期望它打包异常测试设备以在您的视线之外生成这样的东西。例如,在 googletest 中,您可以编写以下代码:

TYPED_TEST(t_my_template,insist_non_zero_increase)
{
    ASSERT_THROW(TypeParam::template increase<0>(1),
        exceptionalized_static_assert);
}

现在我可以重新计算世界末日的日期了 :)

【讨论】:

  • // A macro redefinition of 'static_assert' ... 是的,不要那样做。创建一个新的令牌my_static_assert,在标准下映射到static_assert,在你的测试引擎下映射到my_strange_thing。当您不需要时,无需滥用语言......
  • @Yakk 宏重定义仅在测试套件中可见,因为定义它的标头仅包含在其中,并且只有在使用-DTEST__EXCEPTIONALIZE_STATIC_ASSERT=1 构建测试套件时才可见。更新了答案以明确这一点。
  • @MikeKinghan 您的宏重新定义不起作用(使用我的 STL 版本),不幸的是,由于使用模板参数时宏的限制,例如static_assert( std::is_same&lt; T1, T2 &gt;, "blabla" )。这段代码也恰好出现在标准库中。之前没有想到那个,但是对于您建议的解决方案来说,它是一个交易破坏者。正如 Yakk 建议的那样,在这种情况下,定义一个新的令牌是要走的路。顺便说一句,仍然是一个很好的解决方案。
  • 您的方法还有一个问题,static_assert 可用于确保给出编译器错误并带有有意义的错误消息,即使删除了 static_assert,某些代码也可能无法编译。
  • @MikeKinghan 也可以看看这篇文章:petriconi.net/?p=118 它没有测试很多不同的东西,但可能对扩展你的套件有用。
猜你喜欢
  • 1970-01-01
  • 2023-03-24
  • 2023-03-31
  • 1970-01-01
  • 1970-01-01
  • 2021-12-06
  • 1970-01-01
  • 1970-01-01
  • 2020-05-31
相关资源
最近更新 更多