如何使用gcov对动态库进行单元测试?

如何使用gcov对动态库进行单元测试?,c,unit-testing,gcc,gcov,C,Unit Testing,Gcc,Gcov,我正在构建一个动态链接库,并正在设置一个简单的测试套件。我想使用gcov生成静态代码分析覆盖率报告 我的库是一个包含函数实现的C文件和一个包含函数原型的头文件。我的测试套件只是一个以各种方式调用函数并确认输出有效性的应用程序 我正在使用-fprofile arcs和-ftest coverage标志编译库和测试套件,如前所述。我还包括禁用编译器优化的-O0标志和启用调试符号的-g标志。从测试套件生成的可执行文件动态链接到库 所有文件都编译干净,没有任何警告,但它无法将测试套件链接到库——引用了一

我正在构建一个动态链接库,并正在设置一个简单的测试套件。我想使用gcov生成静态代码分析覆盖率报告

我的库是一个包含函数实现的C文件和一个包含函数原型的头文件。我的测试套件只是一个以各种方式调用函数并确认输出有效性的应用程序

我正在使用
-fprofile arcs
-ftest coverage
标志编译库和测试套件,如前所述。我还包括禁用编译器优化的
-O0
标志和启用调试符号的
-g
标志。从测试套件生成的可执行文件动态链接到库

所有文件都编译干净,没有任何警告,但它无法将测试套件链接到库——引用了一个“隐藏符号
\uuugcov\umerge\uadd
”。如果我编译时没有
-fprofile弧
-ftest coverage
标志,则链接成功,并且我能够运行测试套件可执行文件

