Модульное тестирование Cmocka с C: поддельные вызовы вложенных функций - PullRequest
3 голосов
/ 25 февраля 2020

Итак, игрушечная программа, которая дублирует возникающие у меня проблемы, использует cmocka для разработки модульных тестов для существующего кода. Проблема заключается в том, что вызов вложенной функции не является ложным, что делает модульный тест зависимым от правильного выполнения вызова вложенной функции. Обратите внимание, что определение «mockable_stati c» использовалось, так как исходный код имеет функции stati c, которые существуют как «внутренние вызовы функций», но для целей модульных тестов они открыты для внешних вызовов. ( См. Сообщение от stackoverflow, откуда появилась эта идея )

Без лишних слов, вот код:

fun c .h:

#ifndef FUNC_H_
#define FUNC_H_

#ifdef UNIT_TESTING
#define mockable_static
mockable_static char* bar();
#endif

char* foo();

#endif // FUNC_H_

fun c. c:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#ifndef UNIT_TESTING
#define mockable_static static
#else
#define mockable_static
#endif

mockable_static char* bar (){
    printf("This is bar!\n");
    char *str = "This is the result of bar!";
    return str;
}

char* foo(){
    printf("This is foo, and it should return the results of bar()\n");
    char * res;
    res = bar();
    return res;
}

test. c:

#include <setjmp.h> /* needs to be before cmocka.h */
#include <stdio.h>
#include <string.h>
#include <cmocka.h>

#include "func.h"

static void test_bar(void **state);
static void test_wrap_bar(void **state);
static void test_foo(void **state);

char* __real_bar();

char* __wrap_bar(){
    printf("This is a wrap and doesn't return bar!\n");
    return (char*)mock();
}

int main(void){
    //bar test
    const struct CMUnitTest bar_tests[] = {
        cmocka_unit_test(test_bar),
        cmocka_unit_test(test_wrap_bar)
    };
    const struct CMUnitTest foo_tests[] = {
        cmocka_unit_test(test_foo)
    };
    //foo test w/ mocking bar

    int status;
    status = cmocka_run_group_tests(bar_tests,NULL,NULL);
    status = cmocka_run_group_tests(foo_tests,NULL,NULL);

    printf("Status = %d\n",status);
    return status;
}

static void test_bar(void **state){
    char expected_res[] = "This is the result of bar!";
    char * actual_res;

    actual_res = __real_bar();
    assert_string_equal(actual_res,expected_res);
}

static void test_wrap_bar(void **state){
    char * this =  "I don't want bar!";
    will_return(__wrap_bar,this);

    char * res = bar();
    assert_string_equal(res,this);
}

static void test_foo(void **state){
    char * this =  "I don't want bar!";
    will_return(__wrap_bar,this);

    char * res = foo();
    assert_string_equal(res,this);
}

g cc строка компиляции:

gcc ./test.c ./func.c -DUNIT_TESTING -g -Wl,--wrap=bar -o test -lcmocka-static

результаты выполнения теста:

[==========] Running 2 test(s).
[ RUN      ] test_bar
This is bar!
[       OK ] test_bar
[ RUN      ] test_wrap_bar
This is a wrap and doesn't return bar!
[       OK ] test_wrap_bar
[==========] 2 test(s) run.
[  PASSED  ] 2 test(s).
[==========] Running 1 test(s).
[ RUN      ] test_foo
This is foo, and it should return the results of bar()
This is bar!
[  ERROR   ] --- "This is the result of bar!" != "I don't want bar!"
[   LINE   ] --- ./test.c:59: error: Failure!
[  FAILED  ] test_foo
[==========] 1 test(s) run.
[  PASSED  ] 0 test(s).
[  FAILED  ] 1 test(s), listed below:
[  FAILED  ] test_foo

 1 FAILED TEST(S)
Status = 1

