去年寫的文章,搬到cnblog
本文所述的方法只對xcode5做過測試,xcode6是否可行尚未可知。
-
配置編譯選項
首先請參考蘋果官方的文檔Configuring Xcode for Code Coverage進行相依的編譯選項配置,以生成最基所需的基礎數據。為了區別與Release&Debug的版本,建議新建一個叫做Coverage(任何名字都行)編譯配置Configuration(從Debug Copy),方法在上面的文檔里面有說明。
對這個Coverage編譯選項,做如下配置:(僅對Coverage做修改,不要影響到原來的Debug,Release以及Distribution
Generate Debug Symbols
配置成YES
Generate Test Coverage Files
配置成YES
Instrument Program Flow
配置成YES做了上面的配置以后,如果為模擬器編譯Coverage版本,那么可以看到~/Library/Developer/Xcode/DerivedData/{project_dir}/Build/Intermediates/{target_name}.build/Coverage-iphonesimulator/{target-name}.build/Objects-normal/i386下面,有一些.gcno文件。
-
收集運行時數據
程序運行的時候,會自動記錄覆蓋率相關的數據,但是這個數據只有在程序正常退出的時候才會寫到文件。對於OS X程序來說,只要通過Quit菜單退出即可;
對與ios程序來說,要想正常的退出app,只有將info.plist里面的UIApplicationExitsOnSuspend置為YES,這樣按home鍵就退出了,但這種方法需要干擾正常的應用邏輯,不利於維護;
第二種方式是通過添加代碼,只要在AppDelegate里面添加這樣的代碼即可:
- (void)applicationDidEnterBackground:(UIApplication *)application
{
#if NT_COVERAGE
extern void __gcov_flush(void);
__gcov_flush();
#endif
}
NT_COVERAGE是一個自定義的宏,建議在主taget的Build Setting/Preprocessor Macros為Coverage配置添加一個宏`NT_COVERAGE=1`,這樣通過這個宏可以用來過濾這些專門為覆蓋率測試所寫的代碼片段。
以模擬器為例,將app運行起來,進行一些操作,最后切換到后台,此時~/Library/Developer/Xcode/DerivedData/{project_dir}/Build/Intermediates/{target_name}.build/Coverage-iphonesimulator/{target-name}.build/Objects-normal/i386下應該有一些.gcda文件。
-
轉換數據
這一步是將收集的數據轉換成可讀的形式,使用一個開源庫XcodeCoverage可以完美做到這一點:得到一組html文件,可以方便地產看每個目錄下的每個類,每個函數的執行情況。
請按照這個鏈接所指向的文章的指示進行配置。
這個開源庫就是一些腳本及幾個很小的可執行文件,建議直接放在project的目錄下即可。腳本下面的getcov是個bash腳本,可以進行一些必要的修改:
1)、exclude_data函數里面,可以排除一些不想統計覆蓋率的代碼
假設一些第三方庫都位於library子目錄,可以這樣來排除
LCOV --remove ${LCOV_INFO} "library/*" -d "${OBJ_DIR}" -o ${LCOV_INFO}2)、generate_report函數里面,可以更改產生html的目錄
我是這么改的,在文件的頭部增加 REPORT_DIR=${DIR}/report,指定html的目錄為XcodeCoverage/report
generage_report被改成:
generate_report()
{
"${LCOV_PATH}/genhtml" --output-directory “${REPORT_DIR}” ${LCOV_INFO}
cd ${REPORT_DIR}
open index.html
}
當完成數據收集后,運行./getcov命令即可。
- 如何針對ios設備進行覆蓋率測試
與模擬器相比,區別的地方在於測試數據輸出的位置,因為默認的位置在設備上是不可寫的,因此需要在appDelegate里面做一些修改:
- (void)applicationDidEnterBackground:(UIApplication *)application
{
#if NT_COVERAGE
#if !TARGET_IPHONE_SIMULATOR
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
setenv("GCOV_PREFIX", [documentsDirectory cStringUsingEncoding:NSUTF8StringEncoding], 1);
setenv("GCOV_PREFIX_STRIP", "13", 1);
#endif
extern void __gcov_flush(void);
__gcov_flush();
#endif
}
第一個setenv的意思是將數據的根目錄設置為app的Documents;
第二個setenv的意思是strip掉一些目錄層次,因為覆蓋率數據默認會寫入一個很深的目錄層次。
在完成app測試之后,將這些.gcda文件拷貝到對應.gcno文件同樣的目錄,在我的測試case下是~/Library/Developer/Xcode/DerivedData/{project_dir}/Build/Intermediates/{target_name}.build/Coverage-iphoneos/{target-name}.build/Objects-normal/armv7。然后運行上面3中getcov命令即可。
-
與單元測試相結合
XcodeCoverage介紹了如何在單元測試后自動生成覆蓋率測試報告,只是一個腳本配置而已。
需要注意的是,如果使用單元測試,那么單元測試target也必須使用上文所說的Coverage編譯配置項,因為運行單元測試時,會自動編譯主target的同名編譯配置項。這個開源工具是通過run_code_coverage_post.sh來在test執行完畢之后來啟動覆蓋率數據收集的,我修改了一下這個腳本,打開一個終端窗口來打印執行過程:
#!/bin/bash
button=`/usr/bin/osascript <<EOT
tell application "Finder"
activate
set dialogText to "Generate code coverage report?"
set cancelText to "Cancel"
set okText to "OK"
set myReply to button returned of (display dialog dialogText buttons
{cancelText, okText} cancel button cancelText default button okText)
end tell
EOT`
if [[ $button = "OK" ]]; then
`/usr/bin/osascript <<EOT
tell application "Terminal"
close back window
do script "/bin/bash ${SRCROOT}/XcodeCoverage/getcov"
end tell
EOT`
fi
echo "Done.”
代碼覆蓋率的結果,可以用來指導開發者進行充分的調試,或是評估單元測試的case是否足夠;第一次完成上面的配置之后,以后的使用是很方便的。
另外需要指出的幾點:
1)__gcov_flush在一次測試中是可以被反復調用的,可以邊測試邊看覆蓋率;
2)本文對XcodeCoverage所做的幾處修改,在這里可以找到;
3)另外每次覆蓋率測試最好用一個全新的build,因此需要將上次build的中間文件全部刪除,可以打開Xcode的Product菜單,按住Option鍵,點擊Clear Build Folder…。