Google Test - 为模板类instanciation生成值

时间:2018-02-20 16:50:53

标签: c++ c++11 templates googletest

我遇到了Google Test的问题。我有一个等同于以下内容的代码:

A.H

 foreach (string row in File.ReadAllLines(fullPath))
 {
   if (!string.IsNullOrEmpty(row))
   {
     dt.Rows.Add();
     int i = 0;
     foreach (string cell in row.Split('\t'))
     {
       dt.Rows[dt.Rows.Count - 1][i] = cell;
       i++;
     }
   }
 }

test_A.cpp

template<int N> class A {
public:
    A() {}
    A(int n) {
        var = n;
    }
    int var;
};

总而言之,我想在#include "gtest/gtest.h" #include "A.h" template<typename T> class TestA : public ::testing::Test {}; TYPED_TEST_CASE_P(TestA); TYPED_TEST_P(TestA, SomeTest){ TypeParam x(0); EXPECT_EQ(x.var,0); ... } REGISTER_TYPED_TEST_CASE_P(TestA, SomeTest); typedef ::testing::Types<A<0>, A<1>, A<2>, A<3>, A<4>, A<5> ... A<511>, A<512>> MyTypes; INSTANTIATE_TYPED_TEST_CASE_P(My, TestA, MyTypes); int main(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); } 的许多版本的SomeTest课程上运行测试A。对于上面提到的type-parameterized test,问题在于&#34;类型测试&#34;方法,我必须手动编写我要测试的类N的所有版本。

使用Google Test,您还可以编写value-parameterized tests,这非常方便,因为您可以使用A等生成器:

Range

然后,您可以使用测试中的函数INSTANTIATE_TEST_CASE_P(InstantiationName, TestA, ::testing::Range(0,512,1)); 来访问这些值。这种语法将使我想要做的事情很容易设置。但是,似乎在编译时未解析这些值,因此它们无法用于指定模板值。

我的问题是:

  • 是否有一些语法相当于Google Test中的值参数化测试,以指定我要测试的GetParam()的所有版本?
  • 如果没有,我如何生成要与类型参数化测试一起使用的A类型列表?

N.B:必须符合c ++ 11标准。

1 个答案:

答案 0 :(得分:0)

你正在寻找一种方法来节省googletest中的指法 通过避免类型列表的硬编码,您已在上面概述了解决方案:

A<0>, A<1>, A<2>, A<3>, A<4>, A<5>, ... A<511>, A<512>

MyTypes的定义中,并以某种方式在编译时生成 只给出了类型列表的长度。

令人遗憾的是,你可以通过解决来节省非常有限的指法 这个问题。在内部,googletest专门使用C ++ 03。所以没有变量 模板,与C ++ 11一起到达。因此,有一个硬编码限制 类型列表的长度:

::testing::Types<T0, T1, ... Tn>

您需要513种类型的列表:硬编码限制为50.在编译之后 失败。

尽管如此,如果您要使用此类googletest解决方案做很多事情, 如果类型列表长度在N x 10范围内,则可能值得您使用代码 有货可以按照你想要的方式生成它们。

C ++ 14只是这类工作的工具。它是template< class T, T... Ints> class std::integer_sequence。 但是你说你被限制在C++11。在那种情况下,你需要一个 手卷代替std::integer_sequence。那里有很多 在Google土地上。 Jonathan Wakeley's one 脱颖而出:std::integer_sequence是他的C ++ 14提案,以及他的实施 是C ++ 14标准的原型。

现在我要用自己的杂草来做。这是一个头文件:

<强> integer_seq.h

#ifndef INTEGER_SEQ_H
#define INTEGER_SEQ_H

#if __cplusplus >= 201402L

#include <utility>

template<typename IntType, IntType ...Is>
using integer_seq = std::integer_sequence<IntType,Is...>;

template<typename IntType, IntType N>
using make_integer_seq = std::make_integer_sequence<IntType,N>;

