您正在寻找一种在 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 的手卷替代品。里面有很多
在谷歌土地上。 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<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 的固定大小的方形点阵。我说应该
因为现在它被一个 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));
}
...
团队负责人代码审查和点:-
您重新处理此案以解决此疏忽:-
TYPED_TEST_P(TestDotMatrix, SizeIsRight){
TypeParam matrix;
EXPECT_EQ(???,matrix.side());
}
但你不能。你不能填写???,因为TypeParam只是
dot_matrix<N> 的某个值 N 测试不知道,以及 N
是什么需要替换???。
因为没有一个测试知道N 的值,它们的TypeParam
被实例化后,他们都只能测试dot_matrix<N> 的行为
对于N 是不变的,就像这样:
TYPED_TEST_P(TestA, SomeTest){
TypeParam x(0);
EXPECT_EQ(x.var,0);
}
以上。针对 50 个不同的值测试与 N 不发生变化的行为
N 没用。当然,对那些N-invariant 行为进行测试很有用。
只是不是因为N 的 50 个不同的值,或者实际上不止一个。
随着N 的变化做 的行为也需要测试。喜欢:有吗
dot_matrix<N> 报告正确的大小和面积 N?
对于 这些 测试,您的 googletest 解决方案需要一个范围
在 TYPED_TEST_Ps 的实例化中变化 N。这就是需要的
不胜感激让那些TYPED_TEST_Ps 退出“???”疑问:-
template struct dot_matrix<std::size_t N> 正在测试这一事实是不变的。
不需要为任何测试提供此信息:测试范围不需要
传达它。唯一不同的是N。 N 的值是每个测试需要的一件事
要知道。实例化测试所需的范围是值的范围 N。
乍一看,这对 googletest 解决方案来说是个难题。
Googletest 提供了一个生成Value Parameterized Tests 的框架
可以为给定的值范围中的每一个实例化一个TEST_P
作为::testing::Values(...) 的runtime 参数。
Googletest 还提供了用于生成 Typed Tests 的框架
和Type Parameterized Tests
可以为给定的一系列 types 中的每一个实例化一个 TYPED_TEST 或 TYPED_TEST_P
作为 ::testing::Types<...> 的 compiletime 模板参数。
但是我们需要一个非类型模板参数的值范围。 C++ 需要
这样的参数是编译时整数常量。测试范围的 googletest 选项
不包括一系列编译时整数常量。
幸运的是,C++11 或更高版本提供了一种映射积分的方法
类型唯一的常量。它是template< class IntType, IntType v > 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<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
所有不变的测试都通过了。对于dot_matrix<0>,变体测试SizeIsRight 通过了,理应如此。然后它
对于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.
一切都好。