Тестирование кода на C
Перечислим их.
pros:
- тот же самый язык, что и для продукта
- тесты получаются компактными
cons:
- у Си не так много средств для тестирования в стандартной библиотеке. Скорее
их там вообще нет, если не считать стандартного
assert()
. Вам нужно будет написать свои примитивы функций (многие так и делают, чтобы не добавлять внешних зависимостей в проект) или использовать из существующих - библиотеки для тестирования на Си не богаты на функциональность. Для примитивного тестирования с помощью примеров их ещё хватит, а если хочется использовать позитивное генеративное тестирование (для негативного есть LibFuzzer), то уже сложнее. Да, для C и C++ есть нативные библиотеки для тестирования с помощью свойств: mcandre/qc, silentbicycle/theft, skhoroshavin/qcc, emil-e/rapidcheck, grogers0/CppQuickCheck, thejohnfreeman/autocheck, для тестирования с помощью поиска: teamcoinse/cavm, список библиотек и инструментов для тестирования с помощью моделей, но большинство из них заброшены.
- многословность языка по сравнению с высокоуровневыми языками
- трудоемкость разработки из-за низкоуровневости языка (всё это ручное управление памятью в случае тестов не сильно нужно, а проблем из-за него много может быть). Если при разработке самого продукта трудоёмкость может быть оправдана, то в случае тестов думаю, что нет.
Чтобы устранить большую часть минусов можно использовать скриптовые языки совместно с FFI.
FFI это Foreign Function Interface, то есть механизм, который позволяет выполнять функции, написанные на одном языке программирования, на другом языке. Например для Python есть библиотека CFFI.
Допустим у нас есть функция addme()
, которая складывает два целых числа.
Содержимое файла add.c
:
int addme(int a, int b) {
return (a + b);
}
Содержимое файла add.h
:
int addme(int a, int b);
Пример с демонстрацией CFFI для вызова функции addme()
будет выглядеть так:
from cffi import FFI
ffibuilder = FFI()
ffibuilder.cdef("int addme(int a, int b);")
ffibuilder.set_source("pyadd",'#include "add.h"',sources=["add.c"])
ffibuilder.compile()
from pyadd.lib import addme
print(addme(2, 6))
На мой взгляд, FFI для Питона не очень удобен в использовании. Другое дело FFI
для Lua. В LuaJIT модуль FFI встроен, а для PUC Rio Lua нужно дополнительно
установить библиотеку, которая доступна в LuaRocks. Давайте посмотрим на
пример использования FFI в Lua. Код на Си оставим тот же, но соберём его
вручную: gcc -o libadd.so -shared -fPIC -Wall -Werror add.c
.
Скрипт для использования функции addme()
в Lua будет таким:
local ffi = require "ffi"
ffi.cdef([[int addme(int a, int b);]])
local lib = ffi.load("./libadd.so")
print(lib.addme(1, 2))
Всё, что было нужно это описать интерфейс и подгрузить разделяемую библиотеку.
В обоих примерах скрипты всего лишь вычисляют сумму двух чисел, но понятно, что раз мы вообще можем использовать функцию, написанную на Си, в Питоне или Lua, то таким же образом можно и использовать эту функцию с нужными библиотеками. Для Питона это может быть pytest, Hypothesis, для Lua есть библиотека luc-tielen/lua-quickcheck. Её возможности скромнее, чем у Hypothesis, но там есть поддержка генераторов для стандартных типов данных, минимизация тестовых данных.
Кстати QuviQ, компания, в которой работает John Hughes, для тестирования C++ проектов c помощью своей коммерческой библиотеки на Erlang для PBT тестирования использует враппер eqc_cpp, который в свою очередь использует SWIG.
Удачного тестирования и не забывайте использовать санитайзеры вместе с генеративным тестированием! Так тестирование будет гораздо эффективнее.
Ссылки
- Про тестирование с помощью Python FFI есть доклад Александра Штеффена - Writing unit tests for C code in Python
- Обсуждение плюсов и минусов FFI
- статьи FFI Tutorial и FFI Library от автора LuaJIT
- Про накладные расходы для FFI в разных языках программирования
- Обзор библиотек на Си для тестирования с помощью свойств