Тестирование кода на C

Posted on

При разработке проектов, написанных на Си, для тестирования кода обычно используют фреймворки для юнит-тестирования, написанные тоже на Си (например Google Test или CMocka). Всё выглядит разумно - разработчики тестируют код на том же языке, на котором написан продукт. Тем не менее у юнит-тестов, написанных на Си, есть как плюсы так и минусы. Перечислим их:

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.

Удачного тестирования и не забывайте использовать санитайзеры вместе с генеративным тестированием! Так тестирование будет гораздо эффективнее.

Ссылки

Теги: testingsoftwarefeed