testc.ru.md 14 KB


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;
}

Отчет о выполнении трех этих функций можно увидеть выше, в разделе 'Пример отчета'.

Версия API

Если в будущем прототип тестовой функции изменится, либо изменится формат списка тестовых функций, то утилита '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

Такой способ позволит тестировать не только интерфейс библиотеки, но также и локальные статические функции.