× К оглавлению На главную Об авторе

Дата и время публикации:    
Дата и время модификации:

Cуществует [масса фреймворков модульного тестирования для С](https://stackoverflow.com/a/65845/24648605), но они не всегда бывают [полезны](https://entrenchant.blogspot.com/2010/08/unit-testing-in-c.html). Конечно, каждый из них можно использовать для выполнения автоматического тестирования, а некоторые из них могут даже генерировать тестовую заглушку, но у них один недостаток ─ они ограничены своими API, то есть только той функцией, которой могут быть модульно протестированы с подключением заголовочных файлов, где объявлены их прототипы. Язык программирования C отличается от объектно-ориентированных языков программирования тем, что большая часть работы заключается в модульном тестировании, которое производится с помощью статических, не экспортируемых функциямй. Поэтому нет убедительных причин, чтобы использовать модульные тесты вместо обычно подхода, когда в Makefile'ах используются тестовые программы с тестовыми данными и возвращающие 0 или 1 . Модули тестирования работают по одинаковому принципу: ┌─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ Модуль тестирования ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─┐ ┌─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─┐ ┌──────────────────────┐ ┌─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─┐ │ │ │ Тестовая ├──────▶│ Тестируемый ├──────▶│ Выходная тестовая ├────┐ ┌──▶│ последовательность │ │ программной модуль │ │ последовательность │ │ │ │ │ └─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─┘ └──────────────────────┘ └─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─┘ │ │ │ │ │ │ ┌─────────────────────────▼────┐ │ │ Сравнение с ожидаемой │ │ │ │ выходной последовательностью │ │ └─────────────────────────┬────┘ │ └── ─ ─ ─ ─ ─ ─ ─ ── ─ ─ ─ ─ ─ ─ ─ ── ─ ─ ─ ─ ─ ─ ─ ── ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─┘ │ │ повторное тестирование ┌─────────────────────────┐ 1 │ 0 Передача └──────────────────────────────────────────┤ Доработка кода, │◀─────────────────────◆──────▶ на тестирование │ исправление ошибок │ CI/CD └─────────────────────────┘ путем генерации тестовой последовательности или данных на вход передоверяемого программного модуля, который должен возвращать, в случае: * 0 -- правильного формирования ожидаемой выходной последовательности; * 1 -- расхождения полученной выходной последовательности с ожидаемой. ## 2. Использование Тестирование кода разделяемой библиотеки, написанного на С, будет производится в поддиректории сfuncs/tests модуля [PyCFuncs](https://github.com/rjaan/PyCFuncs): . ├── basic_funcs.c ├── clone_funcs.c ├── logmsg.h ├── Makefile ├── mstack.h └── tests ├── basic_funcs-test.c ├── clone_funcs-test.c └── Makefile Сравнение выходной последовательности с ожидаемой для функций str_to_integer() и uint_pow(),на мой взгляд, лучше делать внутри модуля тестирования и уже возвращать результат в виде 0/1 : extern int str_to_integer( char *s ); extern unsigned int uint_pow( unsigned char x, unsigned int y ); int main(void){ char *s[] = { "1234", "-1234" }; if ( str_to_integer(s[0]) != 1234 ) return 1; if ( str_to_integer(s[1]) != -1234 ) return 1; if ( uint_pow( 123, 2 ) != 15129 ) return 1; return 0; } В данном случае, если контрольные значения сойдутся с полученными модуль тестирования basic_funcs-test вернет 0, а если нет ─ 1 . Были добавлены модули тестирования basic_funcs-test и clone_funcs-test, директивно исполняемые целью ```make test``` в файле сfuncs/Makefile : ... .PHONY: test test: @echo "Running test-units for libcfuncs_${PLATFORM}:" make -C tests unit-tests PWD=$(PWD) ... Которая, в свою очередь, выполняет цель ```make unit-tests``` в ```сfuncs/test/Makefile```: ... .PHONY: unit-tests unit-tests: build-tests basic_funcs-test clone_funcs-test @echo "All tests were done." ; build-tests: basic_funcs.test clone_funcs.test @echo "All tests were successfully built." .PHONY: %-test %-test: %.test @echo -n "Checking the "; \ echo -n "$@" | sed 's/^\(.*\)-test/\1/' ; \ echo -n " module "; \ if $(TESTCMD) ; then \ echo " Ok" ; \ else \ echo " failure."; exit 1;\ fi %.test : %-test.c $(CC) $(CFLAGS) -o $@ $^ $(CFLAGS) -L$(LIB_PATH) -l$(LIB_NAME) ... Директивы ```LIB_PATH``` и ```LIB_NAME``` устанавливают путь и имя билиотеки с линкуемым тестом (```%.test```). Выполнение цели ```unit-tests``` можно представить в виде графа : unit-tests => make -C tests unit-tests PWD=$(PWD) │ ▼ └── build-tests │ ├── basic_funcs.test │ │ │ └────────────▶────────┐ │ │ └── clone_funcs.test ──▶───┤ │ %.test │ ┌── basic_funcs-test ──◀───┘ │ │ ▼ ▼ ┌──▶ Ok │ %-test => $TESTCMD ─┤ │ ▲ └──▶ failure │ │ └─── clone_funcs-test Выполняемая цель ```unit-tests``` обозначена .PHONY, т.к. имеет только одно выполняемое правило и группу целей: build-tests, clone_funcs.test и basic_funcs-test . Цель build-tests собирает бинарники тестов clone_funcs.test и basic_funcs.test, а цели basic_funcs-test и clone_funcs-test выполняют правило тестирования TESTCMD, которая запускается в целе ```%-test```, где оценивается результата выполнения директивы TESTCMD, которая прерывает выполнения цепочки целей, если результат не нулевой. Пример выполнения группы целей basic_funcs.test и clone_funcs.test : $ make test Running test-units for libcfuncs_linux: make -C tests unit-tests PWD=$HOME/Projects/PyCfuncs.git/cfuncs make[1]: вход в каталог «$HOME/Projects/PyCfuncs.git/cfuncs/tests» gcc -O2 -g -I $HOME/Projects/PyCfuncs.git/cfuncs/ -o basic_funcs.test basic_funcs-test.c -O2 -g -I $HOME/Projects/PyCfuncs.git/cfuncs -L$HOME/Projects/PyCfuncs.git/cfuncs -lcfuncs_linux gcc -O2 -g -I $HOME/Projects/PyCfuncs.git/cfuncs -o clone_funcs.test clone_funcs-test.c -O2 -g -I $HOME/Projects/PyCfuncs.git/cfuncs -L$HOME/Projects/PyCfuncs.git/cfuncs -lcfuncs_linux All tests were successfully built. ... Пример выполнения группы целей basic_funcs-test и clone_funcs-test : ... Checking the basic_funcs module Ok Checking the clone_funcs module failure. make[1]: *** [Makefile:28: clone_funcs-test] Ошибка 1 make[1]: выход из каталога «/home/user/Projects/Python/python-kivytwisted/src/python-modules/PyCfuncs.git/cfuncs/tests» make: *** [Makefile:28: test] Ошибка 2 Запуск директивы TESTCMD для программного модуля basic_funcs завершилось без ошибок, а clone_funcs ─ отказом (failure), потому что не были соблюдены необходимые условия тестирования системного вызова CLONE при создании нового процесса, флаг CLONE_NEWPID. Но, это уже другая история...