Google Test是Google的C++测试框架, 关于如何使用GTest的文章已经很多了,现在就来让我探索一下它的内部实现。兄弟水平有限,不对之处还望指出。
如果有对GTest如何使用不清楚的,可以去看园子里CoderZH写的“玩转Google开源C++单元测试框架Google Test系列(gtest)(总)”系列。
第一章:测试用例的管理
在代码里我们使用TEST(test_case_name, test_case)来管理测试用例,TEST宏声明在include/gtest/gtest.h,其实对应的是GTEST_TEST。
GTEST_TEST_(test_case_name, test_name, \
::testing::Test, ::testing::internal::GetTestTypeId())
再继续展开GTEST_TEST_。
2 class GTEST_TEST_CLASS_NAME_(test_case_name, test_name) : public parent_class {\
3 public:\
4 GTEST_TEST_CLASS_NAME_(test_case_name, test_name)() {}\
5 private:\
6 virtual void TestBody();\
7 static ::testing::TestInfo* const test_info_ GTEST_ATTRIBUTE_UNUSED_;\
8 GTEST_DISALLOW_COPY_AND_ASSIGN_(\
9 GTEST_TEST_CLASS_NAME_(test_case_name, test_name));\
10 };\
11 \
12 ::testing::TestInfo* const GTEST_TEST_CLASS_NAME_(test_case_name, test_name)\
13 ::test_info_ =\
14 ::testing::internal::MakeAndRegisterTestInfo(\
15 #test_case_name, #test_name, NULL, NULL, \
16 (parent_id), \
17 parent_class::SetUpTestCase, \
18 parent_class::TearDownTestCase, \
19 new ::testing::internal::TestFactoryImpl<\
20 GTEST_TEST_CLASS_NAME_(test_case_name, test_name)>);\
21 void GTEST_TEST_CLASS_NAME_(test_case_name, test_name)::TestBody()
OK,现在我们就以一段例子代码来看以上宏展开后生成了什么内容。
2 // This test is named "Negative", and belongs to the "FactorialTest"
3 // test case.
4 EXPECT_EQ(1, Factorial(-5));
5 // ...
6 }
1. 利用宏GTEST_TEST_CLASS_NAME我们生成一个类FactorialTest_Negative并继承自::testing::Test,关于Tes类里有什么以后再说。GetTestTypeId现在不关心,以后会回过头来再讨论。
2. 禁止拷贝构造函数和=操作符。
3. 从GTEST_TEST_的第12行开始,是大多数C++测试框架都要做的功课,向某个全局变量注册信息。向Ruby之类的动态语言就没这个麻烦,把当前所有的类扫一遍,只要是继承自TestCase的就去执行。但是C++没这个能力,所以只能自己堆代码了。我们先不关心MakeAndRegisterTestInfo做了什么,反正它把信息都写到这个类的静态变量test_info里去了。
4. 至于你在括号里写的"EXPECT_EQ(1, Factorial(-5));"等代码统统被塞进了成员函数TestBody。
我们在main函数里在初始化后通过调用RUN_ALL_TESTS来执行测试用例,这其实对应以下宏:
(::testing::UnitTest::GetInstance()->Run())
我们继续走,进入Run函数,前面是一堆和异常相关的东西直接跳过,直接来到重点,发现UnitTest有个成员指针impl指向UnitTestImpl,这个才是真正干活的类。它这时调用internal::HandleExceptionInMethodIfSupported(impl(), &internal::UnitTestImpl::RunAllTests, "..." ? 0 : 1),第3个参数我们不care,第一个参数是指向impl的指针,第2个参数是类成员函数指针,在HandleExceptionInMethodIfSupported里,直接看这句:(object->*method)()就知道我们应该去看internal::UnitTestImpl::RunAllTests的具体实现了。这里顺便说一句,GTest为了考虑可移植性,花了很大的精力来处理异常,我们学习的时候可以先跳过。
5. OK,现在进入函数internal::UnitTestImpl::RunAllTests。首先判断一下是否被初始化。后面的一些零零碎碎直接跳过,death test什么的日后再说。因为gtest可以指定执行几次测试,所以大循环从for (int i = 0; forever || i != repeat; i++)开始,后面无非是乱序testcase,setup,执行,teardown之后再处理结果。现在我们再跳回来看看MakeAndRegisterTestInfo做了什么:
GetUnitTestImpl()->AddTestInfo(set_up_tc, tear_down_tc, test_info);
第一步把testcase名字什么的填到TestInfo里去,注意,最后一个参数是TestFactoryBase* factory,等会我们会关心它。第二步和UnitTestImpl挂勾了,UnitTestImpl维护着一个test_info_list,现在我们的test被加入了。
6. 工厂。在GTEST_TEST_宏里调用MakeAndRegisterTestInfo中有这么一句:new ::testing::internal::TestFactoryImpl<GTEST_TEST_CLASS_NAME_(test_case_name, test_name)>,这个C++不向Ruby这种动态语言,你单告诉它类的名字就行了,还得告诉它怎么来生成这个类,典型的工厂模式。
第一章先写这么多,已经有快2年没碰C++了,进度有点慢,如有错误大家果断拍。
本文链接
问题描述:写一个程序,用字典顺序把一个集合的所有子集找出来。
此题的思路来自《C语言名题精选百则技巧篇》:字典顺序,也就是字符串比较时的顺序规则。可以采取这样的思路(以下是我根据书上的思路进行归纳再加上我自己的理解得来的步骤):
先定义n是集合的个数并且集合是已经从小到大排好顺序的{1,2,3....n}的集合。集合从{1}开始(此时下标index=0),
1.当state[i]<n时,就向右进行扩展,将state[2]=2;接着将state[3]=3;
2.当state[index]==n时,就不能向右边进行扩展了,此时就需要向左边处理了。此时的集合是{1,2,3,....,n-2,n-1,n},那么,要找比这个集合刚好大一点的,就是{1,2,3,....n-2,n},所以就可以归纳出规则为:将index减1并且将state[index]加1。
3.如果state[0]==n,那么循环就结束,反之则重复第1,2步,直到state[0]==n。
代码如下:
2 #define MAX 1000
3
4 int main()
5 {
6 int n=3;
7 int set[MAX]={1};
8 int index=0;
9 int count=2;
10 printf("1:{}\n2:{1}\n");
11 while(set[0]!=n)
12 {
13 if(set[index]<n)
14 {
15 set[index+1]=set[index]+1;
16 index++;
17 }
18 else
19 {
20 index--;
21 set[index]++;
22 }
23 int a_index;
24 count++;
25 printf("%d:{",count);
26 for(a_index=0;a_index<=index;a_index++)
27 printf("%d ",set[a_index]);
28 printf("}\n");
29 }
30 return 0;
31 }
参考资料:《C语言名题精选百则技巧篇》
如果你觉得我的文章对你有帮助,请推荐一下,非常感谢!
本文链接
摘要:
很多场合之所以使用C++,一方面是由于C++编译后的native code的高效性能,另一方面是由于C++优秀的并发能力。并行方式有多进程 和多线程之分,本章暂且只讨论多线程,多进程方面的知识会在其他章节具体讨论。多线程是开发C++服务器程序非常重要的基础,如何根据需求具体的设计、分配线程以及线程间的通信,也是服务器程序非常重要的部分,除了能够带来程序的性能提高外,若设计失误,则可能导致程序复杂而又混乱,变成bug滋生的温床。所以设计、开发优秀的线程组件以供重用,无论如何都是值得的。
线程相关的api并不复杂,然而无论是linux还是windows系统,都是c风格的接口,我们只需简单的封装成对象,方便易用即可。任务队列是设计成用来进行线程间通信,使用任务队列进行线程间通信设计到一些模式,原理并不难理解,我们需要做到是弄清楚,在什么场景下选用什么样的模式即可。
任务队列的定义:
任务队列对线程间通信进行了抽象,限定了线程间只能通过传递任务,而相关的数据及操作则被任务保存。任务队列这个名词可能在其他场景定义过其他意义,这里讨论的任务队列定义为:能够把封装了数据和操作的任务在多线程间传递的线程安全的先入先出的队列。其与线程关系示意图如下:
注:两个虚线框分别表示线程A和线程B恩能够访问的数据边界,由此可见 任务队列是线程间通信的媒介。
任务队列的实现:
任务的定义
生产者消费者模型在软件设计中是极其常见的模型,常常被用来实现对各个组件或系统解耦合。大到分布式的系统交互,小到网络层对象和应用层对象的通讯,都会应用到生产者消费者模型,在任务队列中,生产和消费的对象为“任务”。这里把任务定义为组合了数据和操作的对象,或者简单理解成包含了void (void*) 类型的函数指针和void* 数据指针的结构。我们把任务定义成类task_t,下面来分析一下task_t的实现。
插入代码:
{
public:
virtual ~task_impl_i(){}
virtual void run() = 0;
virtual task_impl_i* fork() = 0;
};
class task_impl_t: public task_impl_i
{
public:
task_impl_t(task_func_t func_, void* arg_):
m_func(func_),
m_arg(arg_)
{}
virtual void run()
{
m_func(m_arg);
}
virtual task_impl_i* fork()
{
return new task_impl_t(m_func, m_arg);
}
protected:
task_func_t m_func;
void* m_arg;
};
struct task_t
{
static void dumy(void*){}
task_t(task_func_t f_, void* d_):
task_impl(new task_impl_t(f_, d_))
{
}
task_t(task_impl_i* task_imp_):
task_impl(task_imp_)
{
}
task_t(const task_t& src_):
task_impl(src_.task_impl->fork())
{
}
task_t()
{
task_impl = new task_impl_t(&task_t::dumy, NULL);
}
~task_t()
{
delete task_impl;
}
task_t& operator=(const task_t& src_)
{
delete task_impl;
task_impl = src_.task_impl->fork();
return *this;
}
void run()
{
task_impl->run();
}
task_impl_i* task_impl;
};
Task最重要的接口是run,简单的执行保存的操作,具体的操作保存在task_impl_i的基类中,由于对象本身就是数据加操作的集合,所以构造task_impl_i的子类对象时,为其赋予不同的数据和操作即可。这里使用了组合的方式实现了接口和实现的分离。这么做的优点是应用层只需知道task的概念即可,对应task_impl_i不需要了解。由于不同的操作和数据可能需要构造不同task_impl_i子类,我们需要提供一些泛型函数,能够将用户的所有操作和数据都能轻易的转换成task对象。task_binder_t 提供一系列的gen函数,能够转换用户的普通函数和数据为task_t对象。
{
//! C function
static task_t gen(void (*func_)(void*), void* p_)
{
return task_t(func_, p_);
}
template<typename RET>
static task_t gen(RET (*func_)(void))
{
struct lambda_t
{
static void task_func(void* p_)
{
(*(RET(*)(void))p_)();
};
};
return task_t(lambda_t::task_func, (void*)func_);
}
template<typename FUNCT, typename ARG1>
static task_t gen(FUNCT func_, ARG1 arg1_)
{
struct lambda_t: public task_impl_i
{
FUNCT dest_func;
ARG1 arg1;
lambda_t(FUNCT func_, const ARG1& arg1_):
dest_func(func_),
arg1(arg1_)
{}
virtual void run()
{