經過這麼長的時間,我們的通用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