【问题标题】:Google Test - generate values for template class instanciationGoogle Test - 为模板类实例化生成值
【发布时间】:2018-02-20 16:50:52
【问题描述】:

我在使用 Google 测试时遇到问题。我有一个等效于以下内容的代码:


啊.h

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

test_A.cpp

#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();
}

总而言之,我想针对N 的多个版本在A 类上运行测试SomeTest。使用上面开发的type-parameterized test,问题是使用“类型测试”方法,我必须手动编写我想测试的所有版本的类A

使用Google Test,你也可以写value-parameterized tests,非常方便,因为你可以使用像Range这样的生成器:

INSTANTIATE_TEST_CASE_P(InstantiationName, TestA, ::testing::Range(0,512,1));

然后,您可以在测试中使用函数GetParam() 访问这些值。这种语法将使我想做的事情很容易设置。但是,这些值似乎在编译时没有解析,因此它们不能用于指定模板值。

我的问题是:

  • 是否有一些语法相当于 Google Test 中的值参数化测试来指定我要测试的 A 的所有版本?
  • 如果没有,我如何生成类型列表A&lt;0&gt;, ..., A&lt;512&gt; 以用于类型参数化测试?

注意:它必须符合 c++11。

【问题讨论】:

  • 关于第二个问题,在 c++14 中 std::make_integer_sequence 可能有用,或者类似的东西:ideone.com/hySojY
  • @Caninonos:我没有在我的帖子中告诉它,但不幸的是我仅限于 c++11。还是谢谢你!
  • @MikeKinghan:我的问题不一样。您在您指向的帖子中的答案已经过时并且没有回答我的问题,我的 test_A.cpp 的代码有效并且等同于您在另一篇帖子中给出的答案。我的意思是想知道是否有办法不一个一个地写出从 0 到 512 的所有数字。
  • “我的 test_A.cpp 的代码有效”。它没有。即使删除了...,它也不会编译。 main.cpp:14:15: error: conversion from 'int' to non-scalar type 'gtest_case_TestA_::SomeTest&lt;A&lt;512&gt; &gt;::TypeParam {aka A&lt;512&gt;}' requestedTypeParam x = 0; 请发布一些可以编译的内容以澄清您的问题。
  • 当然不是,A中没有operator =重载,但你已经知道了。现在可以了。

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


【解决方案1】:

您正在寻找一种在 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&lt; class T, T... Ints&gt; class std::integer_sequence。 但是你说你被限制在C++11。在这种情况下,您需要一个 std::integer_sequence 的手卷替代品。里面有很多 在谷歌土地上。 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;

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

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&lt;IntType&gt; class T,那么:

indexed_type_list_t<IntType,T,Size>

是以下的别名:

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

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

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

ma​​in.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&lt;int N&gt; 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&lt;std::size_t N&gt; struct dot_matrix 封装了一个大小为N x N 的固定大小的方形点阵。我说应该 因为现在它被一个 bug 搞砸了 - 真的,一个不切实际的明显 bug - 我们希望通过 googletest 单元测试来淘汰。

您抽出稻草来编写单元测试代码并准备好一切 TYPED_TEST_CASE_P 风格测试,带有:

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

不管你是否编译时生成

dot_matrix<0>,... dot_matrix<49>

或者用硬编码的方式。

你写一堆TYPED_TEST_Ps,比如:

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&lt;N&gt; 中的一个错误。它不封装 N x N 点阵。 实际上,它封装了一个2N x 2N 点阵。

  • 您没有测试来验证 dot_matrix&lt;N&gt; 是否包含 N x N 矩阵。

您重新处理此案以解决此疏忽:-

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

但你不能。你不能填写???,因为TypeParam只是 dot_matrix&lt;N&gt; 的某个值 N 测试不知道,以及 N 是什么需要替换???

因为没有一个测试知道N 的值,它们的TypeParam 被实例化后,他们都只能测试dot_matrix&lt;N&gt; 的行为 对于N 是不变的,就像这样:

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

以上。针对 50 个不同的值测试与 N 不发生变化的行为 N 没用。当然,对那些N-invariant 行为进行测试很有用。 只是不是因为N 的 50 个不同的值,或者实际上不止一个。

随着N 的变化 的行为也需要测试。喜欢:有吗 dot_matrix&lt;N&gt; 报告正确的大小和面积 N?

对于 这些 测试,您的 googletest 解决方案需要一个范围 在 TYPED_TEST_Ps 的实例化中变化 N。这就是需要的 不胜感激让那些TYPED_TEST_Ps 退出“???”疑问:-

template struct dot_matrix&lt;std::size_t N&gt; 正在测试这一事实是不变的。 不需要为任何测试提供此信息:测试范围不需要 传达它。唯一不同的是NN 的值是每个测试需要的一件事 要知道。实例化测试所需的范围是值的范围 N

乍一看,这对 googletest 解决方案来说是个难题。

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

Googletest 还提供了用于生成 Typed Tests 的框架 和Type Parameterized Tests 可以为给定的一系列 types 中的每一个实例化一个 TYPED_TESTTYPED_TEST_P 作为 ::testing::Types&lt;...&gt;compiletime 模板参数。

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

幸运的是,C++11 或更高版本提供了一种映射积分的方法 类型唯一的常量。它是template&lt; class IntType, IntType v &gt; struct std::integral_constant。 对于整数类型IntType 的任何常量N

struct std::integral_constant<IntType,N>;

是唯一映射的类型,您可以从该类型中检索编译时间 常量N 为:

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 范围 像以前一样在编译时生成。

一个新的标题:

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 不同,此标头的命名不错。它是 您的 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&lt;N&gt;的测试套件:

ma​​in.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

所有不变的测试都通过了。对于dot_matrix&lt;0&gt;,变体测试SizeIsRight 通过了,理应如此。然后它 对于dot_matrix&lt;1&gt;dot_matrix&lt;49&gt;,应该失败。

更好的调试:

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.

一切都好。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-02-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多