#elif __cplusplus == 201103L

#include <type_traits>

template<typename IntType, IntType ...Is> struct integer_seq {};

namespace detail {

template<typename IntType, IntType Count, bool Done, IntType ...Is>
struct gen_seq;

template<typename IntType, IntType Count, IntType ...Is>
struct gen_seq<IntType, Count, false, Is...>
{
    static_assert(Count > 0,"Count must be positive");    
    using type = 
        typename gen_seq<   IntType, 
                            Count - 1, Count - 1 == 0, 
                            Count - 1, Is...
                        >::type;
};

template <typename IntType, IntType Count, IntType ...Is>
struct gen_seq<IntType, Count, true, Is...>
{
    using type = integer_seq<IntType,Is...>;
};

} // namespace detail


template<typename IntType, IntType N>
using make_integer_seq = typename detail::gen_seq<IntType,N,N == 0>::type;

#else
#error At least C++11 required :(
#endif

#endif

此标题定义:

template< typename IntType, IntType ...Is> struct integer_seq;

表示某些整数类型Is...的值的序列IntType,并且:

template<typename IntType, IntType N> make_integer_seq;

是类型的别名:

integer_sequence<IntType,0,... N - 1>

如果在C ++ 14中编译,则integer_seq本身只是一个别名 std::integer_sqeuence。如果C ++ 11那么integer_seq是手动的。

一旦您获得了模板integer_seq,就可以使用它来获得一些杠杆作用 在googletest的模板::testing::Types上。这是另一个头文件:

<强> indexed_type_list

#ifndef INDEXED_TYPE_LIST_H
#define INDEXED_TYPE_LIST_H

#include <cstddef>
#include "gtest/gtest.h"
#include "integer_seq.h"

template<typename IntType, template<IntType> class T, typename Seq>
struct indexed_type_list;

template<typename IntType, template<IntType> class T, IntType ...Is>
struct indexed_type_list<IntType,T,integer_seq<IntType,Is...>>
{
    using type = ::testing::Types<T<Is>...>;
};

template<typename IntType, template<IntType> class T,  std::size_t Size>
using indexed_type_list_t =
    typename indexed_type_list<IntType,T,make_integer_seq<IntType,Size>>::type;

#endif

定义:

template<typename IntType, template<IntType> class T,  IntType Size>
indexed_type_list_t;

这样,给定一些整数类型IntType,该类型的数字Size, 和一些一元非类型模板template<IntType> class T,然后:

indexed_type_list_t<IntType,T,Size>

是:

的别名
::testing::Types<T<0>,... T<Size - 1>>;

这就是你所追求的。 indexed_type_list无疑是一个非常蹩脚的名字 对于这个概念,但我的想象力让我失望。

现在我们准备好了。以下是我们如何调整您的标本解决方案:

<强>的main.cpp

#include "indexed_type_list.h"
#include "A.h"

template<typename T> class TestA : public ::testing::Test{};

TYPED_TEST_CASE_P(TestA);

TYPED_TEST_P(TestA, SomeTest){
    TypeParam x(0);
    EXPECT_EQ(x.var,0);
}

REGISTER_TYPED_TEST_CASE_P(TestA, SomeTest);

using MyTypes = indexed_type_list_t<int,A,50>;

INSTANTIATE_TYPED_TEST_CASE_P(My, TestA, MyTypes);

int main(int argc, char **argv) {
    ::testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}

编译和链接:

$ g++ -Wall -Wextra -pedantic -std=c++11 -c main.cpp
$ g++ -o testrunner main.o -lgtest -pthread

它的运行方式如下:

$ ./testrunner
[==========] Running 50 tests from 50 test cases.
[----------] Global test environment set-up.
[----------] 1 test from My/TestA/0, where TypeParam = A<0>
[ RUN      ] My/TestA/0.SomeTest
[       OK ] My/TestA/0.SomeTest (0 ms)
[----------] 1 test from My/TestA/0 (0 ms total)