Как видите, bar () не оборачивается в foo (), но в тесте обтекания bar переносится точно так же, как foo () вызывает bar. Bar тестируется с использованием __real_bar (), который является частью тестовой библиотеки cmocka (в то время как __real_bar () имеет прототип, функция никогда не определяется и возвращает ожидаемые результаты в соответствии с документацией cmocka. Любой имеет опыт использования модульных тестов при вызове вложенных функций «Я не нашел никаких результатов насмешливых вызовов вложенных функций с помощью cmocka, но мой google-foo может отсутствовать. Если assert удален в конце test_foo (), тест не пройден из-за неиспользуемых значений в очереди will_return.

[==========] Running 2 test(s).
[ RUN      ] test_bar
This is bar!
[       OK ] test_bar
[ RUN      ] test_wrap_bar
This is a wrap and doesn't return bar!
[       OK ] test_wrap_bar
[==========] 2 test(s) run.
[  PASSED  ] 2 test(s).
[==========] Running 1 test(s).
[ RUN      ] test_foo
This is foo, and it should return the results of bar()
This is bar!
[  ERROR   ] --- %s() has remaining non-returned values.
./test.c:56: note: remaining item was declared here

[  FAILED  ] test_foo
[==========] 1 test(s) run.
[  PASSED  ] 0 test(s).
[  FAILED  ] 1 test(s), listed below:
[  FAILED  ] test_foo

 1 FAILED TEST(S)
Status = 1

1 Ответ

0 голосов
/ 26 февраля 2020

Хорошо, так что есть несколько способов решить эту проблему. Я публикую решение, чтобы кто-то другой мог увидеть это.

Решение № 1: отдельные вызовы вложенных функций в отдельные файлы. c. IE - fun c. c содержит foo () и (newfile) bar. c содержит bar (). Это позволяет G CC --wrap = bar работать в потехе c. c, так как он должен ссылаться на другой файл.

Решение №2: создать отдельные тесты для тестирования bar и foo , Используйте следующую строчку в забаве c. c, чтобы сделать бар "слабым"

__attribute__((weak))
mockable_static char* bar ().............(code follows)

В файле, который тестирует foo, с поддельным баром мы переопределяем bar (), чтобы действовать как определена оригинальная функция char * __wrap_bar (). С __attribute __ ((слабым)) этот переопределенный бар переопределяет исходный бар, и мы можем перейти к принудительному выводу результатов в тестовом файле так, как мы хотим.

Полученный файл test_foo. c будет выглядит следующим образом:

#include <setjmp.h> /* needs to be before cmocka.h */
#include <stdio.h>
#include <string.h>
#include <cmocka.h>

#include "func.h"
#include "bar.h"

static void test_bar(void **state);
static void test_wrap_bar(void **state);
static void test_foo(void **state);


char* bar(){
    printf("This is a wrap and doesn't return bar!\n");
    return (char*)mock();
}

int main(void){
    //bar test
    const struct CMUnitTest bar_tests[] = {
        cmocka_unit_test(test_wrap_bar)
    };
    const struct CMUnitTest foo_tests[] = {
        cmocka_unit_test(test_foo)
    };
    //foo test w/ mocking bar

    int status;
    status = cmocka_run_group_tests(bar_tests,NULL,NULL);
    status += cmocka_run_group_tests(foo_tests,NULL,NULL);

    printf("Status = %d\n",status);
    return status;
}

static void test_wrap_bar(void **state){
    char * this =  "I don't want bar!";
    will_return(bar,this);

    char * res = bar();
    assert_string_equal(res,this);
}

static void test_foo(void **state){
    char * this =  "I don't want bar!";
    will_return(bar,this);

    char * res = foo();
    assert_string_equal(res,this);
}

с забавным файлом c. c:

#include <stdio.h>
#include <string.h>

#ifndef UNIT_TEST
#define mockable_static static
#else
#define mockable_static __attribute__((weak))
#endif


mockable_static char* bar (){
    printf("This is bar!\n");
    char *str = "This is the result of bar!";
    //char *str = "This is the resfjkl;dsaj of bar!";
    return str;
}

char* foo(){
    printf("This is foo, and it should return the results of bar()\n");
    char * res;
    res = bar();
    return res;
}

Был бы отдельный файл test_bar. c, который не переопределить bar, и можно было бы протестировать bar () в рамках fun c. c.

Да, первое решение - это решение моей собственной проблемы! Публикация для других, чтобы видеть / комментировать / кричать на меня:)

Сотрудники, спасибо за помощь в этом!

...