title: Система тестирования testc
author: Сергей Каличев <serj.kalichev(at)gmail.com>
date: 2020
...
Система тестирования testc
является частью проекта faux
(библиотека вспомогательных функций) и предназначена для модульного тестирования (unit-test) программного обеспечения, написанного на языке C (Си). Утилита testc
последовательно запускает набор тестов, получает результат (успех/неуспех) и генерирует отчет. Каждый тест представляет собой функцию. Источником тестов может являться любой двоичный исполняемый файл - программа или разделяемая библиотека. Для этого внутри исполняемого файла должен быть определен символ с фиксированным именем. Символ указывает на массив, в котором хранится список тестовых функций. Таким образом исполняемый файл может содержать и рабочий код и тестовый код одновременно. Также тестовый код может содержаться и в отдельном модуле. В этом случае модуль должен быть слинкован с необходимыми ему библиотеками.
Система расчитана на максимальную простоту создания тестов, а также на максимальную интеграцию тестирования в процесс разработки кода.
testc
Утилита testc
принимает на вход список исполняемых двоичных файлов и запускает все функции тестирования, содержащиеся в них. В процессе исполнения тестов генерируется отчет и выводится на экран.
Каждая функция тестирования исполняется в отдельном процессе. Благодаря этому неудачные тесты, которые могут быть прерваны сигналом, не влияют на работу самой утилиты testc
, которая продолжит запускать тесты и собирать статистику. Для каждого теста на экран выводится результат его выполнения. Это может быть успех
, неуспех
или сообщение, что тест прерван сигналом
. Если тест завершился с ошибкой, то в отчете появится также текстовый вывод (stdout, stderr) теста. В случае успешного завершения теста, его текстовый вывод подавляется.
Код возврата утилиты testc
будет равен нулю, если все тесты выполнились успешно. Если хоть один тест выполнился с ошибкой или был прерван сигналом, то утилита вернет значение отличное от нуля.
Утилита testc
написана и собрана таким образом, чтобы не иметь внешних зависимостей, за исключением стандартной библиотека libc
. Это позволяет использовать переменную окружения LD_LIBRARY_PATH
для указания пути к тестируемым библиотекам, что полезно для тестирования программного обеспечения на месте, без установки его в систему.
Тестируемые файлы могут задаваться в командной строке утилиты с абсолютным или относительным путем, а также без пути, т.е. только имя разделяемой библиотеки. В случае, когда путь не указан, утилита будет искать файл в путях LD_LIBRARY_PATH
, а затем в стандартных системных путях.
Ниже приведены примеры запуска утилиты testc
с указанием относительного пути, абсолютного пути, поиска по стандартным путям и поиска по путям LD_LIBRARY_PATH
, соответственно.
$ testc ./.libs/libfaux.so.1.0.0
$ testc /home/pkun/faux/.libs/libfaux.so.1.0.0
$ testc libfaux.so
$ LD_LIBRARY_PATH=/home/pkun/faux/.libs testc libfaux.so
-v
, --version
- Показать версию утилиты.-h
, --help
- Показать справку по использованию утилиты.$ LD_LIBRARY_PATH=.libs/ testc libfaux.so absent.so libsecond.so
--------------------------------------------------------------------------------
Processing module "libfaux.so" v1.0 ...
Test #001 testc_faux_ini_good() INI subsystem good: success
(!) Test #002 testc_faux_ini_bad() INI bad: failed (-1)
Some debug information here
[!] Test #003 testc_faux_ini_signal() Interrupted by signal: terminated (11)
Module tests: 3
Module errors: 2
--------------------------------------------------------------------------------
Error: Can't open module "absent.so"... Skipped
--------------------------------------------------------------------------------
Processing module "libsecond.so" v1.0 ...
Test #001 testc_faux_ini_good() INI subsystem good: success
(!) Test #002 testc_faux_ini_bad() INI bad: failed (-1)
Some debug information here
[!] Test #003 testc_faux_ini_signal() Interrupted by signal: terminated (11)
Module tests: 3
Module errors: 2
================================================================================
Total modules: 2
Total tests: 6
Total errors: 5
Система тестирования построена так, чтобы можно было писать тесты не линкуясь ни с какими специальными библиотеками тестирования и даже не использовать никакие специальные заголовочные файлы. Все, что требуется, это соответствие функций тестирования прототипу и объявление трех символов со специальными фиксированными именами. Это версия testc
API (старший байт и младший байт) и список функций тестирования.
Функция тестирования должна иметь следующий прототип:
int testc_my_func(void) {
...
}
Имя функции может быть произвольным. Рекомендуется использовать префикс testc_
, чтобы отличать функции тестирования от других.
Функция должна вернуть 0
в случае успеха или любое другое число при возникновении ошибки. Также функция тестирования может выводить на экран (stdout, stderr) любую отладочную информацию. Однако надо помнить, что отладочная информация появится в отчете утилиты testc
только в случае завершения теста с ошибкой. Для успешных тестов вывод подавляется.
Далее приведены простейшие примеры тестов.
Следующий тест всегда завершается успешно. Если тесту для работы нужны какие-либо внешние данные (или информация где эти данные взять), то можно передавать их тестовой функции через переменные окружения. В данном примере выводимая на экран строка никогда не появится в отчете, так как функция завершается успешно, а текстовый вывод успешных тестов подавляется.
int testc_faux_ini_good(void) {
char *path = NULL;
path = getenv("FAUX_INI_PATH");
if (path)
printf("Env var is [%s]\n", path);
return 0;
}
Следующая функция всегда завершается с ошибкой. В отчете появится выводимая на экран строка.
int testc_faux_ini_bad(void) {
printf("Some debug information here\n");
return -1;
}
Следующая функция приводит к Segmentation fault
и тест прерывается сигналом.
int testc_faux_ini_signal(void) {
char *p = NULL;
printf("%s\n", p);
return -1;
}
Отчет о выполнении трех этих функций можно увидеть выше, в разделе Пример отчета
.
Если в будущем прототип тестовой функции изменится, либо изменится формат списка тестовых функций, то утилита testc
должна узнать об этом. Для этого тестируемый объект должен содержать следующие символы:
const unsigned char testc_version_major = 1;
const unsigned char testc_version_minor = 0;
Таким образом тестируемый модуль объявляет версию API, которой он соответствует. Имена символов фиксированы и не могут быть другими. Сейчас существует только одна версия API 1.0
. Однако в будущем это может изменится. И хотя, строго говоря, объявление версии API является необязательным, рекомендуется всегда указывать эту версию. Если версия не указана, то утилита testc
считает, что модуль соответствует самой свежей версии API.
Чтобы утилита testc
узнала о существовании тестовой функции, имя этой функции должно быть упомянуто в списке тестовых функций модуля. Символ, ссылающийся на список тестовых функций, имеет фиксированное имя и тип. Это массив пар текстовых строк.
const char *testc_module[][2] = {
{"testc_faux_ini_good", "INI subsystem good"},
{"testc_faux_ini_bad", "INI bad"},
{"testc_faux_ini_signal", "Interrupted by signal"},
{NULL, NULL}
};
Каждая пара текстовых строк описывает одну тестовую функцию. Первая строка - имя тестовой функции. По этому имени утилита testc
будет искать символ внутри разделяемого объекта.
Вторая строка - произвольное однострочное описание теста. Эта строка печатается в отчете утилиты testc
при выполнении соответствующего теста. Используется для информирования пользователя.
Список тестовых функций должен оканчиваться обязательной нулевой парой {NULL, NULL}
. Без этого утилита не узнает, где кончается список.
Все тестовые функции могут находится в отдельной разделяемой библиотеке, специально предусмотренной для тестирования. Сама библиотека может даже не входить в состав тестируемого проекта.
Тестовые функции могут входить в состав тестируемого проекта, но находиться в отдельных файлах. Эти файлы могут компилироваться или не компилироваться в зависимости от флагов сборки.
# Makefile.am
...
if TESTC
include $(top_srcdir)/testc_module/Makefile.am
endif
...
Тестовые функции могут входить в состав тестируемого проекта, и находиться непосредственно рядом с тестируемыми функциями. Эти тестовые функции могут компилироваться или не компилироваться в зависимости от флагов сборки.
# Makefile.am
if TESTC
libfaux_la_CFLAGS = -DTESTC
endif
int foo(int y) {
...
}
#ifdef TESTC
int testc_foo(void) {
...
if (foo(7)) ...
}
#endif
Такой способ позволит тестировать не только интерфейс библиотеки, но также и локальные статические функции.