Модульные тесты защищают от регрессивных изменений кода и предоставляют разработчикам ПО подробную обратную связь.
Изучив материал статьи, вы убедитесь, насколько просто добавлять модульные тесты в C/C++ проект с помощью google test
.
Начальный этап
Возьмем простой пример вычисления среднего значения из массива целых чисел.
calculate_mean
принимает на вход массив целых чисел и его длину, а на выходе возвращает среднее значение массива (сумму массива, разделенную на его длину) в виде числа с плавающей точкой (float
).
Структура файла
+ Root
+ modules
- calculations.c
- calculations.h
- CMakeLists.txt
+ tests
- test_calculations.cpp
- CMakeLists.txt
- mainapp.c
- CMakeLists.txt
- conanfile.txt
Примечание. Полный вариант кода данной демоверсии доступен на GitHub.
modules/calculations.c
Этот модуль отвечает за вычисления:
#include <stdio.h>
#include "calculations.h"
int calculate_sum(int arr[], size_t length)
{
int sum = 0;
int i = 0;
for (i = 0; i < length; i++)
{
sum += arr[i];
}
return sum;
}
float calculate_mean(int arr[], size_t length)
{
if(length == 0)
{
// при длине равной 0, мы не можем вычислить среднее значение из-за ошибки деления на 0.
return 0;
}
// в ином случае:
int sum = calculate_sum(arr, length);
return (float)sum / length;
}
Все очень просто. У модуля есть функция для вычисления суммы чисел, которая вызывается для функции calculate_mean
, после чего возвращается результат деления суммы на длину.
Воспользуемся данным кодом в mainapp.c
:
#include <stdio.h>
#include "modules/calculations.h"
int main(int argc, char *argv[])
{
int arr[] = {1,5,4,6,7,9,8,10,2};
size_t n = sizeof(arr)/sizeof(arr[0]);
float mean = calculate_mean(arr, n);
printf("Mean=%.2f\n", mean);
return 0;
}
Теперь нам потребуются два файла CMakeLists.txt
: один для mainapp
, другой для модулей:
CMakeLists.txt
cmake_minimum_required(VERSION 3.10.2)
project(MyProject)
add_subdirectory(modules)
add_executable(${PROJECT_NAME} mainapp.c)
target_link_libraries(${PROJECT_NAME} calculations)
modules/CMakeLists.txt
project(calculations)
add_library(calculations calculations.c calculations.h)
Поясним в двух словах. Модули CMakeLists.txt
генерируют библиотеку с именем calculations
. Главный CMakeLists.txt
использует ее и связывает с главным исполняемым файлом.
Переходим к этапам компиляции и выполнения.
Компиляция:
cmake --build ./build --config Debug --target MyProject -j 10 --
Выполнение:
./build/MyProject
Результат:
Mean=5.78
Знакомство с Google Test
Google test или gtest
— это фреймворк с открытым ПО для модульного тестирования C\C++ проектов. Он легко интегрируется с CMake, располагает превосходным механизмом проверки утверждений и создает отчеты для отображения в формате XML, что позволяет интегрировать его с известными фреймворками CI\CD.
Шаг 1. Установка gtest из менеджера зависимостей Conan
Создаем файл conanfile.txt
в корневом каталоге:
[requires]
gtest/cci.20210126
[generators]
cmake
Выполняем conan install . -pr=myprofile
.
Шаг 2. Добавление gtest в CMakeLists
После установки gtest
добавляем его в качестве зависимости в главный файл CMakeLists.txt
:
cmake_minimum_required(VERSION 3.10.2)
project(MyProject)
include(${CMAKE_SOURCE_DIR}/conanbuildinfo.cmake)
conan_basic_setup()
add_subdirectory(modules)
add_subdirectory(tests)
add_executable(${PROJECT_NAME} mainapp.c)
target_link_libraries(${PROJECT_NAME} calculations)
Мы дополнили код 4 строками. Одна нужна для включения конфигураций Conan, две — для запуска настроек Conan CMake и еще одна — для добавления каталога tests
.
Шаг 3. Написание набора тестов
tests/test_calculations.cpp
#include <gtest/gtest.h>
extern "C"
{
#include "../modules/calculations.h"
}
TEST(test_calculations, simple_arr)
{
int arr[] = {1, 5, 4, 6, 7, 9, 8, 10, 2, 3};
size_t n = sizeof(arr) / sizeof(arr[0]);
float mean = calculate_mean(arr, n);
EXPECT_FLOAT_EQ(mean, 5.5);
}
TEST(test_calculations, empty_arr)
{
int arr[] = {};
float mean = calculate_mean(arr, 0);
EXPECT_FLOAT_EQ(mean, 0);
}
TEST(test_calculations, all_negatives)
{
int arr[] = {-1, -5, -4, -6, -7, -9, -8, -10, -2, -3};
size_t n = sizeof(arr) / sizeof(arr[0]);
float mean = calculate_mean(arr, n);
EXPECT_FLOAT_EQ(mean, -5.5);
}
TEST(test_calculations, mix_negative_positive)
{
int arr[] = {-1, -5, -4, 6, 7, 9, -8, -10, -2, -3};
size_t n = sizeof(arr) / sizeof(arr[0]);
float mean = calculate_mean(arr, n);
EXPECT_FLOAT_EQ(mean, -1.1);
}
TEST(test_calculations, with_zeros)
{
int arr[] = {-1, -5, -4, 0, 7, 9, 0, -10, -2, -3};
size_t n = sizeof(arr) / sizeof(arr[0]);
float mean = calculate_mean(arr, n);
EXPECT_FLOAT_EQ(mean, -0.89999998);
}
Выше представлен набор тестов, подлежащий выполнению.
В целом он содержит 5 тест-кейсов, охватывающих разные возможные сценарии.
Шаг 4. Настройка конфигурации исполняемого файла tests с CMake
tests/CMakeLists.txt
cmake_minimum_required(VERSION 3.10.2)
project(tests)
add_executable(${PROJECT_NAME} test_calculations.cpp)
set(CMAKE_CXX_STANDARD 11)
target_link_libraries(${PROJECT_NAME} PUBLIC
calculations
gtest
gtest_main
)
Здесь даны инструкции по созданию исполняемого файла tests
с тремя связанными библиотеками: calculations
(модуль, подлежащий тестированию), gtest
и gtest_main
.
Шаг 5. Выполнение тестов
Компиляция:
cmake --build ./build --config Debug --target tests -j 10 --
Выполнение:
build\bin\tests.exe
Результат:
[==========] Running 5 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 5 tests from test_calculations
[ RUN ] test_calculations.simple_arr
[ OK ] test_calculations.simple_arr (0 ms)
[ RUN ] test_calculations.empty_arr
[ OK ] test_calculations.empty_arr (0 ms)
[ RUN ] test_calculations.all_negatives
[ OK ] test_calculations.all_negatives (0 ms)
[ RUN ] test_calculations.mix_negative_positive
[ OK ] test_calculations.mix_negative_positive (0 ms)
[ RUN ] test_calculations.with_zeros
[ OK ] test_calculations.with_zeros (0 ms)
[----------] 5 tests from test_calculations (70 ms total)
[----------] Global test environment tear-down
[==========] 5 tests from 1 test suite ran. (107 ms total)
[ PASSED ] 5 tests.
Все 5 тестов были успешно выполнены и пройдены!
Запускаем их еще раз и экспортируем результаты в output.xml
:
build/bin/tests --gtest_output=xml:output.xml
Полученный результат в формате XML выглядит так:
<?xml version="1.0" encoding="UTF-8"?>
<testsuites tests="5" failures="0" disabled="0" errors="0" time="0.083" timestamp="2022-02-16T12:57:20.151" name="AllTests">
<testsuite name="test_calculations" tests="5" failures="0" disabled="0" skipped="0" errors="0" time="0.055" timestamp="2022-02-16T12:57:20.166">
<testcase name="simple_arr" status="run" result="completed" time="0" timestamp="2022-02-16T12:57:20.171" classname="test_calculations" />
<testcase name="empty_arr" status="run" result="completed" time="0" timestamp="2022-02-16T12:57:20.181" classname="test_calculations" />
<testcase name="all_negatives" status="run" result="completed" time="0" timestamp="2022-02-16T12:57:20.192" classname="test_calculations" />
<testcase name="mix_negative_positive" status="run" result="completed"
time="0" timestamp="2022-02-16T12:57:20.204" classname="test_calculations" />
<testcase name="with_zeros" status="run" result="completed" time="0" timestamp="2022-02-16T12:57:20.216" classname="test_calculations" />
</testsuite>
</testsuites>
Загружаем его в xUnit Viewer от CodeBeautify:
Отлично!
Такой вариант можно легко опубликовать как результат тестирования в конвейерах ci/cd.
Читайте также:
- Идиома CRTP и написание общих функций в C++
- 2 инструмента для автоматизации тестирования производительности на стороне клиента
- Визуализация стратегии автоматизированного тестирования
Читайте нас в Telegram, VK и Дзен
Перевод статьи Eldad Uzman: Introduction to Google Test: An Open Source C/C++ Unit-Testing Framework