--- title: Система тестирования 'testc' author: Сергей Каличев <serj.kalichev(at)gmail.com> date: 2020 ... # О проекте Система тестирования 'testc' является частью проекта 'faux' (библиотека вспомогательных функций) и предназначена для модульного тестирования (unit-test) програмного обеспечения, написанного на языке C (Си). Утилита 'testc' последовательно запускает набор тестов, получает результат (успех/неуспех) и генерирует отчет. Каждый тест представляет собой функцию. Источником тестов может являться любой двоичный исполняемый файл - программа или разделяемая библиотека. Для этого внутри исполняемого файла должен быть определен символ с фиксированным именем. Символ указывает на массив, в котором хранится список тестовых функций. Таким образом исполняемый файл может содержать и рабочий код и тестовый код одновременно. Также тестовый код может содержаться и в отдельном модуле. В этом случае модуль должен быть слинкован с необходимыми ему библиотеками. Система расчитана на максимальную простоту создания тестов, а также на максимальную интеграцию тестирования в процесс разработки кода. ## Ссылки * Репозиторий GIT * Релизы * Список рассылки # Утилита '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 ``` Такой способ позволит тестировать не только интерфейс библиотеки, но также и локальные статические функции.