【问题标题】:Using macros on functions in an array to make gtest typed-parameterized tests more succinct在数组中的函数上使用宏使 gtest 类型参数化测试更简洁
【发布时间】:2015-08-18 20:45:32
【问题描述】:

目前,IMO、Google 类型参数化测试很烦人。你必须这样做:

template <typename fixtureType>
class testFixtureOld : public ::testing::Test
{

};

// Tell google test that we want to test this fixture
TYPED_TEST_CASE_P(testFixtureOld);


// Create the tests using this fixture
TYPED_TEST_P(testFixtureOld, OIS1Old)
{
  TypeParam n = 0;

  EXPECT_EQ(n, 0);
}

TYPED_TEST_P(testFixtureOld, OIS2Old)
{
  TypeParam n = 0;

  EXPECT_EQ(n, 0);
}

// Register the tests we just made
REGISTER_TYPED_TEST_CASE_P(testFixtureOld, OIS1Old, OIS2Old);


// Run the tests
typedef ::testing::Types<char, int, unsigned int> TypesTestingOld;
INSTANTIATE_TYPED_TEST_CASE_P(RunOldTests, testFixtureOld, TypesTestingOld);

这些东西中的大部分似乎都可以实现自动化。例如:

#define TYPED_TESTS_P(fixture, testName1, test1, testName2, test2) TYPED_TEST_CASE_P(fixture); TYPED_TEST_P(fixture, testName1) test1 TYPED_TEST_P(fixture, testName2) test2 REGISTER_TYPED_TEST_CASE_P(fixture, testName1, testName2);

#define RUN_TYPED_TESTS_P(testSuiteName, fixture, type1, type2, type3) typedef::testing::Types<type1, type2, type3> TypesTesting; INSTANTIATE_TYPED_TEST_CASE_P(testSuiteName, fixture, TypesTesting);


template <typename fixtureType>
class testFixtureNew : public ::testing::Test
{

};

// Make our tests. This tells google test that we want to test this fixture,
// creates the tests using this fixture, and registers them.
TYPED_TESTS_P(testFixtureNew,

OISNew,
{
  TypeParam n = 0;
  EXPECT_EQ(n, 0);
},

OIS2New,
{
  TypeParam n = 0;
  EXPECT_EQ(n, 0);
}

)

// Run the tests
RUN_TYPED_TESTS_P(RunNewTests, testFixtureNew, char, int, unsigned int);

(这些宏可以很容易地扩展到一个非常大的尺寸,然后它们就足以满足大多数用途)

这行得通,但是,这种语法相当不正常,所以我想让它看起来更正常,以便更具可读性。这需要一种方法来做这样的事情:

#include <std>
using namespace std;

#define PassIntoThenListOut(inArg, fun1, fun2) something

PassIntoThenListOut(6,

int a(int foo)
{
  cout << "5+first = " << (5+foo);
},

int b(int bar)
{
  cout << "10+second = " << (10+bar);
}
)

// Should output:
// 5+first = 11
// 10+second = 16
// ArgumentNames: foo bar

我不确定这是否可行。这可能吗?

我会简单地发布最后一段代码,但其他人似乎认为想象一个用例太晦涩难懂,所以我也想提供它。

【问题讨论】:

  • 看起来你在谈论Type-Parameterised Tests,而不是更简单的Typed Tests,后者使用起来有点乏味。
  • 我认为原始语法并没有那么糟糕,但如果我要为它制作任何语法糖,我会首先尝试深入研究 Google 提供的宏,看看它们生成了什么,什么是真的需要实现。对于宏,您最好检查一下,例如我在 SO 上的某个地方发布了关于如何以可移植的方式通过参数分发宏调用的帖子。也就是说,要有现实的期望:几乎每个人都会做这种事情一两次或三次,这个想法一开始看起来很棒,做起来很有趣/有趣,但后来……一个人不使用它。学习一些! :)
  • 哦,另外,有时 C++ 预处理器或 TMP 编程不是生成代码的正确工具。有时,只需要一点脚本和源代码生成即可。 ;-)
  • @IKavanagh,问题是如何做最后一段代码。你们可能是对的,有更好的工具可以解决这个问题,但我仍然想知道是否有可能使用 c++ 预处理器获得最后一段代码所需的行为(不管使用预处理器是否是最好的方法来实现它)。
  • @Fraser 是的,对不起,你是对的。