[----------] 1 test from My/TestA/1, where TypeParam = A<1>
[ RUN      ] My/TestA/1.SomeTest
[       OK ] My/TestA/1.SomeTest (0 ms)
[----------] 1 test from My/TestA/1 (0 ms total)

...
...

[----------] 1 test from My/TestA/48, where TypeParam = A<48>
[ RUN      ] My/TestA/48.SomeTest
[       OK ] My/TestA/48.SomeTest (0 ms)
[----------] 1 test from My/TestA/48 (0 ms total)

[----------] 1 test from My/TestA/49, where TypeParam = A<49>
[ RUN      ] My/TestA/49.SomeTest
[       OK ] My/TestA/49.SomeTest (0 ms)
[----------] 1 test from My/TestA/49 (0 ms total)

[----------] Global test environment tear-down
[==========] 50 tests from 50 test cases ran. (0 ms total)
[  PASSED  ] 50 tests.

这样你的问题就解决了。它在这里都是可选的。

反面

这种测试任何类模板的方法存在致命的设计缺陷 在一系列非类型模板参数上。在我们解决之前它就在那里 为::testing::Types生成类型列表的问题,它仍然存在 那里。

在实际应用中,经过精心设计,你不会遇到类似的类模板 template<int N> class A中定义的A.h,其中模板参数N 在模板定义或模板特化中根本不使用。 该模板参数没有实际意义。 A的定义可能会 以及:

class A {
public:
    A() {}
    A(int n) {
        var = n;
    }
    int var;
};

假设我们不得不处理A而不是更多 逼真的模板:

<强> dot_matrix.h

#ifndef DOT_MATRIX_H
#define DOT_MATRIX_H

#include <array>

template<std::size_t N>
struct dot_matrix
{
    bool get(std::size_t x, std::size_t y) const {
        return _square.at(x).at(y);
    }
    void set(std::size_t x, std::size_t y) {
        _square.at(x).at(y) = true;
        ++_filled;
    }
    void clear(std::size_t x, std::size_t y){
        _square.at(x).at(y) = false;
        --_filled;
    }

    unsigned filled() const {
        return _filled;
    }

    unsigned vacant() const {
        return area() - _filled;
    }

    static constexpr std::size_t side() {
        return N * 2;
    }

    static constexpr std::size_t area() {
        return side() * side();
    }

private:
    unsigned _filled;
    std::array<std::array<bool,N>,N> _square;
};

#endif

据说,template<std::size_t N> struct dot_matrix一般来说 封装大小为N x N的固定大小的方点阵。我说应该是 因为现在它被一个错误所遗忘 - 真的,一个不切实际的明显错误 - 我们希望通过googletest单元测试进行清理。

您绘制吸管以对单元测试进行编码并完成所有设置 TYPED_TEST_CASE_P样式测试,用:

using MyTypes = ::testing::Types<dot_matrix<0>,... dot_matrix<49>>;

是否编译时生成

并不重要
dot_matrix<0>,... dot_matrix<49>

或严格编码。

你写了一堆TYPED_TEST_P,例如:

TYPED_TEST_P(TestDotMatrix,IsSquare){
    TypeParam matrix;
    auto side = matrix.side();
    EXPECT_EQ(matrix.area(),side * side));
}

TYPED_TEST_P(TestDotMatrix, IsConsistent){
    TypeParam matrix;
    auto cap = matrix.filled() + matrix.vacant();
    EXPECT_EQ(matrix.area(),cap));
}
...

团队负责人代码审核和点: -

  • dot_matrix<N>中的错误。它没有封装N x N点阵。 实际上,它封装了2N x 2N点阵。

  • 您还没有通过测试来验证dot_matrix<N>是否包含 N x N矩阵。

你回过头来解决这个疏忽: -

TYPED_TEST_P(TestDotMatrix, SizeIsRight){
    TypeParam matrix;
    EXPECT_EQ(???,matrix.side());
}

