经过这么长的时间,我们的通用makefile已基本构建完成,下面使用一个具体的Examle,作为如何使用的说明。
我们这个例子为之前写的hello world编写单元测试。单元测试的工具使用gtest,比较简单嘛。
1. 导入gtest文件,生成libgtest.a
从官网上下载gtest源码包,解压后,里面有个fused-src目录,在里面就是gtest文件夹,包括1个头文件gtest.h, 两个源文件gtest_all.cc, gtest_main.cc。我们直接使用这三个文件,它与主src目录基本等效的,只是将各种头文件、源文件打包到一个文件中而已,也正是因为这样,使用gtest时,也更方便一些,不用include一堆的头文件。
话不多说,下面开始工作
第一步是重命名gtest_all.cc, gtest_main.cc为gtes_all.cpp, gtest_main.cpp. 因为我们通用makefile还没有那么聪明,目前只能识别.cpp后缀。
第二步,我们来编译一下这三个头文件,看看是否可以正常编译。
一般习惯的,我喜欢单独为测试文件建立一个独立的文件夹,这里我就直接src目录同等位置下新建一个test目录,当查看gtest_all.cpp, gtest_main.cpp中发现“include "gtest/gtest.h"”这样的语句,这就暗示我们需要将gtest_main.cpp, gtest_all.cpp 放在与gtest目录同等位置下。
然后添加一个module.mk,( 在build/templates目录下有module.mk的模板,拷贝一份到这里,免得写得辛苦)。修改MODULE_NAME, SRC_FILES, 以及最后的编译目标是可执行文件gtest.exe。然后记得在main.mk中 include test/moduel.mk。
于是我们的第一个应用,编译一个可执行文件已经成功搞定了,很简单,只要加入源文件,稍微改一下module.mk就可以了。
细心的同学可能已经发现了,编译起来有点慢,主要是gtest_all.cpp实在是有点大,所以接下来我们要将它变成静态lib库,以后编译test时只要链接一下lib库就可以了,不用再重新编译。
基本不用修改,只要增加一个module-gtestlib.mk
#module-gtestlib.mk MODULE_PATH := $(call current_path) MODULE_NAME := gtest ## 定义product定制文件 PRODUCT_SPECS := #TODO ## 定义platform定制文件 PLATFORM_SPECS := #TODO ## 定义target定制文件 TARGET_SPECS := #TODO ## 生成产品、平台相关的信息,在此之前不要使用相关的变量,如CXX, CPPFLAGS,会直接覆盖。 include $(BUILD_CONFIGURE) ## 定义源文件列表 SRC_FILES:= $(MODULE_PATH)/gtest-all.cpp ## 调用目标编译规则 $(call build_target,$(BUILD_STATIC_LIBRARY),$(MODULE_PATH)/lib/libgtest.a)
注意到它与module.mk的不同,它的编译目标是生成静态库lib/libtest.a。同样将这个makefile include到main.mk中,就可以编译生成libgtest.a
为了可以测试上面编译生成的静态库是否有效,我们修改原先的module.mk, 改为直接链接libgtest.a
# module.mk ... ## 定义源文件列表 SRC_FILES:= $(MODULE_PATH)/gtest_main.cpp LDLIBS += $(MODULE_PATH)/lib/libgtest.a ## 调用目标编译规则 $(call build_target,$(BUILD_EXECUTABLE),$(MODULE_PATH)/gtest.exe)
验证结果当然是ok啦,要不然哥纯粹是找砖拍
2. 在单独的test目录下做单元测试
从现在开始,我们在单元测试中将直接使用gtest.h, 和libgtest.a。下面就为helloworld编写一个测试用例。因为想不到什么好内容,所以干脆将makefile中产品信息和版本号打印出来,
// hello.cpp std::string product_vendor() { return PRODUCT_VENDOR; } std::string product_version() { return PRODUCT_VERSION; }正确输出分别是Sample 和 1.0
在test目录下新增一个HelloTest.cpp,内容如下
#include "gtest/gtest.h" #include <string> #include "../src/hello/hello.h" TEST(hello, product_vendor) { ASSERT_TRUE(product_vendor() == std::string("Sample")); } TEST(hello, product_version) { ASSERT_TRUE(product_version() == "1.0"); }
同时修改module.mk,将HelloTest.cpp, 以及他所依赖的源文件hello.cpp包含进去 (, 这里包括上面引用hello.h,着实比较丑陋,但个人表示也很无奈,有哪位大侠知道的话,还望指点迷津)
## 定义源文件列表 SRC_FILES:= $(MODULE_PATH)/gtest_main.cpp $(MODULE_PATH)/HelloTest.cpp $(MODULE_PATH)/hello.cpp LDLIBS += $(MODULE_PATH)/lib/libgtest.a ## 调用目标编译规则 $(call build_target,$(BUILD_EXECUTABLE),$(MODULE_PATH)/gtest.exe)
编译后运行,就会提示2 test ok
3. 将测试文件与源文件放在一起
可能也有同学喜欢将测试文件与源文件放在一起,青菜萝卜各有所爱,我们的makefile虽然简单,不过这样的需求总是要想办法满足的。
因为这时,gtest.h要被各个模块的测试文件引用,为了简单起见,我们把gtest/gtest.h放到专门的头文件目录inc,在makefile中使用"-Iinc"选项,保证可以使用以include <gtest/gtest.h>的形式引用。对libgtest.a, 我们也放到专门的lib目录下。最后在hello目录下新建HelloTest.cpp, 内容同刚才一样。 添加相应的makefile,详细内容看贴图
我们的编译目标只是lib库,因为可能有很多模块,当然你也可以每个模块一个单元测试,这其实也是我所中意的形式。这里为了说一个现象,稍微复杂一点点,我们在src目录下同样新建一个module-test.mk,这个module-test.mk负责生成最终的可执行文件test.exe
# module.mk # # Created on: 2013-12-21 # Author: lenovo MODULE_PATH := $(call current_path) MODULE_NAME := test ## 定义product定制文件 PRODUCT_SPECS := #TODO ## 定义platform定制文件 PLATFORM_SPECS := #TODO ## 定义target定制文件 TARGET_SPECS := #TODO ## 生成产品、平台相关的信息,在此之前不要使用相关的变量,如CXX, CPPFLAGS,会直接覆盖。 include $(BUILD_CONFIGURE) ## 定义源文件列表 SRC_FILES:= $(MODULE_PATH)/gtest_main.cpp CXXFLAGS += -Iinc LDLIBS += lib/libgtest.a lib/hellotest.a ## 调用目标编译规则 $(call build_target,$(BUILD_EXECUTABLE))
编译运行,发现确实有test.exe生成,但是HelloTest.cpp中的两个测试并没有执行,这是为什么呢? 这是因为makefile好吃懒做,当他发现编程生成test.exe时,并不依赖于HelloTest.cpp中的任何内容时,就不会包含HelloTest.o,在这个例子中,也就是根本不会链接hellotest.a中的任何内容。知道原因之后,呵呵,那我们就只能给他下套了
/* * HelloTest.cpp * * Created on: 2014-1-12 * Author: lenovo */ #include "gtest/gtest.h" #include <string> #include "hello.h" // 增加诱导条件 void include_hellotest() {} TEST(hello, product_vendor) { ASSERT_TRUE(product_vendor() == std::string("Sample")); } 。。。
// gtest_main.cpp #include <iostream> #include "gtest/gtest.h" extern void include_hellotest(); GTEST_API_ int main(int argc, char **argv) { std::cout << "Running main() from gtest_main.cc\n"; include_hellotest(); // 通过函数调用,强制引用HelloTest中的内容 testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); }
再次编译运行,2 test ok
4. 同时编译生成test.exe 和产品
最后,可能有同学要问了,所有的单元测试文件,会构成一个个module-test.mk, 本身的源文件也有module.mk, 如果我想编译生成正常的产品以及对应的单元测试,那需要怎么做呢?
嗯,方法很多,
你可以在main.mk中按对添加 src/module.mk src/module-test.mk, src/hello/module.mk src/hello/module-test.mk, 如此
可能觉得有麻烦,那也不要紧,修改每一个模块下的module.mk, 将原来的module.mk重命名为module-product.mk, 然后module.mk的内容修改为
include module-product.mk module-test.mk当include module.mk时,相当于同时编译正常产品和单元测试。
每次都要两个module-xxx.mk, 能不能就一个module.mk, 也可以,直接把module-test.mk的内容添加到module.mk末尾就可以了。只是注意删除MODULE_PATH这一行
# module.mk # # Created on: 2013-12-21 # Author: lenovo MODULE_PATH := $(call current_path) MODULE_NAME := hello 。。。 ## 定义了如何生成静态库的通用规则 $(call build_target,$(BUILD_STATIC_LIBRARY), $(MODULE_PATH)/hello.a) ## 这里不能再使用$(call current_path),会出错。实际上您也没有必要重新定义,MODULE_PATH没有改变,还是上面的值。 #MODULE_PATH := $(call current_path) MODULE_NAME := gtest ## 定义product定制文件 PRODUCT_SPECS := #TODO ## 定义platform定制文件 PLATFORM_SPECS := #TODO ## 定义target定制文件 TARGET_SPECS := #TODO 。。。 ## 定义源文件列表 SRC_FILES:= $(MODULE_PATH)/hello.cpp $(MODULE_PATH)/HelloTest.cpp CXXFLAGS += -Iinc ## 调用目标编译规则 $(call build_target,$(BUILD_STATIC_LIBRARY),lib/hellotest.a)
一切就是这么简单,因为这是一个通用的makefile, simple but flexible
over
整个commonmakefile代码库已经完整的上传到github, 上面有完整详细的修改记录。
https://github.com/crylearner/CommonMakefile
其中gtest branch包含这个测试例子;默认分支是主干master