经过这么长的时间,我们的通用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