Google C++ 库¶
gflags¶
fgrep -l -f /var/tmp/foo johannes brahms
其中-l, -f /var/tmp/foo
是flags,而后面的johannes brahms
叫做命令行参数;gflags支持的几种类型(三个参数:flag_name, default_name, help_string):
- DEFINE_bool: boolean
- DEFINE_int32: 32-bit integer
- DEFINE_int64: 64-bit integer
- DEFINE_uint64: unsigned 64-bit integer
- DEFINE_double: double
- DEFINE_string: C++ string
- 与DEFINE_*对应的是DECLARE_*宏,相当于一处定义,多处声明;
例如在foo.cc中进行DEFINE_string, 同时在foo.h中进行DECLARE_string,就可以像一个全局变量一样到处使用。
注意DEFINE_*和DECLARE_*宏都必须在全局的命名空间,gflags中其它的符号都是在google namespace下;
RegisterFlagValidator函数可以用来注册参数的有效性检查函数,使用方案如下,一看就懂:
static bool ValidatePort(const char* flagname, int value) { if (value > 0 && value < 32768) // value is ok return true; printf("Invalid value for --%s: %d\n", flagname, (int)value); return false; } DEFINE_int32(port, 0, "What port to listen on"); static const bool port_dummy = google::RegisterFlagValidator(&FLAGS_port, &ValidatePort);
google::ParseCommandLineFlags(&argc, &argv, true); 一般在main函数的第一行用来初始化所有的参数,最后一个参数指定是否在函数返回时从argc, argv中清除已经处理过的参数;
gflags支持多种形式的参数表达方式,但是Google推荐统一为–flag=value的形式,对于bool型的参数,统一为–flag, –noflag的形式,而且不支持例如ls -la这样将2个flags组合缩写的方式;
特殊的库自带的flags:
--help shows all flags from all files
--helpfull same as -help, but unambiguously asks for all flags (in case -help changes in the future)
--helpshort shows only flags for the file with the same name as the executable (usually the one containing main())
--helpxml like –help, but output is in xml for easier parsing
--helpon=FILE shows only flags defined in FILE.*
--helpmatch=S shows only flags defined in
*S*.*
--helppackage shows flags defined in files in same directory as main()
--version prints version info for the executable
--undefok 默认情况下传入未知的命令行参数会引起程序ERROR退出,但可以指定–undefok=flag1, flag2来允许一些未知的命令行参数;
--
会结束flag prase,后面的内容会被忽略,例如:foo -f1 1 -- -f2 2
的结果是只会处理-f1从其它地方读取参数:
--fromenv 从环境变量中读取flag
--tryfromenv 和–fromenv的区别在于如果环境变量中没有定义,也不会引起fatal error
--flagfile 非常好用的从文件读取flags
可以通过google::SetUsageMessage(), google::SetVersionString()来设置Usage和Version信息。但是,切记这2个函数必须在google::ParseCommandLineFlags()函数之前运行,否则无效;
- 还可以在#include <gflags/gflags.h>之前定义宏#define STRIP_FLAG_HELP 1来删除–help的信息;
虽然gflags的官方文档这么写,但是目前的实验看来没有达到这个效果。
glog¶
首先glog是依赖于gflags的,常用的几个命令行参数必须要知道:
--logtostderr 把所有的日志信息都输出到stderr上,默认是false,在公司的ybuild里已经设置成了默认true;
--stderrthreshold 超过这个级别的日志除了输出到日志外,还输出到stderr;
--minloglevel 超过这个级别的信息才进行记录;
--log_dir 当logtostderr为false时是输出到文件,这里指定输出目录,默认是/tmp/;
--v 对于VLOG(m)型的日志,最大的输出日志级别,有可能被vmodule覆盖;
--vmodule 分模块(文件名)指定VLOG(m)的最大日志级别,以字符串
<module patter1>=<log level1>,<module patter2>=<log level2>
的形式指定,支持通配符;LOG(m)的默认4个级别:INFO、WARNING、ERROR、FATEL;
常用的条件日志记录方式:LOG_IF, LOG_EVERY_N, LOG_IF_EVERY_N, LOG_FIRST_N;
- CHECK*家族:这些CHECK*宏非常好用,在判定失败时会生成一个FATAL级别的日志,并输出调用堆栈,非常有用;
还可以用以字符串不敏感的进行字符串判等和double的判等。
在宏名前加D可以指定只在Debug模式下生效,如DLOG、DLOG_IF、DLOG_EVERY_N等等;
- 自定义级别宏VLOG(n) :自定义日志级别,在
--v=3
的参数影响下,将只输出级别<=3的日志。 同样,自定义级别的VLOG宏(Verbose Logging, 详细日志)也支持VLOG_IF、VLOG_EVERY_N、VLOG_IF_EVERY_N等等;
非常非常有用,可以分等级查看不同详细程度的日志,尤其对于程序库来说,这太重要了,库几乎只能以VLOG(n)进行日志输出!
- 自定义级别宏VLOG(n) :自定义日志级别,在
当需要线程安全时,使用RAW_LOG、RAW_CHECK等;
系统级日志记录:使用SYSLOG、SYSLOG_IF、SYSLOG_EVERY_N宏,它们将调用syslog()函数来记录系统级别的日志;
perror风格的日志,使用PLOG、PLOG_IF形式的宏能够在errno的状态及描述添加到日志中;
还可以定义GOOGLE_STRIP_LOG宏来清减glog的日志信息,剔出前面的那些日志级别、所在代码行数的信息:
#define GOOGLE_STRIP_LOG 1 // this must go before the #include! #include <glog/logging.h>
gtest¶
GTEST中的 断言宏 大概分为2种:
- ASSERT_* 系列的断言,当检查点失败时,退出当前案例的执行。
- EXPECT_* 系列的断言,当检查点失败时,继续往下执行。
- 还有一个
testing::StaticAssertTypeEq<int, T>();
可以用来检查类型T是不是int类型,否则产生一个编译时的静态断言错误。
/*! * Author : chuanqi.tan@gmail.com */ #include "gtest/gtest.h" #include <string> using namespace std; template<typename T> void ThrowIt(T const &t) { throw t; } /*! * 全部使用EXPECT_*断言,同时要知道与EXPECT_*对应ASSERT_*断言 * 其实,几乎所有的断言都是可以用EXPECT_TRUE()来模拟的。 */ TEST(test, test) { EXPECT_TRUE(true); EXPECT_FALSE(false); EXPECT_EQ(1, 1); EXPECT_NE(1, 2); EXPECT_LT(1, 2); EXPECT_LE(1, 1); EXPECT_GT(2, 1); EXPECT_GE(2, 2); EXPECT_STREQ("abc", "abc"); EXPECT_STRNE("ABC", "abc"); EXPECT_STRCASEEQ("ABC", "abc"); EXPECT_STRCASENE("bcd", "abc"); // ADD_FAILURE(); // FAIL(); // SUCCEED(); EXPECT_THROW(ThrowIt(1), int); EXPECT_THROW(ThrowIt(string("abc")), string); EXPECT_ANY_THROW(ThrowIt(1)); EXPECT_ANY_THROW(ThrowIt(string("abc"))); EXPECT_NO_THROW(int(1)); EXPECT_FLOAT_EQ(1.0000001, 1.00000001); EXPECT_DOUBLE_EQ(1.00000000000001, 1.00000000000001); EXPECT_NEAR(2, 3, 2); typedef int IntergetType; testing::StaticAssertTypeEq<int, IntergetType>(); } int main(int argc, char **argv) { testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); }
事件机制 ,参照dream/test/gtest/event.cc:
- 全局的,所有案例执行前后。
- TestSuite级别的,在 某一批案例中第一个案例前,最后一个案例执行后 , 注意是
static void SetupTestCase(){}
。 - TestCae级别的, 每个TestCase前后 , 注意是
virtual void Setup() {}
。 - 参数化机制可以极大的减少代码,使得代码更简洁:
/*! * Author : chuanqi.tan@gmail.com */ #include "gtest/gtest.h" #include <iostream> /*! * 全局事件 */ class GlobalEvent : public testing::Environment { public: virtual void SetUp() { std::cout << "FooFooEnvironmentSetUP" << std::endl; } virtual void TearDown() { std::cout << "FooFooEnvironmentTearDown" << std::endl; } }; /*! * TestSuit事件 */ class TestSuitEventTest : public testing::Test { protected: TestSuitEventTest() { value_ = 99; } static void SetUpTestCase() { std::cout << "SetUpTestCase of TestSuit" << std::endl; } static void TearDownTestCase() { std::cout << "TearDownTestCase of TestSuit" << std::endl; } int value_; }; TEST_F(TestSuitEventTest, T1) { EXPECT_EQ(1, 1); std::cout << value_ << std::endl; } TEST_F(TestSuitEventTest, T2) { EXPECT_EQ(2, 2); std::cout << value_ << std::endl; } /*! * TestCase事件 */ class TestCaseEventTest : public testing::Test { protected: virtual void SetUp() { std::cout << "SetUp of TestCase" << std::endl; } virtual void TearDown() { std::cout << "TearDown of TestCase" << std::endl; } }; TEST_F(TestCaseEventTest, T1) { EXPECT_EQ(1, 1); } TEST_F(TestCaseEventTest, T2) { EXPECT_EQ(2, 2); } TEST_F(TestCaseEventTest, T3) { EXPECT_EQ(3, 3); } int main(int argc, char **argv) { testing::AddGlobalTestEnvironment(new GlobalEvent()); testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); }
参数化机制 可以极大的减少代码,使得代码更简洁,主要就是几步:
- 确定参数的类型,通过继承TestWithParam<T>来实现;
- GetParm()得到当前传递过来的参数;
- INSTANTIATE_TEST_CASE_P 宏注册test case。
/*! * Author : chuanqi.tan@gmail.com */ #include "gtest/gtest.h" #include <iostream> using namespace std; class IsPrimeParamTest : public ::testing::TestWithParam<int> { }; TEST_P(IsPrimeParamTest, HandleTrueReturn) { int n = GetParam(); EXPECT_TRUE(n % 2 == 1); } INSTANTIATE_TEST_CASE_P(TrueReturn1, IsPrimeParamTest, testing::Values(1, 3, 5, 7, 9, 11)); vector<int> vec; INSTANTIATE_TEST_CASE_P(TrueReturn2, IsPrimeParamTest, testing::ValuesIn(vec)); int main(int argc, char **argv) { for (int i = 1; i < 6; i += 2) { vec.push_back(i); } testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); }
死亡测试 通常在测试过程中,我们需要考虑各种各样的输入,有的输入可能直接导致程序崩溃,这时我们就需要检查程序是否按照预期的方式挂掉,这也就是所谓的 死亡测试 。 gtest的死亡测试能做到在一个安全的环境下执行崩溃的测试案例,同时又对崩溃结果进行验证,使用
[ASSERT|EXPECT]_[DEATH|EXIT]
宏对崩溃的方式进行测试:/*! * Author : chuanqi.tan@gmail.com */ #include "gtest/gtest.h" void Foo() { int *pInt = 0; *pInt = 42; } /*! * EXPECT_DEATH(statement, regex)在引起了DEAD,并且输出的ERROR信息匹配regex时才通过测试。 */ TEST(FooDeathTest,Demo) { EXPECT_DEATH(Foo(), ""); } TEST(ExitDeathTest,Demo) { EXPECT_EXIT(_exit(1), testing::ExitedWithCode(1), ""); } int main(int argc, char **argv) { testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); }
通过死亡测试的条件是:程序以EXPECT的方式崩溃,并且输出的stderr内容能够匹配regex
- 注意事项
- 不要在死亡测试里释放内存。
- 在父进程里再次释放内存。
- 不要在程序中使用内存堆检查。
- gtest的参数设置 对于运行参数,gtest提供了三种设置的途径:
系统环境变量: 参数对应的大写,如gtest_output对应的环境变量为GTEST_OUTPUT 命令行参数: --output=xml
代码中指定FLAG: testing::GTEST_FLAG(output) = "xml:"
推荐将这句放置InitGoogleTest之前,这样就可以使得对于同样的参数,命令行参数优先级高于代码中指定。- 我常用的参数就是–gtest_filter, –gtest_output,完整的参数列表可以用
--help
得到,具体如下: --gtest_list_tests 使用这个参数时,将不会执行里面的测试案例,而是输出一个案例的列表。
--gtest_filter 对执行的测试案例进行过滤,支持通配符(
?*-:
)--gtest_also_run_disabled_tests 执行案例时,同时也执行被置为无效的测试案例。
--gtest_repeat 设置案例重复运行次数,非常棒的功能!
--gtest_color (yes|no|auto)输出命令行时是否使用一些五颜六色的颜色,默认是auto。
--gtest_print_time 打印每个测试案例的执行时间。
--gtest_output 将测试结果输出到一个xml中
--gtest_break_on_failure 调试模式下,当案例失败时停止,方便调试
--gtest_throw_on_failure 当案例失败时以C++异常的方式抛出
--gtest_catch_exceptions 是否捕捉异常(windows下专用)