# Базовая настройка тестирования. ## Используемые технологии - C++11 - CMake - GTest (Google test framework for C++) - Conan ## Структура проекта ``` - Project/ -- build/ --- --- bin/ ---- program -- include/ --- algos.h --- algos.c -- src/ --- main.c -- tests/ --- test.cpp -- conanfile.txt -- CMakeLists.txt ``` ## conanfile Для подключения тестового фреймворка используется система менеджмента артефактов Conan, которая позволяет осуществлять подключение заранее скомпилированных библиотек без необходимости их установки на ОС разработчика. Подключение осуществляется описанием корректных настроек в файле conanfile.txt: ``` [requires] gtest/cci.20210126 [generators] cmake ``` Таким образом, будет подключена последняя на момент написания данного документа сборка фреймворка тестирования. ## sources Исходные коды проекта разделены на две части: собственно исходники и включаемые файлы. Исходники - это код самой программы, использующей алгоритмы, описанные во включаемых заголовочных файлах. ### src/main.c В файле с программой подключается файл с тестируемым алгоритмом, который используется в коде программы: ```C++ #include #include "../include/algos.h" void swap(char* a, char* b); int main(int argc, const char** argv) { char a = -11; char b = 15; printf("a=%d; b=%d\n", a, b); swap(&a, &b); printf("a=%d; b=%d\n", a, b); return 0; } ``` Выводом данного кода будет: ``` user@debian: ./build/bin/prog a=-11; b=15 a=15; b=-11 ``` ### include/algos.h Подключаемый в код программ и тестов заголовочный файл должен содержать только одно объявление функций: ```C++ #ifndef algos #define algos void swap(char* a, char* b); #endif ``` ### include/algos.c файл с реализацией лаконичен: ```C++ #include "algos.h" void swap(char* a, char* b) { *a ^= *b; *b ^= *a; *a ^= *b; } ``` ## tests Тесты описываются в отдельном файле, из которого будет собран бинарный файл. Важно, что фреймворк работает только с С++11 или новее, поэтому использовать более старый стандарт не получится, а исходные файлы на С нужно включать в секции `extern "C"`. Каждый тест описывается в специальной макрофункции `TEST` в параметрах которой указывается детальное название теста. В данном примере будет использоваться только один ассерт - `EXPECT_EQ`, который проверяет значение переданной первым аргументом переменной на равенство второму аргументу ### tests/test.cpp ```C++ #include #include extern "C" { #include "../includes/algos.h" } TEST(HelloTest, WeTestHere) { char a = -11; char b = 15; swap(&a, &b); EXPECT_EQ(a, 15); EXPECT_EQ(b, -11); } TEST(AnotherTest, WeTestThere) { char a = -11; char b = -11; swap(&a, &b); EXPECT_EQ(a, -11); EXPECT_EQ(b, -11); } ``` ## CMake Сборщик CMake описывает правила, по которым будет сформировал Makefile для дальнейшей сборки утилитой GNU Automake или Ninja. В файл настроек необходимо включить строки подключения артефактов Conan, поскольку мы используем готовую сборку фреймворка тестирования, а не устанавливаем его на компьютер. Также в скрипте указывается два исполняемых файла - для сборки программы и тестов, соответственно. ``` cmake_minimum_required(VERSION 3.5) project(BasicC) enable_testing() # GoogleTest requires at least C++11 set(CMAKE_CXX_STANDARD 11) # conan dependencies include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake) conan_basic_setup() # test executable add_executable(check tests/test.cpp include/algos.h include/algos.c) target_link_libraries(check ${CONAN_LIBS} gtest_main) # let make know we use tests include(GoogleTest) gtest_discover_tests(check) # application executable add_executable(prog src/main.c include/algos.h include/algos.c) ``` ## Запуск Запуск осуществляется из папки `build`, соответственно: - `conan install ..` (установить зависимости conan, описанные в файле папкой выше) - `cmake ..` (подготовить Makefile по правилам из файла папкой выше) - `make`/`ninja` (собрать проект) - `ctest` (запустить тесты) - `./bin/prog` (запустить программу) В результате запуска тестов получится следующий вывод: ``` Test project /home/user/Documents/book-c-basic/build Start 1: HelloTest.WeTestHere 1/2 Test #1: HelloTest.WeTestHere ............. Passed 0.01 sec Start 2: AnotherTest.WeTestThere 2/2 Test #2: AnotherTest.WeTestThere .......... Passed 0.00 sec 100% tests passed, 0 tests failed out of 2 Total Test time (real) = 0.01 sec ``` ## Сборка артефакта Данная часть руководства рассматривается в случае, если собранные артефакты не предназначены под конфигурацию Вашей ОС. В качестве примера создаётся артефакт **GTest** под **MinGW**, который отсутствует на данный момент на офф репозиториях **conan center**. * Инициализация репозитория в папке, где будет находиться скаченный с Интернета проект; * Скачать **.zip git googletest**; * Распаковать в папку, где инициализирован репозиторий; * Создать проект на внутреннем **gitlab** и заремоутить; * Теперь нужно создать проект тестирования артефакта, **conan** позволяет уже создать готовый **sample package recipe** для тестирования после сборки артефакта; * Создание рецепта пакета (package recipe) - `conan create . testg/testing`; * После чего появится следующий проект: ``` - Project/ -- test_package/ --- CMakeLists.txt --- conanfile.txt --- example.cpp -- conanfile.py ``` * Поменять во внешнем файле **conanfile(py)** в методе `source` команды `git clone` ссылку на соответствующий проект, а также следующие изменения в данном файле: В методе `source` поменять название проекта и путь до CMakeLists.txt: ```python tools.replace_in_file("hello/CMakeLists.txt", "PROJECT(HelloWorld)", '''PROJECT(HelloWorld) include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake) conan_basic_setup()''') ``` На: ```python tools.replace_in_file("googletest/CMakeLists.txt", "project(googletest-distribution)", '''project(googletest-distribution) include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake) conan_basic_setup()''') ``` В методе `build` меняем соурс папку: ```python def build(self): cmake = CMake(self) cmake.configure(source_folder="hello") cmake.build() ``` На: ```python def build(self): cmake = CMake(self) cmake.configure(source_folder="googletest") cmake.build() ``` В методе `package` изменить название папки и пути до заголовков: ```python def package(self): self.copy("*.h", dst="include", src="hello") self.copy("*hello.lib", dst="lib", keep_path=False) self.copy("*.dll", dst="bin", keep_path=False) self.copy("*.so", dst="lib", keep_path=False) self.copy("*.dylib", dst="lib", keep_path=False) self.copy("*.a", dst="lib", keep_path=False) ``` На: ```python def package(self): self.copy("*.h", dst="include/gtest", src="googletest/googletest/include/gtest") self.copy("*gtest.lib", dst="lib", keep_path=False) self.copy("*.dll", dst="bin", keep_path=False) self.copy("*.so", dst="lib", keep_path=False) self.copy("*.dylib", dst="lib", keep_path=False) self.copy("*.a", dst="lib", keep_path=False) ``` Последний метод будет `package_info`: ```python def package_info(self): self.cpp_info.libs = ["hello"] ``` На: ```python def package_info(self): self.cpp_info.libs = ["gtest"] ``` * После выполнения настроек **.py** файла можно выполнить - `conan create . testg/testing`; * Затем, можно выполнить `upload` артефакта и использовать его по мануалу, который описан выше. Лабо можно использовать данный артефакт локально, conan автоматически вначале проверяет cache, и только потом обращается в remote, где указаны соответствующие ссылки (в нашем случае на Nexus). Также хотелось бы упомянуть один нюанс. Если Вы загружаете уже собранный артефакт, убедитесь, что в файле conan'а настроек конфигураций не были сбиты пути в `[env]` разделе. Была ситуация, когда был загружен артефакт, собранный на ОС GNU Linux, и в данном файле изменились пути под вид данной ОС (C:\MinGW... -> /c/MinGW/).