在数组中的函数上使用宏,使得gtest类型参数化测试更简洁 [英] Using macros on functions in an array to make gtest typed-parameterized tests more succinct

查看:918
本文介绍了在数组中的函数上使用宏,使得gtest类型参数化测试更简洁的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

目前,IMO,Google类型参数化测试令人讨厌。您必须:

 模板< typename fixtureType> 
class testFixtureOld:public :: testing :: Test
{

};

//告诉google测试我们要测试这个夹具
TYPED_TEST_CASE_P(testFixtureOld);


//使用此夹具创建测试
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_TYPED_TEST_CASE_P(testFixtureOld,OIS1Old,OIS2Old);


//运行测试
typedef :: testing :: Types< char,int,unsigned int>类型测试
INSTANTIATE_TYPED_TEST_CASE_P(RunOldTests,testFixtureOld,TypesTestingOld);

这些东西大部分似乎可以自动化。例如:

  #define TYPED_TESTS_P(fixture,testName1,test1,testName2,test2)TYPED_TEST_CASE_P 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&类型测试; INSTANTIATE_TYPED_TEST_CASE_P(testSuiteName,fixture,TypesTesting);


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

};

//进行我们的测试。这告诉谷歌测试,我们想测试这个夹具,
//创建使用这个夹具的测试,并注册它们。
TYPED_TESTS_P(testFixtureNew,

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



//运行测试
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);
}


//应输出:
// 5 + first = 11
// 10 + second = 16
// ArgumentNames:foo bar

我不知道是否可以做到。这是可能的吗?



我只是发布最后一位代码,但其他人似乎认为它是太模糊的想象一个用例,所以我想提供


解决方案

我遇到了使用gtest和celero的问题。
我使用宏和python脚本的组合自动化大多数锅炉板代码。

 这里是我使用的宏
#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>线程; \
for(int i = 0; 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>政策;
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);
};

模板<>
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-> test |();}
// python:start
//python:include=location.test
#includelocation.test
// python:end

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

你可以看到python脚本在哪里。它解析注释中的信息,并通过迭代生成所有的TEST_F(****)代码尽管所有可能的键的组合,并把它放入包含文件。 python脚本也克服了c ++中宏和模板的问题,即如果你有
TEST_F(example,test_name){test_code();};
预处理器会认为在TEST_F
中有三个参数,所以你需要这样写:
typedef example class_type_1_type_2;
TEST_F(class_type_1_type_2,test_name){test_code();};
(在告诉python脚本拉一个类型def只是把类型之间的两个$ like在上面的例子)
你调用python脚本如
coverage.py -i test_file.cpp

  import os 
import re
import copy
import getopt
import sys
#find函数,类型,位置列表;
def getoutputfile():
全局内容
functionRegex = re.compile(r// python:include =(。*))
fun = functionRegex.findall )
return fun [0]
def getkey():
全局内容
params = {}
functionRegex = re.compile(r// python:key :(b)
(b)
func(func) ] [0]] = fun [i] [1] .split()
return params
def get_template():
全局内容
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):
全局键
全局参数
for i in range(keys):
j = paramaters.keys()[i]
array [i] = len(paramaters [j]

def increment(a,k):
如果k a [k] + = 1
如果a [k]> = max_array [k ]:
a [k] = 0
a = increment(a,k + 1)
#******************* read in文件和数据
a,b = getopt.getopt(sys.argv [1:],i:)
input_file = a [0] [1]
source_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 = []

对于i范围(len(template)):
范围(len(contents_new))中的i的内容新值+ = [template [i]] *(lines / len(template))

contents_new [i] + ='\\\
'

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

在范围$ b array [i + 1] = copy.copy(array [i])
increment(array [i + 1],0)

#variable replace
for j在范围(行):
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插入

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

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

contents_new.insert(0,// Tests / benchmarks \\\

typedef_list.insert (0,// typedefs \\\

output = typedef_list + content_new

outputfile = getoutputfile()

#写出到文件
destination_file = open(outputfile,'w')
destination_file.write(.join(output))
destination_file.close()



对不起,如果长的帖子烦人,但我花了时间分配试图加快写单元测试和基准,我希望这些事情可以帮助你,以及
警告:python脚本可能是最好的写在世界上,但工作,我需要它。
如果你有任何关于如何使用这个来感觉自由地问我更多,或者如果你需要它做某事,它不会,我可以添加它。我努力穿上github。


Right now, IMO, Google typed-parameterized tests are annoying. You have to do:

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);

Much of this stuff seems it could be automated. For example:

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

(where those macros could be easily expanded to a very large size and then they will be sufficient for most use)

This works, however, this syntax is rather abnormal, so I'd like to make it more normal looking so it is more readable. This requires a way of doing something like this:

#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

which I'm not sure is possible to do. Is this possible?

I would simply post the last bit of code but others seem to think that it is too obscure to imagine a use case so I wanted to provide that as well.

解决方案

I have run into your problem using gtest and celero. I use a combination of macros and a python script to automate much of the boiler plate code.

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

and i use it like this

`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

at the end you can see where the python script comes in. it parses the information in the comments and generates all the TEST_F(****) code by iteration though all possible cominations of keys and putting it into the include file. the python script also overcomes the issues with macros and templates in c++, namely, if you have TEST_F(example,test_name){test_code();}; the preprocessor will think there are three paramaters in TEST_F so you need to write it like this typedef example class_type_1_type_2; TEST_F(class_type_1_type_2, test_name){test_code();}; (in to tell the python script to pull something into a type def just put the type between two $ like in the example above) you call the python script like 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()

im sorry if the long post is annoying but i spent allot of time trying to speed up writing unit test and benchmarks and i hope these things can help you as well, WARNING: the python script is probably note the best written in the world but works for what i need it for. if you have anyquest about how to use this come feel free to ask me more or if you need it to do something it doesnt do i could add it to it. im working on putting up on github.

这篇关于在数组中的函数上使用宏,使得gtest类型参数化测试更简洁的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