但你不能。您无法填写???,因为TypeParam只是 dot_matrix<N>表示测试不知道的N 的某些值N 需要替换???

因为没有一个测试知道NTypeParam的价值 实例化后,他们都可以只测试dot_matrix<N>的行为 对于N是不变的,就像那样:

TYPED_TEST_P(TestA, SomeTest){
    TypeParam x(0);
    EXPECT_EQ(x.var,0);
}

的上方。测试50个不同值的N不变的行为 N并不有用。当然, 对于那些N - 不变行为进行测试很有用。 只是不是N的50个不同值,或者实际上不是一个。{/ p>

的行为因N而异的行为也需要进行测试。喜欢:是的 dot_matrix<N>报告 N的正确大小和区域

对于这些测试,您的googletest解决方案需要一个范围 在N的实例化中变化TYPED_TEST_P。这是需要的 我们感谢您将TYPED_TEST_P排除在&#39; ???奎德里: -

template struct dot_matrix<std::size_t N>正在接受测试的事实是不变。 这些信息都不需要提供测试:测试范围不需要 传达它。唯一不同的是NN的值是每个测试需要的一件事 知道。实例化测试所需的范围是一系列值 N

这对于googletest解决方案来说乍看起来很困难。

Googletest提供了一个生成Value Parameterized Tests的框架 可以为给定的范围中的每一个实例化TEST_P 作为::testing::Values(...)运行时参数。

Googletest还提供了生成Typed Tests的框架 和Type Parameterized Tests 可以为给定的类型范围中的每一个实例化TYPED_TESTTYPED_TEST_P as {em> compiletime 模板参数::testing::Types<...>

我们需要一系列非类型模板参数的值。 C ++需要 这样的参数是一个编译时积分常量。测试范围的googletest选项 不包括一系列编译时积分常量

令人高兴的是,C ++ 11或更高版本采用方式来映射整数 唯一一个类型。它是template< class IntType, IntType v > struct std::integral_constant。 对于任何常数N的整数类型IntType

struct std::integral_constant<IntType,N>;

是唯一映射的类型,从该类型可以检索编译时 常数N as:

std::integral_constant<IntType,N>::value;

所以这里只是googletest解决方案:

...
using MyTypes =
    ::testing::Types<std::integral_constant<0>,...std::integral_constant<49>>;

...
...

TYPED_TEST_P(TestDotMatrix, SizeIsRight){
    dot_matrix<TypeParam::value> matrix;
    EXPECT_EQ(TypeParam::value,matrix.side());
}

这是一个完整的示例,testing::Types范围 像以前一样生成compiletime。

新标题:

<强> integral_constant_typelist.h

#ifndef INTEGRAL_CONSTANT_TYPELIST_H
#define INTEGRAL_CONSTANT_TYPELIST_H

#include <cstddef>
#include <type_traits>
#include "gtest/gtest.h"
#include "integer_seq.h"

template<typename IntType, IntType Start,typename Seq>
struct integral_constant_typelist;

template<typename IntType, IntType Start,IntType ...Is>
struct integral_constant_typelist<IntType,Start,integer_seq<IntType,Is...>>
{
    using type =
        ::testing::Types<std::integral_constant<IntType,Start + Is>...>;
};

template<typename IntType, IntType Start,std::size_t Size>
using integral_constant_typelist_t =
    typename integral_constant_typelist<
        IntType,Start,make_integer_seq<IntType,Size>
    >::type;

#endif

这个标题与indexed_type_list.h不同,名称并不严重。它&#39; S googletest工具包的候选人。它定义了:

template<typename IntType, IntType Start,std::size_t Size>
integral_constant_typelist_t

是以下类型:

::testing::Types<std::integral_constant<IntType,Start>,...
                    std::integral_constant<IntType,Start + (Size - 1)>

所以,例如。

integral_constant_typelist_t<int,3,10>

的类型为:

::testing::Types<
    std::integral_constant<int,3>,...std::integral_constant<int,12>>

然后这是dot_matrix<N>的测试套件:

<强>的main.cpp

#include "integral_constant_typelist.h"
#include "dot_matrix.h"

using arbitrary = std::integral_constant<std::size_t,42>;

// A fixture template for N-variant tests
template<
    typename T // = `std::integral_constant<std::size_t, N>`, for some `N`
> struct TestDotMatrixVariant : ::testing::Test
{
    using int_type = typename T::value_type; // = std::size_t
    static constexpr int_type N_param() {
        return T::value;    // = N
    }
    using test_type = dot_matrix<N_param()>;
    dot_matrix<N_param()> const & get_specimen() const {
        return _specimen;
    }
    dot_matrix<N_param()> & get_specimen() {
        return _specimen;
    }
protected:
    test_type _specimen;
};

// A fixture for invariant tests
struct TestDotMatrixInvariant : TestDotMatrixVariant<arbitrary>{};

// Invariant test
TEST_F(TestDotMatrixInvariant,IsSquare){
    auto const & specimen = get_specimen();
    auto side = specimen.side();
    EXPECT_EQ(specimen.area(),side * side);
}

// Another invariant test
TEST_F(TestDotMatrixInvariant, IsConsistent){
    auto const & specimen = get_specimen();
    auto cap = specimen.filled() + specimen.vacant();
    EXPECT_EQ(specimen.area(),cap);
}

// Yet another invariant test
TEST_F(TestDotMatrixInvariant,OutOfRangeGetXThrows)
{
    auto const & specimen = get_specimen();
    auto x = specimen.side() + 1;
    EXPECT_THROW(specimen.get(x,0),std::out_of_range);
}

// More invariant tests...

// An N-variant test case
TYPED_TEST_CASE_P(TestDotMatrixVariant);

// An N-variant test
TYPED_TEST_P(TestDotMatrixVariant,SizeIsRight){
    EXPECT_EQ(this->N_param(),this->get_specimen().side());
}

REGISTER_TYPED_TEST_CASE_P(TestDotMatrixVariant,SizeIsRight);

using dot_matrices_0_50 = integral_constant_typelist_t<std::size_t,0,50>;

INSTANTIATE_TYPED_TEST_CASE_P(N_0_to_50,TestDotMatrixVariant,dot_matrices_0_50);

// More N-variant test cases and N-variant tests...

int main(int argc, char **argv) {
    ::testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}

编译和链接:

$ g++ -Wall -Wextra -pedantic -std=c++11 -c main.cpp
$ g++ -o testrunner main.o -lgtest -pthread

执行命令

$ ./testrunner
[==========] Running 53 tests from 51 test cases.
[----------] Global test environment set-up.
[----------] 3 tests from TestDotMatrixInvariant
[ RUN      ] TestDotMatrixInvariant.IsSquare
[       OK ] TestDotMatrixInvariant.IsSquare (0 ms)
[ RUN      ] TestDotMatrixInvariant.IsConsistent
[       OK ] TestDotMatrixInvariant.IsConsistent (0 ms)
[ RUN      ] TestDotMatrixInvariant.OutOfRangeGetXThrows
[       OK ] TestDotMatrixInvariant.OutOfRangeGetXThrows (0 ms)
[----------] 3 tests from TestDotMatrixInvariant (0 ms total)

[----------] 1 test from N_0_to_50/TestDotMatrixVariant/0, where TypeParam = std::integral_constant<unsigned long, 0ul>
[ RUN      ] N_0_to_50/TestDotMatrixVariant/0.SizeIsRight
[       OK ] N_0_to_50/TestDotMatrixVariant/0.SizeIsRight (0 ms)
[----------] 1 test from N_0_to_50/TestDotMatrixVariant/0 (0 ms total)

[----------] 1 test from N_0_to_50/TestDotMatrixVariant/1, where TypeParam = std::integral_constant<unsigned long, 1ul>
[ RUN      ] N_0_to_50/TestDotMatrixVariant/1.SizeIsRight
main.cpp:58: Failure
Expected equality of these values:
  this->N_param()
    Which is: 1
  this->get_specimen().side()
    Which is: 2
[  FAILED  ] N_0_to_50/TestDotMatrixVariant/1.SizeIsRight, where TypeParam = std::integral_constant<unsigned long, 1ul> (0 ms)
[----------] 1 test from N_0_to_50/TestDotMatrixVariant/1 (0 ms total)
...
...
...
[----------] 1 test from N_0_to_50/TestDotMatrixVariant/49, where TypeParam = std::integral_constant<unsigned long, 49ul>
[ RUN      ] N_0_to_50/TestDotMatrixVariant/49.SizeIsRight
main.cpp:58: Failure
Expected equality of these values:
  this->N_param()
    Which is: 49
  this->get_specimen().side()
    Which is: 98
[  FAILED  ] N_0_to_50/TestDotMatrixVariant/49.SizeIsRight, where TypeParam = std::integral_constant<unsigned long, 49ul> (0 ms)
[----------] 1 test from N_0_to_50/TestDotMatrixVariant/49 (0 ms total)

[----------] Global test environment tear-down
[==========] 53 tests from 51 test cases ran. (1 ms total)
[  PASSED  ] 4 tests.
[  FAILED  ] 49 tests, listed below:
[  FAILED  ] N_0_to_50/TestDotMatrixVariant/1.SizeIsRight, where TypeParam = std::integral_constant<unsigned long, 1ul>
...
...
...
[  FAILED  ] N_0_to_50/TestDotMatrixVariant/49.SizeIsRight, where TypeParam = std::integral_constant<unsigned long, 49ul>

49 FAILED TESTS

所有不变测试都通过了。对于SizeIsRight,变体测试dot_matrix<0>应该通过。然后呢 dot_matrix<1>dot_matrix<49>已失败,

更好的调试:

static constexpr std::size_t side() {
    // return N * 2; <-- Wrong
    return N; // <-- Right
}

然后:

$ ./testrunner
[==========] Running 53 tests from 51 test cases.
[----------] Global test environment set-up.
[----------] 3 tests from TestDotMatrixInvariant
[ RUN      ] TestDotMatrixInvariant.IsSquare
[       OK ] TestDotMatrixInvariant.IsSquare (0 ms)
[ RUN      ] TestDotMatrixInvariant.IsConsistent
[       OK ] TestDotMatrixInvariant.IsConsistent (0 ms)
[ RUN      ] TestDotMatrixInvariant.OutOfRangeGetXThrows
[       OK ] TestDotMatrixInvariant.OutOfRangeGetXThrows (0 ms)
[----------] 3 tests from TestDotMatrixInvariant (0 ms total)

[----------] 1 test from N_0_to_50/TestDotMatrixVariant/0, where TypeParam = std::integral_constant<unsigned long, 0ul>
[ RUN      ] N_0_to_50/TestDotMatrixVariant/0.SizeIsRight
[       OK ] N_0_to_50/TestDotMatrixVariant/0.SizeIsRight (0 ms)
[----------] 1 test from N_0_to_50/TestDotMatrixVariant/0 (0 ms total)
...
...
...
[----------] 1 test from N_0_to_50/TestDotMatrixVariant/49, where TypeParam = std::integral_constant<unsigned long, 49ul>
[ RUN      ] N_0_to_50/TestDotMatrixVariant/49.SizeIsRight
[       OK ] N_0_to_50/TestDotMatrixVariant/49.SizeIsRight (0 ms)
[----------] 1 test from N_0_to_50/TestDotMatrixVariant/49 (0 ms total)

[----------] Global test environment tear-down
[==========] 53 tests from 51 test cases ran. (1 ms total)
[  PASSED  ] 53 tests.

一切都很好。