标签: c++ macros c-preprocessor googletest


【解决方案1】:

我在使用 gtest 和 celero 时遇到了您的问题。 我结合使用宏和 python 脚本来自动化大部分样板代码。

here are the macros i use
#define DEFINE(name,threads)  \
     void name();             \
     REGISTER(name,threads)   \
#define REGISTER(name,threads)          \
     SINGLE(name)                       \
     THREADED(name,threads)             \

#define THREADED(name, num_of_threads)            \
 void name##Threaded(){                       \
      std::vector< std::thread > threads;          \
      for(int i=0; i<num_of_threads; i++){         \
           threads.push_back( std::thread([this](){this->name##Single();}));     \
      };                                           \
      for(auto &t : threads){                      \
           t.join();                               \
      };                                           \
 };

#define SINGLE(name)               \
 void name##Single(){          \
      this->name();                 \
 };

我就是这样用的

`template<typename T>
 class LocationTest : public ::testing::Test{
      protected:
      location<T> policy;
      DEFINE(mallocfreetest,   LOCATION_THREADS)
      DEFINE(copytest,         LOCATION_THREADS)
 };'

template<typename T>
void LocationTest<T>::mallocfreetest(){
     void* p=NULL;
     p=policy.New(10);
     policy.Delete(p);
     EXPECT_TRUE(p);
};
template<typename T>
void LocationTest<T>::copytest(){
 int a=1;
 int* a_ptr=&a;
 int b=0;
 int* b_ptr=&b;

 policy.MemCopy(a_ptr,b_ptr,sizeof(int));
 EXPECT_EQ(1,b);
};

template<>
void LocationTest<device>::copytest(){
 size_t size=sizeof(int);
 int a=1;
 int* a_d=static_cast<int*>( policy.New(size) );
 ASSERT_TRUE(a_d);

 int b=0;
 int* b_d=static_cast<int*>( policy.New(size) );
 ASSERT_TRUE(b_d);

 cudaMemcpy(a_d,&a,size,cudaMemcpyHostToDevice);
 cudaMemcpy(b_d,&b,size,cudaMemcpyHostToDevice);

 policy.MemCopy(a_d,b_d,size);
 cudaMemcpy(&b,b_d,size,cudaMemcpyDeviceToHost);
 EXPECT_EQ(1,b);
};
#define HOST host
#define UNIFIED unified
#define DEVICE device
#define PINNED pinned

//python:key:policy=HOST UNIFIED DEVICE PINNED
//python:key:tests=copytestSingle mallocfreetestSingle copytestThreaded mallocfreetestThreaded
//python:template=TEST_F($LocationTest<|policy|>$,|tests|){this->|tests|();}
//python:start
//python:include=location.test
#include"location.test"
//python:end

#undef HOST
#undef UNIFIED
#undef DEVICE
#undef PINNED
#undef LOCATION_THREADS

最后,您可以看到 python 脚本的来源。它解析 cmets 中的信息并通过迭代生成所有 TEST_F(****) 代码,尽管所有可能的键组合并将其放入包含文件. python脚本还克服了c ++中宏和模板的问题,即,如果你有 TEST_F(example,test_name){test_code();}; 预处理器会认为 TEST_F 中有三个参数 所以你需要这样写 typedef 示例 class_type_1_type_2; TEST_F(class_type_1_type_2, test_name){test_code();}; (告诉 python 脚本将某些内容放入类型 def 中,只需将类型放在两个 $ 之间,就像上面的示例一样) 你把python脚本称为 coverage.py -i test_file.cpp

import os
import re
import copy
import getopt
import sys
#find functions,types,locations list;
def getoutputfile():
 global contents
 functionRegex=re.compile(r"//python:include=(.*)")
 fun=functionRegex.findall(contents)
 return fun[0]
def getkey():
 global contents
 params={}
 functionRegex=re.compile(r"//python:key:(\w*)=(.*)")
 fun=functionRegex.findall(contents)
 for i in range( len(fun) ):
      params[ fun[i][0] ]=fun[i][1].split(" ")
 return params
def get_template():
 global contents
 functionRegex=re.compile(r"//python:template=(.*)")
 fun=functionRegex.findall(contents)
 return fun

def getnumlines(array,temp):
 num=1
 for i in array:
      num*=i
 return num*len(temp)

def initializeMaxArray(array):
 global keys
 global paramaters
 for i in range(keys):
      j=paramaters.keys()[i]
      array[i]=len( paramaters[j])

def increment(a,k):
 if k<keys:
      a[k]+=1
      if a[k]>=max_array[k]:
           a[k]=0
           a=increment(a,k+1)
# *******************read in file and data
a,b=getopt.getopt(sys.argv[1:],"i:")
input_file=a[0][1]
source_file=open(input_file,"r")
contents=source_file.read()
source_file.close()

#*****************initalize varaibles
paramaters=getkey()
template=get_template()
keys=len( paramaters.keys() )
max_array=[0]*keys
initializeMaxArray(max_array)

lines=getnumlines(max_array,template)
contents_new=[]

for i in range(len(template)):
   contents_new+=[template[i]]*(lines/len(template))
for i in range(len(contents_new)):
 contents_new[i]+='\n'

temps=len(template)
array=[[0]*keys]*(lines*temps)

for i in range(lines-1):
 array[i+1]=copy.copy(array[i])
 increment(array[i+1],0)

#variable replacement
for j in range(lines):
  for i in range(keys):
      key=paramaters.keys()[i]
      x=array[j][i]
      result=contents_new[j].replace("|"+key+"|",paramaters[key][x])
      contents_new[j]=result
#typedef insertion


typedef_list=[];
typedefreg=re.compile(r".*\$(.+)\$.*")
for k in range(len( contents_new) ):
 matches=typedefreg.findall(contents_new[k] )
 for j in matches:
      match=j
      clear={"<":"_",">":"_",",":"_"," ":""}

      for i in clear.keys():
           match= match.replace(i,clear[i] )
 for j in matches:
      typedef=r"typedef "+j+" "+match+"; \n"                                                                                rep="$"+j+"$"
      contents_new[k]=contents_new[k].replace(rep,match)
      typedef_list.append(typedef)

contents_new.insert(0,"//Tests/benchmarks \n")
typedef_list.insert(0,"//typedefs \n")
output=typedef_list+contents_new

outputfile=getoutputfile()

#write out to file
destination_file=open(outputfile,'w')
destination_file.write( "".join(output) )
destination_file.close()

很抱歉,如果长篇文章很烦人,但我花了很多时间试图加快编写单元测试和基准测试,我希望这些东西也能帮助你, 警告:python 脚本可能是世界上写得最好的脚本,但可以满足我的需要。 如果您对如何使用它有任何疑问,请随时问我更多,或者如果您需要它来做一些它不做的事情,我可以将它添加到它。我正在努力把它放在 github 上。

【讨论】:

  • 谢谢。是的,python 脚本可以工作,但它需要在构建过程中添加很多内容,这就是我想避免它的原因。最简单的方法是拥有一个可以包含以这种方式改进宏的文件,因为在不更改构建过程的情况下添加它是微不足道的。
  • 你是对的,本质上你必须在编译之前运行脚本,如果你使用自动生成工具,如 gnu make,那么你可以将它合并到那里,然后没有额外的步骤,只需调用 make。
  • link 这是我的代码的 github,请随时为您想要的功能打开问题
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-07-18
  • 1970-01-01
  • 1970-01-01
  • 2017-04-06
相关资源
最近更新 更多