因此,在阅读了GCOV的GNU指南之后,我有几个问题仍然没有解决

  • 为什么链接失败了?我如何解决这个问题
  • 在编译库和测试套件时,我是否需要包括概要文件和覆盖率标志

  • 这是我的
    inc/mylib.h
    文件:

    #ifndef __MYLIB_H__
    #define __MYLIB_H__
    
    #ifdef __cplusplus
    extern "C" {
    #endif /* __cplusplus */
    
    int
    foo (int a);
    
    int
    bar (int a);
    
    #ifdef __cplusplus
    }
    #endif /* __cplusplus */
    
    #endif /* __MYLIB_H__ */
    
    #include <stdio.h>
    #include "mylib.h"
    
    int foo(int a) {
        if (a > 5) {
            return 5;
        }
        return a;
    }
    
    int bar(int a) {
        if (a < 0) {
            return 0;
        }
        return a;
    }
    
    #include <stdio.h>
    #include "mylib.h"
    
    void run_foo_tests() {
        int inputs[] = {3, 6};
        int expected_results[] = {3, 5};
        int i, actual_result;
    
        for ( i = 0; i < sizeof(inputs) / sizeof(int); i++ ) {
            actual_result = foo(inputs[i]);
            if (actual_result == expected_results[i]) {
                printf("Test %d passed!\n", i + 1);
            } else {
                printf("Test %d failed!\n", i + 1);
                printf("  Expected result: %d\n", expected_results[i]);
                printf("  Actual result: %d\n", actual_result);
            }
        }
    }
    
    void run_bar_tests() {
        int inputs[] = {3, -1};
        int expected_results[] = {3, 0};
        int i, actual_result;
    
        for ( i = 0; i < sizeof(inputs) / sizeof(int); i++ ) {
            actual_result = bar(inputs[i]);
            if (actual_result == expected_results[i]) {
                printf("Test %d passed!\n", i + 1);
            } else {
                printf("Test %d failed!\n", i + 1);
                printf("  Expected result: %d\n", expected_results[i]);
                printf("  Actual result: %d\n", actual_result);
            }
        }
    }
    
    int main(int argc, char *argv[]) {
        run_foo_tests();
        run_bar_tests();
        return 0;
    }
    

    这是我的
    src/mylib.c
    文件:

    #ifndef __MYLIB_H__
    #define __MYLIB_H__
    
    #ifdef __cplusplus
    extern "C" {
    #endif /* __cplusplus */
    
    int
    foo (int a);
    
    int
    bar (int a);
    
    #ifdef __cplusplus
    }
    #endif /* __cplusplus */
    
    #endif /* __MYLIB_H__ */
    
    #include <stdio.h>
    #include "mylib.h"
    
    int foo(int a) {
        if (a > 5) {
            return 5;
        }
        return a;
    }
    
    int bar(int a) {
        if (a < 0) {
            return 0;
        }
        return a;
    }
    
    #include <stdio.h>
    #include "mylib.h"
    
    void run_foo_tests() {
        int inputs[] = {3, 6};
        int expected_results[] = {3, 5};
        int i, actual_result;
    
        for ( i = 0; i < sizeof(inputs) / sizeof(int); i++ ) {
            actual_result = foo(inputs[i]);
            if (actual_result == expected_results[i]) {
                printf("Test %d passed!\n", i + 1);
            } else {
                printf("Test %d failed!\n", i + 1);
                printf("  Expected result: %d\n", expected_results[i]);
                printf("  Actual result: %d\n", actual_result);
            }
        }
    }
    
    void run_bar_tests() {
        int inputs[] = {3, -1};
        int expected_results[] = {3, 0};
        int i, actual_result;
    
        for ( i = 0; i < sizeof(inputs) / sizeof(int); i++ ) {
            actual_result = bar(inputs[i]);
            if (actual_result == expected_results[i]) {
                printf("Test %d passed!\n", i + 1);
            } else {
                printf("Test %d failed!\n", i + 1);
                printf("  Expected result: %d\n", expected_results[i]);
                printf("  Actual result: %d\n", actual_result);
            }
        }
    }
    
    int main(int argc, char *argv[]) {
        run_foo_tests();
        run_bar_tests();
        return 0;
    }
    

    这是我的
    Makefile

    CC=gcc
    CFLAGS=-Wall -std=c89 -g -O0 -Iinc -fprofile-arcs -ftest-coverage
    
    all: clean build run_tests
    
    build:
        $(CC) $(CFLAGS) -fPIC -c src/*.c -o lib/mylib.o
        $(CC) -shared lib/mylib.o -o lib/libmylib.so
        $(CC) $(CFLAGS) test/*.c -o bin/unittests -Llib -lmylib
    
    run_tests:
        LD_LIBRARY_PATH=lib bin/unittests
        gcov src/*.c
    
    clean:
        rm -f *.gcda *.gcno *.gcov
        rm -rf bin lib ; mkdir bin lib
    

    当我运行
    make
    时,会显示以下输出:

    rm -f *.gcda *.gcno *.gcov
    rm -rf bin lib ; mkdir bin lib
    gcc -Wall -std=c89 -g -O0 -Iinc -fprofile-arcs -ftest-coverage -fPIC -c src/*.c -o lib/mylib.o
    gcc -shared lib/mylib.o -o lib/libmylib.so
    gcc -Wall -std=c89 -g -O0 -Iinc -fprofile-arcs -ftest-coverage test/*.c -o bin/unittests -Llib -lmylib
    /usr/bin/ld: bin/unittests: hidden symbol `__gcov_merge_add' in /usr/lib/gcc/x86_64-redhat-linux/4.8.5/libgcov.a(_gcov_merge_add.o) is referenced by DSO
    /usr/bin/ld: final link failed: Bad value
    collect2: error: ld returned 1 exit status
    make: *** [build] Error 1
    

    您需要为链接器提供命令行参数
    -lgcov
    (通常
    -ftest coverage
    意味着
    -lgcov
    ,但您有一个单独的链接步骤,
    -ftest coverage
    不作为命令行参数提供)。或者,您可以只使用
    --coverage
    命令行参数,该参数也是
    -fprofile弧
    -ftest coverage
    的快捷方式,如下所述:

    --覆盖范围

    此选项用于编译和链接插入指令的代码 覆盖率分析。该选项是“-fprofile弧”的同义词 `-ftest覆盖率“(编译时)和“--lgcov”(链接时)。看见 有关更多详细信息,请参阅这些选项的文档


    顺便说一句,在同一个地方,还解释了您不必使用这些选项编译所有文件,这很有希望回答您的问题2。

    您真的不应该使用古老的ANSI-C。至少使用C99,更好的标准C。我需要库符合c89,因为我将要提供的平台不太明确。这与gcov的操作有关吗?(显然,真正的库比我在这里发布的更复杂。这只是MCVE版本。)我尝试用
    --coverage
    替换两个编译标志,错误仍然存在。根据您链接的页面,
    -ftest coverage
    已经暗示了
    -lgcov
    。如果我将makefile中的CFLAGS变量更改为使用
    -coverage
    而不是我最初使用的两个标志,并将
    -lgcov
    选项添加到链接步骤,它成功地构建并链接了程序,但在尝试运行gcov时,它显示“mylib.gcno:无法打开图形文件”。它似乎将libmylib.gcno和libmylib.gcda文件存储在lib文件夹中,尽管unittest.gc**文件位于根目录中。aha!最后,这是我的工作makefile:。如果您用更改的选项更新您的答案,我将很高兴将其标记为已接受。也就是说,我在编译测试套件时不需要添加任何选项,在运行gcov时需要-o。