Unit testing

Wikipedia: Unit testing

Další typy testů

Integration (kombinace různých modulů), GUI (chování grafického rozhraní), installation (postup pro instalaci), regression (ověření předchozích bug fixů, změny konfigurace, apod.), security, atd.

Test-driven development

Vývojový cyklus podle test-driven development:

  1. Přidat test pro novou funkčnost
  2. Spustit všechny testy, nový test samozřejmě selže.
  3. Implementovat co nejjednodušší kód, který zajistí splnění všech testů. (V této fázi nový kód ani nemusí být obecný, může řešit jen konkrétní případ daný testem.)
  4. Ověřit, že všechny testy projdou bez chyb.
  5. Refaktorování kódu dle potřeby. V průběhu pomocí testů ověřovat, že nedošlo ke změně chování.
    • příklady refaktorování:
      • přesun kódu na logičtější místo
      • odstranění duplicitního kódu
      • přejmenování proměnných, funkcí, tříd
      • rozdělení funkcí a metod na menší části (to ale často znamená přidání nového testu, neboť každá funkce či metoda by měla být testovaná samostatně)
      • změna hierarchie dědičnosti mezi třídami
  6. (volitelně) Ověřit kvalitu kódu pomocí programů jako např. clang-tidy a clang-format.
  7. (volitelně) Commitovat změny do systému pro správu kódu (např. git).

Tyto kroky se opakují pro každou novou funkčnost, kterou je třeba v programu implementovat.

Změny a testy by měly být malé a inkrementální a commitované samostatně:

Rozšířením tohoto přístupu je behavior-driven development, kde se pro specifikaci očekávaného chování používá doménově specifický jazyk.

Knihovny pro testování v C++

Existuje spousta knihoven, my použijeme GoogleTest, což je zřejmě nejrozšířenější knihovna pro testování v C++.

Pro zprovoznění miniprojektu nejprve vytvořte soubor CMakeLists.txt v prázdném adresáři:

cmake_minimum_required(VERSION 3.25)
project(GoogletestDemo)

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

set(CMAKE_CXX_FLAGS "-Wall -pedantic")
set(CMAKE_CXX_FLAGS_DEBUG "-g")
set(CMAKE_CXX_FLAGS_RELEASE "-O3 -DNDEBUG")

# add normal target(s)
add_executable(demo demo.cpp)

# configure testing
include(CTest)  # this must be in the top-level CMakeLists.txt file!

include(FetchContent)

FetchContent_Declare(
   googletest
   GIT_REPOSITORY https://github.com/google/googletest.git
   GIT_TAG        v1.14.0
   OVERRIDE_FIND_PACKAGE
   SYSTEM
)

# For Windows: Prevent overriding the parent project's compiler/linker settings
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)

# Prevent installing GTest along with TNL (the tests themselves are not installed)
set(INSTALL_GTEST OFF CACHE BOOL "" FORCE)
set(INSTALL_GMOCK OFF CACHE BOOL "" FORCE)

FetchContent_MakeAvailable(googletest)

# add test target(s)
add_executable(test-demo test_demo.cpp)
target_link_libraries(test-demo PUBLIC GTest::gtest_main)
add_test(test-demo ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/test-demo)

Podrobnosti najdete na stránce Quickstart: Building with CMake.

Poté vytvořte soubory demo.h, demo.cpp a test_demo.cpp. V případě umístění do nějakého podadresáře (např. src) je potřeba upravit cesty i v souboru CMakeLists.txt. Na pořadí, v jakém budete přidávat kód do jednotlivých souborů, úplně nezáleží – my si vyzkoušíme test-driven development a jako první naprogramujeme test:

#include <gtest/gtest.h>
#include "demo.h"

// Tests factorial of 0.
TEST(FactorialTest, HandlesZeroInput)
{
    EXPECT_EQ(factorial(0), 1);
}

// Tests factorial of positive numbers.
TEST(FactorialTest, HandlesPositiveInput)
{
    EXPECT_EQ(factorial(1), 1);
    EXPECT_EQ(factorial(2), 2);
    EXPECT_EQ(factorial(3), 6);
    EXPECT_EQ(factorial(8), 40320);
}

Podrobnosti k makrům výše najdete na stránce GoogleTest Primer.

Test očekává implementaci funkce factorial v souboru demo.h, kterou přidáme jako druhou. Naposled přidáme kód do souboru demo.cpp – samotná spustitelná aplikace.

Pro připomenutí: kompilaci pomocí cmake můžeme provést následujícími příkazy:

  1. Konfigurace: cmake -B build -S . (případně s dalšími parametry, např. cmake -B build -S . -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo)
  2. Kompilace: cmake --build build

Poté můžeme spustit výsledný binární soubor, např. ./build/test-demo --help. Testy můžeme spouštět buď samostatně s určitými parametry, nebo celý test suite současně:

cmake --build build --target test