Testing, Benchmarking & Sanitizers
Scrieți teste unitare cu GoogleTest, comparați cu Google Benchmark, identificați erori cu dezinfectanții (ASan, UBSan, TSan) și integrați analiza statică în conducta dvs. CI/CD.
Piramida de testare pentru C++
Dezvoltarea profesională C++ se bazează pe mai multe straturi de asigurare a calității: testele unitare verifică funcțiile și clasele individuale, testele de integrare verifică interacțiunile componentelor, dezinfectanții detectează erorile de memorie și comportamentul nedefinit în timpul execuției, analiza statică găsește erori fără a rula codul și benchmarks asigură performanța îndeplinește cerințele. Împreună, aceste instrumente formează o plasă de siguranță care prinde bug-uri înainte ca acestea să ajungă la producție.
Cele mai populare două cadre de testare C++ sunt GoogleTest (de la Google, utilizat pe scară largă în industrie) și Catch2 (numai antet, sintaxă în stil BDD). Ambele se integrează bine cu sistemele CMake și CI/CD.
Testare unitară cu GoogleTest
GoogleTest oferă macrocomenzi pentru afirmații (EXPECT_*pentru non-fatal,ASSERT_*pentru fatal), dispozitive de testare pentru setări/demontare partajate și teste parametrizate:
#include <gtest/gtest.h>
#include <vector>
#include <stdexcept>
// Function under test
int factorial(int n) {
if (n < 0) throw std::invalid_argument("negative input");
if (n <= 1) return 1;
return n * factorial(n - 1);
}
// Basic test — TEST(TestSuite, TestName)
TEST(FactorialTest, HandlesZero) {
EXPECT_EQ(factorial(0), 1);
}
TEST(FactorialTest, HandlesPositive) {
EXPECT_EQ(factorial(1), 1);
EXPECT_EQ(factorial(5), 120);
EXPECT_EQ(factorial(10), 3628800);
}
TEST(FactorialTest, ThrowsOnNegative) {
EXPECT_THROW(factorial(-1), std::invalid_argument);
}
// Test fixture — shared setup/teardown
class StackTest : public ::testing::Test {
protected:
std::vector<int> stack;
void SetUp() override {
stack.push_back(1);
stack.push_back(2);
stack.push_back(3);
}
void TearDown() override {
stack.clear();
}
};
TEST_F(StackTest, TopElement) {
EXPECT_EQ(stack.back(), 3);
}
TEST_F(StackTest, PopReducesSize) {
stack.pop_back();
EXPECT_EQ(stack.size(), 2);
EXPECT_EQ(stack.back(), 2);
}
TEST_F(StackTest, IsNotEmpty) {
ASSERT_FALSE(stack.empty()); // fatal — stops test if false
EXPECT_GT(stack.size(), 0u); // non-fatal — continues on failure
}Integrarea testelor cu CMake
Configurați GoogleTest cu CMake și rulați teste prin CTest, rulerul de testare încorporat:
# CMakeLists.txt for testing
cmake_minimum_required(VERSION 3.20)
project(MyProject LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Fetch GoogleTest
include(FetchContent)
FetchContent_Declare(
googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG v1.14.0
)
FetchContent_MakeAvailable(googletest)
# Main library
add_library(mylib src/math.cpp src/utils.cpp)
target_include_directories(mylib PUBLIC include)
# Test executable
enable_testing()
add_executable(unit_tests
tests/test_math.cpp
tests/test_utils.cpp
)
target_link_libraries(unit_tests PRIVATE mylib GTest::gtest_main)
# Register tests with CTest
include(GoogleTest)
gtest_discover_tests(unit_tests)
# Run tests:
# cmake -B build && cmake --build build
# cd build && ctest --output-on-failure --parallel $(nproc)Dezinfectanți: Detectarea erorilor de rulare
Dezinfectanții sunt verificări de rulare instrumentate de compilator care prinde clase întregi de erori. Activați-le cu semnalizatoarele compilatorului - adaugă o încetinire de ~2 ori, dar prind erori pe care nicio cantitate de testare nu le poate găsi fără ele:
# AddressSanitizer (ASan) — catches memory errors
# Buffer overflows, use-after-free, double-free, memory leaks
cmake -B build-asan \
-DCMAKE_CXX_FLAGS="-fsanitize=address -fno-omit-frame-pointer -g" \
-DCMAKE_EXE_LINKER_FLAGS="-fsanitize=address"
# UndefinedBehaviorSanitizer (UBSan) — catches UB
# Signed overflow, null dereference, misaligned access, shift errors
cmake -B build-ubsan \
-DCMAKE_CXX_FLAGS="-fsanitize=undefined -fno-omit-frame-pointer -g" \
-DCMAKE_EXE_LINKER_FLAGS="-fsanitize=undefined"
# ThreadSanitizer (TSan) — catches data races
# Data races, lock-order inversions, deadlocks
cmake -B build-tsan \
-DCMAKE_CXX_FLAGS="-fsanitize=thread -g" \
-DCMAKE_EXE_LINKER_FLAGS="-fsanitize=thread"
# MemorySanitizer (MSan) — catches uninitialized reads (Clang only)
cmake -B build-msan \
-DCMAKE_CXX_FLAGS="-fsanitize=memory -fno-omit-frame-pointer -g" \
-DCMAKE_EXE_LINKER_FLAGS="-fsanitize=memory"
# Combine ASan + UBSan (commonly done together)
cmake -B build-asan-ubsan \
-DCMAKE_CXX_FLAGS="-fsanitize=address,undefined -fno-omit-frame-pointer -g" \
-DCMAKE_EXE_LINKER_FLAGS="-fsanitize=address,undefined"
# IMPORTANT: ASan and TSan cannot be combined — use separate builds
# IMPORTANT: Always compile ALL code with sanitizer flags (including libraries)Static Analysis & Code Coverage
Analiza statică vă examinează codul fără a-l rula, detectând erori, încălcări ale stilului și probleme de securitate:
clang-tidy — Cel mai puternic linter C++. Verifică oportunitățile de modernizare, problemele de performanță, lizibilitatea și modelele predispuse la erori:
clang-tidy src/*.cpp -- -std=c++20 -I include
# Or with CMake:
cmake -B build -DCMAKE_CXX_CLANG_TIDY="clang-tidy;-checks=*,-fuchsia-*"cppcheck — Complementary to clang-tidy, catches different bug classes:
cppcheck --enable=all --std=c++20 src/Code Coverage — Measure which lines your tests actually execute:
cmake -B build-cov -DCMAKE_CXX_FLAGS="--coverage -g -O0"
cmake --build build-cov && cd build-cov && ctest
gcovr --html-details coverage.html -r ../srcVizualizați 80%+ acoperire de linie pe căile de cod critice. Acoperirea de 100% merită rareori efortul - concentrați-vă pe testarea logicii, cazurile marginale și gestionarea erorilor.
Construiți o strategie cuprinzătoare de testare:
1. Scrieți teste alături de cod — nu după. Dezvoltarea bazată pe teste (TDD) funcționează bine pentru C++: scrieți un test eșuat, implementați codul minim de trecut, refactorizați.
2. Rulați dezinfectante în CI — au sarcini CI separate pentru ASan+UBSan și TSan. Aceste prinde bug-uri pe care testele singure le dor.
3. Folosiți dispozitive de testare pentru configurarea/demontarea resurselor partajate (conexiuni la baze de date, mânere de fișiere, obiecte complexe).
4. Testează cazuri de margine și căi de eroare — intrări nule, containere goale, depășire a numărului întreg, eșec de alocare.
5. Păstrați testele rapid — testele unitare ar trebui să ruleze în câteva secunde. Mutați testele lente (rețea, disc) în suitele de teste de integrare.
6. Evaluare înainte de optimizare — măsurați mai întâi, apoi optimizați. Nu ghici unde sunt blocajele.
Testarea implementării, nu a comportamentului: testele care depind de structurile de date interne se întrerup atunci când refactorizați. Testați API-ul public și comportamentul observabil, nu metodele private.
Ignorarea descoperirilor de dezinfectant: rapoartele de dezinfectant NU sunt fals pozitive (cu excepții foarte rare). Fiecare raport ASan sau UBSan indică un comportament real nedefinit care VA cauza probleme la un anumit nivel de platformă sau de optimizare.
Nu se testează cu optimizări: Unele erori se manifestă doar cu-O2sau-O3(în special UB pe care optimizatorul îl exploatează). Rulați suita de teste atât cu versiunileDebug, cât și cuRelease.
Teste nerezolvate: testele care trec uneori și uneori eșuează sunt mai rele decât testele lipsite - erodează încrederea în suita de teste. Remediați sau ștergeți imediat testele scazute.
- GoogleTest oferă macrocomenzi
EXPECT_*(non-fatal) șiASSERT_*(fatal), dispozitive și teste parametrizate - Use
gtest_discover_tests()in CMake to auto-register tests with CTest - ASan catches memory errors, UBSan catches undefined behavior, TSan catches data races — run all in CI
- Static analysis (clang-tidy, cppcheck) catches bugs without running the code
- Testați comportamentul, nu implementarea - și testați întotdeauna cazurile marginale și căile de eroare
- Rulați teste atât cu versiunile Debug, cât și cu Release pentru a detecta erorile dependente de optimizare
Test — Verifică-ți cunoștințele
(15 XP)1. What is the difference between`EXPECT_EQ`and`ASSERT_EQ`in GoogleTest?
2. Which sanitizer detects use-after-free and buffer overflow bugs?
3. De ce ar trebui să rulați teste atât cu versiunile Debug, cât și cu Release?