Важливість тестування і авто-тестів

Неодноразово у своїх статтях на даному сайті наголошував про важливість тестування. Тести це дуже важливо!

На жаль, з мого досвіду часто розробники нехтують їх написанню. І це являється великою проблемою. Якщо закривати очі і не бачити проблем, це як мінімум не допоможе бізнесу. Кожний розробник чи компанія повинні в повній мірі знати чи функції програми працюють коректно.

Чи коректно працює код? Чи вихідний результат функцій чи процедур відповідає очікуванням розробника? Чи код переміщений на іншу версію ОС або взагалі іншу платформу працює так само і видає той самий очікуваний результат як і на попередній системі? Чи банальне оновлення програмного забезпечення операційної системи і сторонніх додатків не змінило поведінку коду?

На усі ці питання можуть відповісти ціла купа авто-тестів, які будуть постачатись з усім «робочим» кодом і періодично виконуватись (найкраще автоматично за допомогою систем CI/CD).

Фреймворк GTest від Google

І для для систем написаних на С/C++ цю проблему написання автотестів може полегшити фреймворк тестування від корпорації Google на ім’я GTest.

GTest являється планетарно відомим фреймворком яким користуються багато відомих проектів на подобі Android Open Source Project i Chromium, які неодмінно являються собою приклад того, який успіх можуть досягнути проекти разом з тестами і TDD.

Приклади тестів Android Open Source Project

Файл тестів для TCP підключень з використанням класу похідного від testing::Test: https://github.com/aosp-mirror/platform_system_core/blob/8ebc82576e67d06ea38b38953cb71f8b9ebe902a/fastboot/tcp_test.cpp#L95.

Приклади тести проекту Chromium

Ось приклад використання GTest у проекті Chromium для тестування процедур для роботи з Base64 : https://github.com/chromium/chromium/blob/b4ac59211d3167385c2bfade32621c225e33d565/base/base64_unittest.cc#L16.

Основні посилання на прокет

Детальніше про історію GTest можна переглянути за адресою https://en.wikipedia.org/wiki/Google_Test або на самому домашньому сайті проекту http://google.github.io/googletest/ де також розміщена уся вичерпна документація з прикладами на англійській мові.

Проект також можна знайти на GitHub за адресою https://github.com/google/googletest де можна завантажити найсвіжішу версію фреймворку тестування.

Встановлення GTest

Встановлення на GNU/Linux системах

Щоб встановити gtest на локальну GNU/Linux систему на подобі Ubuntu, Linux Mint або інші Debian похідні ОС, потрібно виконати наступну команду у терміналі:

sudo apt-get install -y libgtest-dev

Якщо встановлення пройшло успішно, надалі можна почати використовувати GTest у повній мірі.

Встановлення з джерельного коду проекту GTest

Щоб зібрати проект вручну необхідно завантажити вихідний джерельний код GTest з GitHub-репозиторію за адресою https://github.com/google/googletest, використовуючи наступну команду:

git clone https://github.com/google/googletest.git

Після успішного клонування проекту на локальну систему з GitHub-репозиторію, необхідно побудувати і інсталювати проект за допомогою стандартних команд:

cd googletest
mkdir -vp build
cd build
cmake ../
cmake --build . -j$(nproc)
sudo make install

Вивід у терміналі може виглядати наступним чином

/resources/uploads/img/GTest-clone-and-make.png

Якщо виникла помилка з відсутньою системою Сmake на локальній системі, можна звернутись до статті http://www.kytok.org.ua/post/cmake-lehkyj-start де детально роз’яснюється як встановити систему CMake.

Встановлення на MS Windows

Для того щоб встановити GTest на локальну машину під управлінням MS Windows для MS Visual Studio можна перейти за посиланням https://learn.microsoft.com/uk-ua/visualstudio/test/how-to-use-google-test-for-cpp?view=vs-2022.

Також можна скористатись попередніми інструкціями для компіляції і встановлення проекту з вихідного джерельного коду.

Встановлення GTest прямо у директорію проекту

На сторінці документації проекту GTest розміщені інструкції інтгерації GTest з скачуванням і встановленням фреймворку у дерево побудови коду проекту, яка розміщена на сторінці http://google.github.io/googletest/quickstart-cmake.html, що дозволить не встановлювати проект у систему.

Інтеграція GTest з CMake-проектом

Найперше з чого потрібно розпочати роботу з GTest — це як вбудувати фреймворк тестування у систему побудови проекту CMake.

Для того, щоб імпортувати відомості про встановлений GTest у проект yf CMake, необхідно використати команду find_package з параметром GTest.

find_package(GTest REQUIRED)

