mac下c++单元测试覆盖率工具gcov
gcov 是 GNU 的代码覆盖率检查工具。它利用编译时的 -fprofile-arcs -ftest-coverage 和链接时的 -lgcov 选项参数生成 .gcno 文件进而通过这些文件统计覆盖率。不过高版本的 mac 使用 clang 编译器,不支持 -lgcov 选项生成 .gcno 文件。为了解决这个问题,我们可以使用-coverage参数来生成 .gcno 文件。例如:
我的g++ --version信息如下
Apple LLVM version 7.3.0 (clang-703.0.29)
Target: x86_64-apple-darwin15.4.0
Thread model: posix
使用-lgcov会报错
ld: library not found for -lgcov
可以使用-coverage选项替换-lgcov
1 2 3 |
clang -coverage test.c ./a.out gcov test.c |
在C/C++中产生代码覆盖率的步骤包括如下几步:
一、设置编译参数
如下来设置Makefile中的编译参数以使之支持覆盖率产生:
ifeq ($(coverage), yes)
CXXFLAGS += -coverage
endif
这样,可以使用 make coverage=yes 来引入这些编译选项而不会影响到正常的编译,比如:
#make coverage=yes
这时候会产生.gcno文件。
二、运行测试程序
#./exe
运行测试程序,会针对所有cpp源代码产生相应的.gcda文件。
三、获取覆盖率数据
获取覆盖率数据的方法很多种,这里介绍两种,分别产生txt和html数据:
1、使用gcov获取文本形式的覆盖率数据
使用gcc自带的覆盖率结果产生工具gcov能产生文本格式(.gcov)的覆盖率结果。
#gcov xxx.cpp
2、使用lcov获取html形式的覆盖率数据
使用IBM的lcov来产生html结果数据,具体如下:
#lcov -c -d ./ -o app.info
#genhtml app.info -o cc_result
四、展示数据
将步骤三中产生的覆盖率数据文件放到Apached的htdocs目录下,就能通过浏览器来查看覆盖率结果了。
五、基本术语
1、行覆盖率(line coverage)
即源代码有效行数与被执行的代码行的比率。
2、分支覆盖率(branch coverage)
即有判定语句的地方都会出现2个分支,整个程序经过的分支与所有分支的比率是分支覆盖率。
3、增量覆盖率(incremental coverage)
即被执行的新增和修改的代码行数与新增和修改的代码总行数的比率。
gcov实际例子,通过运行gtest中的sample1.cc单元测试。
makefile文件的写法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
CXX = g++ GTEST_DIR = .. USER_DIR = ../samples CPPFLAGS += -isystem $(GTEST_DIR)/include CXXFLAGS += -g -Wall -Wextra ifeq ($(coverage), yes) CXXFLAGS += -coverage endif TESTS = sample1_unittest GTEST_HEADERS = $(GTEST_DIR)/include/gtest/*.h \ $(GTEST_DIR)/include/gtest/internal/*.h all : $(TESTS) clean : rm -f $(TESTS) gtest.a gtest_main.a *.o *.gcno *.gcda *.gcov GTEST_SRCS_ = $(GTEST_DIR)/src/*.cc $(GTEST_DIR)/src/*.h $(GTEST_HEADERS) gtest-all.o : $(GTEST_SRCS_) $(CXX) $(CPPFLAGS) -I$(GTEST_DIR) $(CXXFLAGS) -c \ $(GTEST_DIR)/src/gtest-all.cc gtest_main.o : $(GTEST_SRCS_) $(CXX) $(CPPFLAGS) -I$(GTEST_DIR) $(CXXFLAGS) -c \ $(GTEST_DIR)/src/gtest_main.cc gtest.a : gtest-all.o $(AR) $(ARFLAGS) $@ $^ gtest_main.a : gtest-all.o gtest_main.o $(AR) $(ARFLAGS) $@ $^ sample1.o : $(USER_DIR)/sample1.cc $(USER_DIR)/sample1.h $(GTEST_HEADERS) $(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $(USER_DIR)/sample1.cc sample1_unittest.o : $(USER_DIR)/sample1_unittest.cc \ $(USER_DIR)/sample1.h $(GTEST_HEADERS) $(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $(USER_DIR)/sample1_unittest.cc sample1_unittest : sample1.o sample1_unittest.o gtest_main.a $(CXX) $(CPPFLAGS) $(CXXFLAGS) -lpthread $^ -o $@ |
编译程序
1 2 3 4 5 6 7 8 9 10 11 12 |
➜ make coverage=yes g++ -isystem ../include -g -Wall -Wextra -coverage -c ../samples/sample1.cc g++ -isystem ../include -g -Wall -Wextra -coverage -c ../samples/sample1_unittest.cc g++ -isystem ../include -I.. -g -Wall -Wextra -coverage -c \ ../src/gtest-all.cc g++ -isystem ../include -I.. -g -Wall -Wextra -coverage -c \ ../src/gtest_main.cc ar rv gtest_main.a gtest-all.o gtest_main.o ar: creating archive gtest_main.a a - gtest-all.o a - gtest_main.o g++ -isystem ../include -g -Wall -Wextra -coverage -lpthread sample1.o sample1_unittest.o gtest_main.a -o sample1_unittest |
编译完成后直接执行gcov命令,会报未执行,需要先执行可执行文件
1 2 3 4 |
➜ gcov sample1 File '../samples/sample1.cc' Lines executed:0.00% of 13 ../samples/sample1.cc:creating 'sample1.cc.gcov |
执行可执行文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
➜ ./sample1_unittest Running main() from gtest_main.cc [==========] Running 6 tests from 2 test cases. [----------] Global test environment set-up. [----------] 3 tests from FactorialTest [ RUN ] FactorialTest.Negative [ OK ] FactorialTest.Negative (0 ms) [ RUN ] FactorialTest.Zero [ OK ] FactorialTest.Zero (0 ms) [ RUN ] FactorialTest.Positive [ OK ] FactorialTest.Positive (0 ms) [----------] 3 tests from FactorialTest (0 ms total) [----------] 3 tests from IsPrimeTest [ RUN ] IsPrimeTest.Negative [ OK ] IsPrimeTest.Negative (0 ms) [ RUN ] IsPrimeTest.Trivial [ OK ] IsPrimeTest.Trivial (0 ms) [ RUN ] IsPrimeTest.Positive [ OK ] IsPrimeTest.Positive (0 ms) [----------] 3 tests from IsPrimeTest (0 ms total) [----------] Global test environment tear-down [==========] 6 tests from 2 test cases ran. (0 ms total) [ PASSED ] 6 tests. |
使用gcov查看sample1.cc文件的覆盖率
1 2 3 4 |
➜ gcov sample1 File '../samples/sample1.cc' Lines executed:100.00% of 13 ../samples/sample1.cc:creating 'sample1.cc.gcov' |
表示:sample1.cc一共有13行(可执行代码),全部被执行,测试覆盖率100%。
具体哪些代码被测还可以看sample1.cc.gcov文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
-: 37:int Factorial(int n) { 8: 38: int result = 1; 44: 39: for (int i = 1; i <= n; i++) { 14: 40: result *= i; 14: 41: } -: 42: 8: 43: return result; -: 44:} -: 45: -: 46:// Returns true iff n is a prime number. -: 47:bool IsPrime(int n) { -: 48: // Trivial case 1: small numbers 16: 49: if (n <= 1) return false; -: 50: -: 51: // Trivial case 2: even numbers 9: 52: if (n % 2 == 0) return n == 2; -: 53: -: 54: // Now, we have that n is odd and n >= 3. -: 55: -: 56: // Try to divide n by every odd number i, starting from 3 4: 57: for (int i = 3; ; i += 2) { -: 58: // We only have to try i up to the squre root of n 7: 59: if (i > n/i) break; -: 60: -: 61: // Now, we have i <= n/i < n. -: 62: // If n is divisible by i, n is not prime. 1: 63: if (n % i == 0) return false; 1: 64: } -: 65: -: 66: // n has no integer factor in the range (1, n), and thus is prime. 3: 67: return true; 11: 68:} |
-表示该行不可执行,数字1或其他表示该行被执行了多少次,#####表示代码没被执行。