如何编写static_assert的runnable测试?

Mik*_*han 18 unit-testing static-assert c++11

我正在为包含static_asserts 的源代码库编写单元测试套件.static_assert在设计方面,我希望保证这些不会超出他们的期望.所以我希望能够测试它们.

我当然可以添加无法编译的接口单元测试,这些测试会导致static asserts被各种各样的手段侵犯,并评论或#if 0全部出来,我个人向用户保证,如果其中任何一个未被注释,那么他们会发现库没有编译.

但这太荒谬了.相反,我想有一些装置,在单元测试套件的上下文中,static_assert用一个等效激发的运行时异常替换,测试框架可以捕获并报告生效:这段代码将static_assert在真实中编辑建立.

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

如果没有,怎么办呢?宏观设备是一种显而易见的方法,我不排除它.但也许,最好是模板专业化或SFINAE方法?

Mik*_*han 12

由于我似乎对这个问题感兴趣,因此我为自己制定了一个答案,其头文件基本上是这样的:

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
Run Code Online (Sandbox Code Playgroud)

此标头仅包含在测试套件中,然后static_assert只有在构建测试套件时才会显示可见的宏重定义

`-DTEST__EXCEPTIONALIZE_STATIC_ASSERT=1`    
Run Code Online (Sandbox Code Playgroud)

可以使用玩具模板库勾勒出此装置的使用:

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
Run Code Online (Sandbox Code Playgroud)

试着想象一下代码是否足够庞大和复杂,以至于你无法直接调查它并挑选出来static_assert并让自己满意,你知道它们为什么存在并且它们满足了它们的设计目的.你信任回归测试.

这是一个玩具回归测试套件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
Run Code Online (Sandbox Code Playgroud)

您可以test.cpp使用至少gcc 6.1,clang 3.8和选项 -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 :)
Run Code Online (Sandbox Code Playgroud)

如果你再重复,但编译-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 :)
Run Code Online (Sandbox Code Playgroud)

显然,try/catch静态断言测试用例中的块的重复编码是繁琐的,但是在真实且可敬的单元测试框架的设置中,人们会期望它将异常测试设备打包以产生这样的东西.例如,在googletest中,您可以编写如下内容:

TYPED_TEST(t_my_template,insist_non_zero_increase)
{
    ASSERT_THROW(TypeParam::template increase<0>(1),
        exceptionalized_static_assert);
}
Run Code Online (Sandbox Code Playgroud)

现在我可以回到我对世界末日日期的计算:)

  • @Yakk宏重定义只能在测试套件中看到,因为定义它的头只包含在其中,并且只有在测试套件使用`-DTEST__EXCEPTIONALIZE_STATIC_ASSERT = 1`构建时才可见.更新了答案以明确说明. (7认同)