Після чого необхідно скомпонувати відповідну ціль тесту з головною функцією тесту, яку постачає GTest фреймворк за допомогою команди

target_link_libraries(
       <Назва цілі тесту>
       GTest::gtest_main
)

Приклад повного лістингу такого CMakeLists.txt файлу може виглядати наступним чином:

# Версія CMake на якій розроблявся проект
cmake_minimum_required(VERSION 3.26)

# виномимо довгу назву проекту
# у окрему змінну для зручності
set(PRJ "kytok_org_ua_gtest_w_cmake_intro")

# обов'язкова команда project з назвою проекту
project(
  ${PRJ} 
  VERSION 1.0.0
  DESCRIPTION "Демонстративний проект використання GTest разом з CMake"
  HOMEPAGE_URL "http://www.kytok.org.ua/post/cmake-komanda-project"
  LANGUAGES CXX
)

# встановлюємо використовувану пову і її стандарт
set(CMAKE_CXX_STANDARD 17)

# додаємо джерельні файли до проекту
add_executable(
        ${PRJ}
        tests-examples/TestsExamples.cpp
)

# виконуємо пошук GTest фреймворку і
# імпортуємо його
find_package(GTest REQUIRED)

# вмикаємо тестування для
# використання програмую ctest
enable_testing()

# компонуємо об'єктні побудовані файли
# проекту з головною функцією main
# яку нам люб'язно проедоставляє GTest
# зазвичай, головний файл проекту не 
# компонується з тестовою main-функцією
target_link_libraries(
        ${PRJ}
        GTest::gtest_main
)

# додаємо зенерований бінарний файл
# проекту у якості тесту для виконання
# командою ctest
add_test(
        NAME ${PRJ}
        COMMAND $<TARGET_FILE:${PRJ}>
)

Даний приклад CMakeLists.txt можна переглянути і завантажити за посиланням на репозиторій GitHub https://github.com/yuriysydor1991/kytok.org.ua-gtest-w-cmake-intro/blob/1.0.0/CMakeLists.txt, а сам проект розміщений за адресою https://github.com/yuriysydor1991/kytok.org.ua-gtest-w-cmake-intro/tree/main.

Зверніть увагу! Що даний лістинг головного CMakeLists.txt файлу проекту призначений тільки для тестувального коду, саме тому головний файл проекту компонується з GTest::gtest_main. В звичайних проектах дана бібліотека компонується тільки з тестовими файлами, а головний бінарний файл проекту - ні.

Приклади тестів

Хоча приклади тестів містять доволі таки тривіальний код, за допомогою цих простих блоків коду можна себе добре забезпечити від неочікуваної поведінки і результатів.

Самі тести написані для функцій стандартної бібліотеки, щоб не відвертати увагу на інший код.

Файл тестів у повному обсязі можна переглянути за адресою https://github.com/yuriysydor1991/kytok.org.ua-gtest-w-cmake-intro/blob/main/tests-examples/GTestsExamples.cpp.

Приклад #1 — тест для функції std::isdigit

Даний тест демонструє використання і перевірку функції std::isdigit, яка описується у статті http://www.kytok.org.ua/post/cplusplus-std-isdigit. Дана фукнція повертає булеве значення true, якщо переданий їй параметр у якості char-символу містить у собі одне з цифрових значень від 0 до 9, в інших випадках дана функція поверне false.

/*
 * Приклад перевірки функції std::isdigit
 * Опис розміщеній у статті за адресою
 * http://www.kytok.org.ua/post/cplusplus-std-isdigit
 * */
TEST(ASimpleTest, isdigit_simple_test_success)
{
  std::locale loc ("uk_UA.UTF-8") ;

  // перевіряємо чи isdigit поверне true
  // для усіх наявних цифр ASCII
  // за допомогою GTest макросу EXPECT_TRUE
  for (auto iter='0'; iter<='9'; ++iter) {
    EXPECT_TRUE (std::isdigit(iter, loc));
  }

  // перевіряємо чи isdigit поверне false
  // на деяких символах алфавіту
  // за допомогою GTest макросу EXPECT_FALSE
  EXPECT_FALSE (std::isdigit(L'ї', loc));
  EXPECT_FALSE (std::isdigit(L'Ї', loc));
  EXPECT_FALSE (std::isdigit(L'г', loc));
  EXPECT_FALSE (std::isdigit(L'Ґ', loc));
}

Сам тест можна переглянути за адресою https://github.com/yuriysydor1991/kytok.org.ua-gtest-w-cmake-intro/blob/1.0.0/tests-examples/GTestsExamples.cpp#L17

Приклад #2 - тест для функцій std::toupper і std::tolower

