XcodeCoverage
是一個基于lcov
的統(tǒng)計工具,用于計算Xcode項目的單元測試覆蓋率绞惦,且能生成html格式的統(tǒng)計報表。
現(xiàn)在需要統(tǒng)計在一個版本周期中增量代碼的覆蓋率拘哨,而XcodeCoverage只能統(tǒng)計全量的覆蓋率疤苹,因此需要借助XcodeCoverage生成的數(shù)據(jù),手動計算版本周期中修改過的文件的覆蓋率踪栋。問題可以分解為三個子問題:
- 獲取一個版本周期內(nèi)存在修改的代碼文件列表
- 獲取Xcode單元測試生成的每個代碼文件的覆蓋率數(shù)據(jù)
- 篩選并計算
獲取一個版本周期內(nèi)存在修改的代碼文件列表
在版本庫中焙格,只要確定當(dāng)某個本周期的起始和結(jié)束commit,就可以利用git diff
命令篩選出我們想要的文件列表夷都。
結(jié)束commit容易確定眷唉,如果統(tǒng)計當(dāng)前正在開發(fā)的版本,那么結(jié)束commit對應(yīng)的就是版本庫的HEAD
。
而起始commit的確定依賴于手動標(biāo)記冬阳,本項目會對每個發(fā)布版本打一個tag蛤虐,所以最新的一個tag對應(yīng)的commit即為上個版本的發(fā)布commit,亦即當(dāng)前版本的起始commit肝陪。
# get_modified_file_list.sh
#!/bin/bash
tag=`git --no-pager tag | sort -V | tail -1` #1
beginCommit=`git --no-pager show $tag --pretty=raw | head -1 | awk '{print $2}'` #2
endCommit=`git --no-pager log --max-count=1 --no-decorate | head -1 | awk '{print $2}'` #3
# echo Calculation will start from $beginCommit to $endCommit, since $tag
git --no-pager diff $beginCommit $endCommit --name-status \ #4
| awk '$2 ~ /\.m$/ {print $2}' \ #5
| awk -F '/' '{print $NF}' #6
- 獲取最新的tag驳庭,這里需注意,tag名是符合
SemVer
規(guī)則描述的版本號氯窍,因此可以使用sort
命令排序 - 根據(jù)上個版本的tag獲取起始commit
- 獲取結(jié)束commit
- 打印起始/結(jié)束commit之間存在修改的文件列表
- 提取文件路徑
- 提取文件名
獲取Xcode單元測試生成的每個代碼文件的覆蓋率數(shù)據(jù)
通過分析XcodeCoverage的腳本可以知道饲常,執(zhí)行Xcode單元測試之后生成的覆蓋率數(shù)據(jù)文件在
~/Library/Developer/Xcode/DerivedData/Demo-axxzxbxzjokinpghkkhgihkbcrgo/Build/ProfileData/F76FA0C5-258D-4233-BE5A-C672666F0D1C/Coverage.profdata
生成的二進(jìn)制包在
~/Library/Developer/Xcode/DerivedData/Demo-axxzxbxzjokinpghkkhgihkbcrgo/Build/Products/Debug-iphonesimulator/Demo.app/Demo
其中~/Library/Developer/Xcode/DerivedData/Demo-axxzxbxzjokinpghkkhgihkbcrgo/Build/
這個路徑,在腳本的執(zhí)行過程中存儲在環(huán)境變量BUILD_ROOT
中狼讨,而F76FA0C5-258D-4233-BE5A-C672666F0D1C
代表測試設(shè)備的UUID贝淤,存儲在環(huán)境變量TARGET_DEVICE_IDENTIFIER
中。因此只需要仿照XcodeCoverage導(dǎo)入環(huán)境變量的方式熊楼,自己實現(xiàn)一個exportsnv.sh
霹娄,在單元測試運行時將我們需要的路徑注入到env.sh
,待計算覆蓋率時使用source
命令導(dǎo)入即可鲫骗。
Xcode執(zhí)行單元測試時提取環(huán)境變量
將exportsnv.sh
加到Project相應(yīng)的Scheme的Run Scripts中犬耻,Xcode執(zhí)行單元測試時即可將所需的環(huán)境變量導(dǎo)入到env.sh
。
# exportsnv.sh
#!/bin/bash
scripts="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
export | egrep '(TARGET_DEVICE_IDENTIFIER)|(BUILD_ROOT)|(TARGET_NAME)' > "${scripts}/env.sh"
# env.sh
declare -x BUILD_ROOT="~/Library/Developer/Xcode/DerivedData/Demo-bxynohvelscfkufcyzjsxmqxonmn/Build/Products"
declare -x TARGET_DEVICE_IDENTIFIER="6C2F1A5C-31E0-4495-9802-B870196E0399"
declare -x TARGET_NAME="Demo"
提取覆蓋率數(shù)據(jù)
覆蓋率數(shù)據(jù)通過xcrun llvm-cov report
命令導(dǎo)出执泰。
xcrun llvm-cov report -instr-profile \
~/Library/Developer/Xcode/DerivedData/Demo-axxzxbxzjokinpghkkhgihkbcrgo/Build/ProfileData/F76FA0C5-258D-4233-BE5A-C672666F0D1C/Coverage.profdata \
~/Library/Developer/Xcode/DerivedData/Demo-axxzxbxzjokinpghkkhgihkbcrgo/Build/Products/Debug-iphonesimulator/Demo.app/Demo \
> file_level_coverage.txt
篩選并計算
# analize_coverage.sh
#!/bin/bash
ScriptsPath="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
XcodeCoveragePath="${ScriptsPath}/../Pods/XcodeCoverage"
source "${XcodeCoveragePath}/envcov.sh" #1
source "./env.sh" #2
CoverageDataName="Coverage.profdata"
CoverageDataPath="${BUILD_ROOT}/../ProfileData/${TARGET_DEVICE_IDENTIFIER}/${CoverageDataName}"
BinPackagePath="${BUILT_PRODUCTS_DIR}/${TARGET_NAME}.app/${TARGET_NAME}"
# test
xcodebuild test \
-workspace ../Demo.xcworkspace \
-scheme ${TARGET_NAME} \
-destination 'platform=iOS Simulator,name=iPad Pro (12.9-inch) (2nd generation)' \
-only-testing:DemoUnitTests \
-enableCodeCoverage YES #3
# get modified files during current app version from repo
echo Fetching modified files...
fileList="$(./get_modified_file_list.sh | tr '\n' '|')"
fileList=${fileList%?} #4
TotalLines=11
MissLines=12
# convert coverage data to humanity-readable format
echo Calculating...
CoverageDataName="file_level_coverage.txt"
xcrun llvm-cov report -instr-profile ${CoverageDataPath} ${BinPackagePath} \ #5
| awk -v total=$TotalLines -v miss=$MissLines 'NR>=3 && $1 ~ /'"$fileList"'/ {print $1,$total,$miss}' \ #6
| awk -f cal_coverage.awk #7
echo Done.
# cal_coverage.awk
#!/bin/awk -f
BEGIN {
totalsum = 0
misssum = 0
}
{
totalsum += $2
misssum += $3
}
END {
printf "Coverage rate: %.2f%%\n", (totalsum - misssum) / totalsum * 100
}
導(dǎo)入XcodeCoverage生成的環(huán)境變量
導(dǎo)入自己生成的環(huán)境變量
-enableCodeCoverage
設(shè)置為YES
枕磁,才能生成Coverage.profdata
文件導(dǎo)入文件名列表,并修改成awk命令中正則表達(dá)式的格式
導(dǎo)入所有文件的單元測試覆蓋率
篩選出
$fileList
中相應(yīng)文件的覆蓋率數(shù)據(jù)-
計算增量代碼覆蓋率
? scripts git:(develop) ? ./analize_coverage.sh Feching modified files... Calculating... Coverage rate: 10.42% Done.
Tips
-
--no-pager
: Do not pipe Git output into a pager -
$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
用于輸出當(dāng)前執(zhí)行的腳本所在目錄