Вот пример того, как вы могли бы реализовать несколько тестов в одной тестовой программе для данной функции, которая может вызывать библиотечную функцию.
Предположим, мы хотим протестировать следующий модуль:
#include <stdlib.h>
int my_div(int x, int y)
{
if (y==0) exit(2);
return x/y;
}
Затем мы создаем следующую тестовую программу:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <setjmp.h>
// redefine assert to set a boolean flag
#ifdef assert
#undef assert
#endif
#define assert(x) (rslt = rslt && (x))
// the function to test
int my_div(int x, int y);
// main result return code used by redefined assert
static int rslt;
// variables controling stub functions
static int expected_code;
static int should_exit;
static jmp_buf jump_env;
// test suite main variables
static int done;
static int num_tests;
static int tests_passed;
// utility function
void TestStart(char *name)
{
num_tests++;
rslt = 1;
printf("-- Testing %s ... ",name);
}
// utility function
void TestEnd()
{
if (rslt) tests_passed++;
printf("%s\n", rslt ? "success" : "fail");
}
// stub function
void exit(int code)
{
if (!done)
{
assert(should_exit==1);
assert(expected_code==code);
longjmp(jump_env, 1);
}
else
{
_exit(code);
}
}
// test case
void test_normal()
{
int jmp_rval;
int r;
TestStart("test_normal");
should_exit = 0;
if (!(jmp_rval=setjmp(jump_env)))
{
r = my_div(12,3);
}
assert(jmp_rval==0);
assert(r==4);
TestEnd();
}
// test case
void test_div0()
{
int jmp_rval;
int r;
TestStart("test_div0");
should_exit = 1;
expected_code = 2;
if (!(jmp_rval=setjmp(jump_env)))
{
r = my_div(2,0);
}
assert(jmp_rval==1);
TestEnd();
}
int main()
{
num_tests = 0;
tests_passed = 0;
done = 0;
test_normal();
test_div0();
printf("Total tests passed: %d\n", tests_passed);
done = 1;
return !(tests_passed == num_tests);
}
Переопределив assert
для обновления логической переменной, вы можете продолжить, если утверждение не выполнено, и запустить несколько тестов, отслеживая, сколько успешно и сколько не удалось.
В начале каждого теста установите rslt
(переменные, используемые макросом assert
) в 1 и установите все переменные, которые управляют вашими функциями-заглушками. Если одна из ваших заглушек вызывается более одного раза, вы можете настроить массивы управляющих переменных, чтобы заглушки могли проверять различные условия при разных вызовах.
Поскольку многие библиотечные функции являются слабыми символами, они могут быть переопределены в вашей тестовой программе, чтобы вместо них их вызывали. Перед вызовом функции для тестирования вы можете установить ряд переменных состояния, чтобы управлять поведением функции-заглушки и проверять условия параметров функции.
В тех случаях, когда вы не можете переопределить подобное, присвойте функции-заглушке другое имя и переопределите символ в коде для проверки. Например, если вы хотите заглушить fopen
, но обнаружите, что это не слабый символ, определите заглушку как my_fopen
и скомпилируйте файл для проверки с помощью -Dfopen=my_fopen
.
В этом конкретном случае проверяемая функция может вызывать exit
. Это сложно, поскольку exit
не может вернуться к тестируемой функции. Это один из редких случаев, когда имеет смысл использовать setjmp
и longjmp
. Вы используете setjmp
перед входом в функцию для тестирования, затем в заглушке exit
вы вызываете longjmp
, чтобы вернуться обратно к вашему тестовому кейсу.
Также обратите внимание, что переопределенный exit
имеет специальную переменную, которая проверяет, действительно ли вы хотите выйти из программы, и вызывает _exit
, чтобы сделать это. Если вы этого не сделаете, ваша тестовая программа может завершиться некорректно.
Этот набор тестов также подсчитывает количество попыток и неудачных тестов и возвращает 0, если все тесты пройдены, и 1 в противном случае. Таким образом, make
может проверять ошибки теста и действовать соответственно.
Приведенный выше тестовый код выведет следующее:
-- Testing test_normal ... success
-- Testing test_div0 ... success
Total tests passed: 2
И код возврата будет 0.