Наступний приклад показує використання і тест функцій std::toupper і std::tolower, про які розказувалось у статтях http://www.kytok.org.ua/post/cplusplus-std-toupper і http://www.kytok.org.ua/post/cplusplus-std-tolower. Якщо коротко, то ці функції повертають передані символи у верхньому або у нижньому регістрах відповідно. А сам тест на декількох прикладах українських символів перевіряє отриманий результат.
/*
 * Приклад перевірки функції std::toupper
 * Опис розміщеній у статті за адресою
 * http://www.kytok.org.ua/post/cplusplus-std-toupper
 * */
TEST(ASimpleTest, toupper_simple_test_success)
{
  std::locale loc ("uk_UA.UTF-8") ;

  // перевіряємо чи toupper поверне символ
  // верхнього регістру
  // через оператор == і за допомогою
  // GTest макросу EXPECT_EQ
  EXPECT_EQ (std::toupper(L'ґ', loc), L'Ґ');
  EXPECT_EQ (std::toupper(L'ї', loc), L'Ї');
}

/*
 * Приклад перевірки функції std::tolower
 * Опис розміщеній у статті за адресою
 * http://www.kytok.org.ua/post/cplusplus-std-tolower
 * */
TEST(ASimpleTest, tolower_simple_test_success)
{
  std::locale loc ("uk_UA.UTF-8") ;

  // перевіряємо чи toupper поверне символ
  // нижнього регістру
  // через оператор == і EXPECT_EQ
  EXPECT_EQ (std::tolower(L'Ґ', loc), L'ґ');
  EXPECT_EQ (std::tolower(L'Ї', loc), L'ї');
}

Дані тести можна переглянути за адресою https://github.com/yuriysydor1991/kytok.org.ua-gtest-w-cmake-intro/blob/1.0.0/tests-examples/GTestsExamples.cpp#L42

Приклад #3 — тест для функції std::set_intersection

Даний тест демонструє використання і перевірку функції з стандартної бібліотеки std::set_intersection, яка допомагає отримати результат від перерізу множин переданих двох контейнерів. Дана функція описана у статті за адресою http://www.kytok.org.ua/post/cplusplus-std-set_intersection-pereriz-mnozhyn.
/*
 * Приклад перевірки функції std::set_intersection
 * Опис розміщеній у статті за адресою
 * http://www.kytok.org.ua/post/cplusplus-std-set_intersection-pereriz-mnozhyn
 * */
TEST(ASimpleTest, set_intersection_simple_test_success)
{
  // створюємо піддослідні множини
  const std::set<int> numset1 {0, 2, 4, 6, 8, 10, 11} ;
  const std::set<int> numset2 {0, 1, 3, 5, 7, 9, 11} ;

  // очікуваний результат
  const std::set<int> expected {0, 11} ;

  std::set<int> setsIntersection ;

  // виклик функції обчислення перерізу множин
  std::set_intersection(
    numset1.begin(), numset1.end(),
    numset2.begin(), numset2.end(),
    std::inserter(setsIntersection, setsIntersection.begin())
  );

  // перевіряємо чи результуючий і очікуваний
  // контейнери рівні
  ASSERT_EQ(expected, setsIntersection);
}

Даний тест можна переглянути за адресою https://github.com/yuriysydor1991/kytok.org.ua-gtest-w-cmake-intro/blob/1.0.0/tests-examples/GTestsExamples.cpp#L75

Збирання тестового проекту

Тестовий проект, використаний у даній статті можна клонувати і скомпілювати за допомогою наступних команд

git clone https://github.com/yuriysydor1991/kytok.org.ua-gtest-w-cmake-intro.git
cd kytok.org.ua-gtest-w-cmake-intro/
mkdir -vp build
cd build
cmake ../
cmake --build . -j$(nproc)

# для виконання тестів скористайся або цією командою
ctest 

# або наступними
./kytok_org_ua_gtest_w_cmake_intro

Вивід може бути подібний до наступного.

/resources/uploads/img/GTest-into project clone and execution.png

У даному проекті-прикладі усі тести виконуються успішно. Але можна спробувати модифікувати тест, щоб побачити як виглядає вивід проваленого тесту:

/resources/uploads/img/Gtest-intro - failed test example.png

Також необхідно пам'ятати, що у разі провалу тесту команда cmake або сам бінарь тесту поверне ненульовий результат. Що у свою чергу допоможе автоматизувати перевірку коду і виявлення проблем.

/resources/uploads/img/GTest-intro - cmake returns non zero result.png

Висновок

Хоча тестування і TDD включано з фреймворком на подобі GTest і являються потужним інструментом запобіганню помилок, але він не являється панацеєю. Тому потрібно з увагою ставитися до коду і тестування.

Тестування багато не буває!