欢迎大家关注我的微信公众号:Xndroid,探索Android技术,解锁移动世界。
GTest全称为Google Test,是由谷歌开源的测试框架,而AOSP源码中集成了这套测试框架,源码路径:external/googletest。
今天这篇文章将介绍如何在Android系统构建中使用GTest来为我们的的C/C++代码进行测试。
使用流程
开门见山,我们直接来讲GTest的具体使用流程。
1.使用TEST()或者TEST_F()宏定义测试函数,该测试函数不包含返回值,TEST()宏定义使用方法;
TEST(TestSuiteName, TestName) {
... test body ...
}
其中TestSuiteName必须是唯一的,表明特定的测试套件,TestName在一个TestSuiteName内是唯一的,而不同的TestSuiteName内允许存在同名的TestName;
2.在该测试函数内,实现测试逻辑,整个过程中使用预值和断言来检测测试中的步骤,其中预值使用类似于EXPECT_*的宏定义,而断言使用类似于 ASSERT_*的宏定义。
3.测试结果由预值和断言的执行确定,如预值不满足,则会打印相关错误信息;如断言未通过则直接中断对应测试
如针对如下接口:
// Returns the factorial of n
int Factorial(int n);
我们编写相应的测试用例如下:
// Tests factorial of 0.
TEST(FactorialTest, HandlesZeroInput) {
EXPECT_EQ(Factorial(0), 1);
}
// Tests factorial of positive numbers.
TEST(FactorialTest, HandlesPositiveInput) {
EXPECT_EQ(Factorial(1), 1);
EXPECT_EQ(Factorial(2), 2);
EXPECT_EQ(Factorial(3), 6);
EXPECT_EQ(Factorial(8), 40320);
}
4.配置编译规则,进行编译得到测试程序
5.将测试程序集成到系统镜像中,或者通过adb命令将其推入我们的系统,手动运行测试程序,观察结果
Running main() from external/googletest/googletest/src/gtest_main.cc
[==========] Running 8 tests from 5 test suites.
[----------] Global test environment set-up.
[----------] 3 tests from VendorTagDescriptorTest
[ RUN ] VendorTagDescriptorTest.ConsistentWithVendorTags
[ OK ] VendorTagDescriptorTest.ConsistentWithVendorTags (0 ms)
[ RUN ] VendorTagDescriptorTest.ConsistentAcrossParcel
[ OK ] VendorTagDescriptorTest.ConsistentAcrossParcel (0 ms)
[ RUN ] VendorTagDescriptorTest.ErrorConditions
[ OK ] VendorTagDescriptorTest.ErrorConditions (0 ms)
[----------] 3 tests from VendorTagDescriptorTest (0 ms total)
以上就是整体的一个使用流程啦。
预值断言
在GTest测试框架下,编写测试代码时我们经常使用预值或者断言来对测试的每个环节进行判定,以确定测试过程和结果是否如预期一样。
在GTest中,预值使用类似于EXPECT_*的判断(宏),适用于测试行为不满足预期但仍需继续的情况,而ASSERT_*(宏)通常适用于测试行为属于严重错误,已无任何继续下去的必要情况下。
以下是一些常用的ASSERT|EXPECT_*宏:
整数数值比较:
{ASSERT|EXPECT}_EQ(val1,val2):确保val1值与val2值相等
{ASSERT|EXPECT}_NE(val1,val2):确保val1值与val2值不相等
{ASSERT|EXPECT}_LE(val1,val2):确保val1值小于或等于val2
{ASSERT|EXPECT}_LT(val1,val2):确保val1值小于val2
{ASSERT|EXPECT}_GE(val1,val2):确保val1值大于或等于val2
{ASSERT|EXPECT}_GT(val1,val2):确保val1值大于val2
使用示例:
EXPECT_NE(Foo(), 5);
EXPECT_EQ(a_pointer, NULL);
ASSERT_LT(i, array_size);
ASSERT_GT(records.size(), 0) << "There is no record left.";
字符串比较:
{ASSERT|EXPECT}_STREQ(s1, s2):确保s1与s2相等,不忽略大小写
{ASSERT|EXPECT}_STRNE(s1, s2):确保s1与s2不相等,不忽略大小写
{ASSERT|EXPECT}_STRCASEEQ(s1, s2):确保s1与s2相等,忽略大小写
{ASSERT|EXPECT}_STRCASENE(s1, s2):确保s1与s2不相等,忽略大小写
布尔值比较:
{ASSERT|EXPECT}_TRUE(result):确保result值为true
{ASSERT|EXPECT}_FALSE(result):确保result值为false
使用示例:
EXPECT_TRUE(my_condition) << "My condition is not true";
所有宏的完整列表都可以在gtest.h中进行查看。
日志打印
GTest在针对我们测试代码中的ASSERT|EXPECT_*未符合预期时会打印相关日志,日志打印包含测试用例名称以及相应的代码行号,但如果我们需要额外的日志信息,可以在使用ASSERT|EXPECT_*时通过<<进行打印,如下所示:
ASSERT_EQ(x.size(), y.size()) << "Vectors x and y are of unequal length";
for (int i = 0; i < x.size(); ++i) {
EXPECT_EQ(x[i], y[i]) << "Vectors x and y differ at index " << i;
}
测试入口
GTest的核心其实就是基于Google Test的测试框架,用使用者编写相应的测试代码,最终编译生成用于测试的可执行文件,将可执行文件推入我们的测试环境中完成测试。
要生成可执行文件,需要有main函数作为入口;通常情况下我们不需要编写自己的main函数入口,在Google Test测试框架中,我们会链接到libgtest_main库,由其提供main函数入口。
在Android系统中, libgtest_main属于静态库,其Android.bp的定义如下:
cc_library_static {
name: "libgtest_main",
defaults: ["libgtest_defaults", "libgtest_host_defaults"],
host_supported: true,
vendor_available: true,
product_available: true,
native_bridge_supported: true,
srcs: ["src/gtest_main.cc"],
}
这里我们来看看gtest_main.cc源码,看看主函数入口是怎样的:
GTEST_API_ int main(int argc, char **argv) {
printf("Running main() from %s\n", __FILE__);
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
这就是Google Test为我们提供的默认的入口,但有些时候如果我们存在特殊的需求,我们需要通过自己的main函数来进行自定义:
int main(int ac, char* av[])
{
testing::InitGoogleTest(&ac, av);
GTEST_FLAG_SET(death_test_style, "fast");
try {
return RUN_ALL_TESTS();
}
catch(const std::exception& e) {
std::cerr << e.what() << '\n';
}
catch(...) {
std::cerr << "catch exception\n";
}
return 0;
}
集成打包
在我们编写完我们的测试代码后,我们需要编写相应的编译配置文件来指导编译系统进行编译,这里分为两种情况:使用Android.mk指导编译,或者使用Android.bp来指导编译;
以AOSP原生代码中的测试用例为例,源码路径:frameworks/av/camera/tests。
使用Android.mk配置编译规则:
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
LOCAL_SRC_FILES:= \
VendorTagDescriptorTests.cpp \
CameraBinderTests.cpp \
CameraZSLTests.cpp \
CameraCharacteristicsPermission.cpp
LOCAL_SHARED_LIBRARIES := \
liblog \
libutils \
libcutils \
libcamera_metadata \
libcamera_client \
libgui \
libsync \
libui \
libdl \
libbinder
LOCAL_C_INCLUDES += \
system/media/private/camera/include \
system/media/camera/tests \
frameworks/av/services/camera/libcameraservice \
LOCAL_CFLAGS += -Wall -Wextra -Werror
LOCAL_MODULE:= camera_client_test
LOCAL_LICENSE_KINDS:= SPDX-license-identifier-Apache-2.0
LOCAL_LICENSE_CONDITIONS:= notice
LOCAL_NOTICE_FILE:= $(LOCAL_PATH)/../NOTICE
LOCAL_MODULE_TAGS := tests
include $(BUILD_NATIVE_TEST)
以Android.bp为例:
cc_test {
name: "camera_client_test",
srcs: [
"VendorTagDescriptorTests.cpp",
"CameraBinderTests.cpp",
"CameraZSLTests.cpp",
"CameraCharacteristicsPermission.cpp",
],
shared_libs: [
"liblog",
"libutils",
"libcutils",
"libcamera_metadata",
"libcamera_client",
"libgui",
"libsync",
"libui",
"libdl",
"libbinder",
],
include_dirs: [
"system/media/private/camera/include",
"system/media/camera/tests",
"frameworks/av/services/camera/libcameraservice",
],
cflags: [
"-Wall",
"-Wextra",
"-Werror",
],
}
在编译完成后,编译产物位于out/target/product/${TARGET_DEVICE}/testcases/${TEST_MODULE_NAME},我们可以找到对应的可执行文件。