Testing, Benchmarking & Sanitizers
Γράψτε δοκιμές μονάδας με το GoogleTest, συγκριθείτε με το Google Benchmark, εντοπίστε σφάλματα με απολυμαντικά (ASan, UBSan, TSan) και ενσωματώστε τη στατική ανάλυση στη διοχέτευση CI/CD.
The Testing Pyramid for C++
Professional C++ development relies on multiple layers of quality assurance: unit tests verify individual functions and classes, integration tests check component interactions, sanitizers catch memory errors and undefined behavior at runtime, static analysis finds bugs without running the code, and benchmarks ensure performance meets requirements. Together, these tools form a safety net that catches bugs before they reach production.
The two most popular C++ testing frameworks are GoogleTest (from Google, widely used in industry) and Catch2 (header-only, BDD-style syntax). Both integrate well with CMake and CI/CD systems.
Δοκιμή μονάδας με το GoogleTest
GoogleTest provides macros for assertions (EXPECT_*for non-fatal,ASSERT_*for fatal), test fixtures for shared setup/teardown, and parameterized tests:
#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
}Ενσωμάτωση δοκιμών με το CMake
Ρυθμίστε το GoogleTest με το CMake και εκτελέστε δοκιμές μέσω του CTest, του ενσωματωμένου προγράμματος εκτέλεσης δοκιμών:
# 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)Απολυμαντικά: Ανίχνευση σφαλμάτων χρόνου εκτέλεσης
Τα Sanitizers είναι έλεγχοι χρόνου εκτέλεσης με όργανα μεταγλωττιστή που εντοπίζουν ολόκληρες κατηγορίες σφαλμάτων. Ενεργοποιήστε τα με σημαίες μεταγλωττιστή — προσθέτουν ~ 2 φορές επιβράδυνση, αλλά εντοπίζουν σφάλματα που κανένας αριθμός δοκιμών δεν μπορεί να βρει χωρίς αυτά:
# 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)Στατική Ανάλυση & Κάλυψη Κώδικα
Η στατική ανάλυση εξετάζει τον κώδικά σας χωρίς να τον εκτελεί, εντοπίζει σφάλματα, παραβιάσεις στυλ και ζητήματα ασφαλείας:
clang-tidy — Η πιο ισχυρή επένδυση C++. Έλεγχοι για ευκαιρίες εκσυγχρονισμού, ζητήματα απόδοσης, αναγνωσιμότητα και μοτίβα επιρρεπή σε σφάλματα:
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 ../srcΣτοχεύστε σε 80%+ κάλυψη γραμμής σε κρίσιμες διαδρομές κώδικα. Η κάλυψη 100% σπάνια αξίζει τον κόπο - επικεντρωθείτε στη λογική δοκιμής, τις περιπτώσεις αιχμής και τον χειρισμό σφαλμάτων.
Δημιουργήστε μια ολοκληρωμένη στρατηγική δοκιμών:
1. Γράψτε δοκιμές μαζί με τον κώδικα — όχι μετά. Η ανάπτυξη βάσει δοκιμής (TDD) λειτουργεί καλά για το C++: γράψτε μια δοκιμή που αποτυγχάνει, εφαρμόστε τον ελάχιστο κωδικό που πρέπει να περάσετε, επαναλάβετε.
2. Εκτελέστε απολυμαντικά σε CI — έχετε ξεχωριστές εργασίες CI για ASan+UBSan και TSan. Αυτά πιάνουν σφάλματα που χάνουν μόνο οι δοκιμές.
3. Χρησιμοποιήστε δοκιμαστικές συσκευές για ρύθμιση/απομάκρυνση κοινόχρηστων πόρων (συνδέσεις βάσεων δεδομένων, λαβές αρχείων, σύνθετα αντικείμενα).
4. Περιπτώσεις άκρων δοκιμής και διαδρομές σφάλματος — μηδενικές είσοδοι, άδεια δοχεία, υπερχείλιση ακεραίων, αποτυχία κατανομής.
5. Διατηρήστε τις δοκιμές γρήγορα — οι δοκιμές μονάδας θα πρέπει να εκτελούνται σε δευτερόλεπτα. Μετακινήστε αργά τεστ (δίκτυο, δίσκος) σε σουίτες δοκιμών ενοποίησης.
6. Συγκριτική αξιολόγηση πριν από τη βελτιστοποίηση — πρώτα μετρήστε και μετά βελτιστοποιήστε. Μην μαντεύετε πού βρίσκονται τα σημεία συμφόρησης.
Δοκιμαστική υλοποίηση, όχι συμπεριφορά: Οι δοκιμές που εξαρτώνται από εσωτερικές δομές δεδομένων διακόπτονται όταν κάνετε αναπαράσταση. Δοκιμάστε το δημόσιο API και την παρατηρήσιμη συμπεριφορά, όχι ιδιωτικές μεθόδους.
Παράβλεψη ευρημάτων απολυμαντικών: Οι αναφορές απολυμαντικών ΔΕΝ είναι ψευδώς θετικά (με πολύ σπάνιες εξαιρέσεις). Κάθε αναφορά ASan ή UBSan υποδεικνύει πραγματική απροσδιόριστη συμπεριφορά που ΘΑ προκαλέσει προβλήματα σε κάποια πλατφόρμα ή επίπεδο βελτιστοποίησης.
Δεν δοκιμάζεται με βελτιστοποιήσεις: Ορισμένα σφάλματα εμφανίζονται μόνο με το-O2ή το-O3(ειδικά το UB που εκμεταλλεύεται ο βελτιστοποιητής). Εκτελέστε τη δοκιμαστική σουίτα με εκδόσειςDebugκαιRelease.
Δοκιμαστικές δοκιμές: Οι δοκιμές που μερικές φορές περνούν και μερικές φορές αποτυγχάνουν είναι χειρότερες από τις μη δοκιμές — διαβρώνουν την εμπιστοσύνη στη σουίτα δοκιμών. Διορθώστε ή διαγράψτε αμέσως τις δοκιμές που ξεφλουδίζουν.
- Το GoogleTest παρέχει μακροεντολές
EXPECT_*(μη θανατηφόρο) καιASSERT_*(θανατηφόρο), φωτιστικά και παραμετροποιημένες δοκιμές - Χρησιμοποιήστε το
gtest_discover_tests()στο CMake για αυτόματη εγγραφή δοκιμών με το CTest - Το ASan εντοπίζει σφάλματα μνήμης, το UBSan εντοπίζει απροσδιόριστη συμπεριφορά, το TSan καταγράφει αγώνες δεδομένων — εκτελούνται όλα σε CI
- Η στατική ανάλυση (clang-tidy, cppcheck) εντοπίζει σφάλματα χωρίς την εκτέλεση του κώδικα
- Δοκιμή συμπεριφοράς, όχι υλοποίησης — και πάντα δοκιμάζετε περιπτώσεις αιχμής και διαδρομές σφαλμάτων
- Εκτελέστε δοκιμές με εκδόσεις εντοπισμού σφαλμάτων και έκδοσης για να εντοπίσετε σφάλματα που εξαρτώνται από τη βελτιστοποίηση
Κουίζ — Δοκιμάστε τις γνώσεις σας
(15 XP)1. Ποια είναι η διαφορά μεταξύ`EXPECT_EQ`και`ASSERT_EQ`στο GoogleTest;
2. Ποιο απολυμαντικό εντοπίζει σφάλματα χωρίς χρήση και υπερχείλισης buffer;
3. Γιατί πρέπει να εκτελείτε δοκιμές τόσο με εκδόσεις εντοπισμού σφαλμάτων όσο και με έκδοση έκδοσης;