टेस्टिंग, बेंचमार्किंग और सैनिटाइज़र
GoogleTest से यूनिट टेस्ट लिखें, Google Benchmark से बेंचमार्क करें, सैनिटाइज़र (ASan, UBSan, TSan) से बग पकड़ें, और स्टैटिक एनालिसिस को अपनी CI/CD पाइपलाइन में जोड़ें।
C++ के लिए टेस्टिंग पिरामिड
प्रोफ़ेशनल C++ डेवलपमेंट गुणवत्ता आश्वासन की कई परतों पर निर्भर करता है: यूनिट टेस्ट अलग-अलग फ़ंक्शन और क्लास की जाँच करते हैं, इंटीग्रेशन टेस्ट घटकों की आपसी क्रिया जाँचते हैं, सैनिटाइज़र रनटाइम पर मेमोरी एरर और अपरिभाषित व्यवहार पकड़ते हैं, स्टैटिक एनालिसिस कोड चलाए बिना बग खोजता है, और बेंचमार्क सुनिश्चित करते हैं कि परफ़ॉर्मेंस ज़रूरतों को पूरा करती है। ये सब मिलकर एक सुरक्षा जाल बनाते हैं जो बग को प्रोडक्शन तक पहुँचने से पहले पकड़ लेता है।
दो सबसे लोकप्रिय C++ टेस्टिंग फ़्रेमवर्क हैं GoogleTest (Google का, इंडस्ट्री में व्यापक रूप से इस्तेमाल होता है) और Catch2 (BDD-शैली सिंटैक्स; इसका पुराना v2 सिंगल-हेडर था, जबकि v3 कंपाइल की जाने वाली लाइब्रेरी है)। दोनों CMake और CI/CD सिस्टम के साथ अच्छी तरह जुड़ते हैं।
GoogleTest से यूनिट टेस्टिंग
GoogleTest असर्शन के लिए मैक्रो (नॉन-फ़ेटल के लिए EXPECT_*, फ़ेटल के लिए ASSERT_*), साझा सेटअप/टियरडाउन के लिए टेस्ट फ़िक्स्चर और पैरामीटराइज़्ड टेस्ट देता है:
#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 के साथ टेस्ट इंटीग्रेट करना
CMake के साथ GoogleTest सेट करें और बिल्ट-इन टेस्ट रनर 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)सैनिटाइज़र: रनटाइम पर बग की पहचान
सैनिटाइज़र कंपाइलर द्वारा जोड़ी गई रनटाइम जाँचें हैं जो बगों की पूरी श्रेणियाँ पकड़ लेती हैं। इन्हें कंपाइलर फ़्लैग से चालू करें — ये लगभग 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 — clang-tidy का पूरक, यह अलग तरह के बग पकड़ता है:
cppcheck --enable=all --std=c++20 src/कोड कवरेज — मापें कि आपके टेस्ट वास्तव में कौन-सी पंक्तियाँ चलाते हैं:
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 में सैनिटाइज़र चलाएँ — ASan+UBSan और TSan के लिए अलग-अलग CI जॉब रखें। ये ऐसे बग पकड़ते हैं जो केवल टेस्ट से छूट जाते हैं।
3. साझा रिसोर्स (डेटाबेस कनेक्शन, फ़ाइल हैंडल, जटिल ऑब्जेक्ट) के सेटअप/टियरडाउन के लिए टेस्ट फ़िक्स्चर उपयोग करें।
4. एज केस और एरर पाथ टेस्ट करें — null इनपुट, खाली कंटेनर, इंटीजर ओवरफ़्लो, आवंटन की विफलता।
5. टेस्ट तेज़ रखें — यूनिट टेस्ट सेकंडों में चलने चाहिए। धीमे टेस्ट (नेटवर्क, डिस्क) को इंटीग्रेशन टेस्ट सूट में ले जाएँ।
6. ऑप्टिमाइज़ करने से पहले बेंचमार्क करें — पहले मापें, फिर ऑप्टिमाइज़ करें। अंदाज़ा न लगाएँ कि बॉटलनेक कहाँ हैं।
व्यवहार की जगह इम्प्लीमेंटेशन की टेस्टिंग: जो टेस्ट आंतरिक डेटा स्ट्रक्चर पर निर्भर होते हैं, वे रीफ़ैक्टर करने पर टूट जाते हैं। पब्लिक API और देखे जा सकने वाले व्यवहार को टेस्ट करें, प्राइवेट मेथड को नहीं।
सैनिटाइज़र के नतीजों को अनदेखा करना: सैनिटाइज़र रिपोर्ट फ़ॉल्स पॉज़िटिव नहीं होतीं (बहुत दुर्लभ अपवादों को छोड़कर)। हर ASan या UBSan रिपोर्ट असली अपरिभाषित व्यवहार दर्शाती है जो किसी न किसी प्लेटफ़ॉर्म या ऑप्टिमाइज़ेशन स्तर पर समस्या ज़रूर पैदा करेगा।
ऑप्टिमाइज़ेशन के साथ टेस्ट न करना: कुछ बग केवल -O2 या -O3 के साथ सामने आते हैं (ख़ासकर वह UB जिसका ऑप्टिमाइज़र फ़ायदा उठाता है)। अपना टेस्ट सूट Debug और Release दोनों बिल्ड के साथ चलाएँ।
फ़्लैकी टेस्ट: जो टेस्ट कभी पास और कभी फ़ेल होते हैं, वे बिना टेस्ट से भी बुरे हैं — वे टेस्ट सूट पर भरोसा घटाते हैं। फ़्लैकी टेस्ट को तुरंत ठीक करें या हटा दें।
- GoogleTest
EXPECT_*(नॉन-फ़ेटल) औरASSERT_*(फ़ेटल) मैक्रो, फ़िक्स्चर और पैरामीटराइज़्ड टेस्ट देता है - टेस्ट को CTest में अपने-आप रजिस्टर करने के लिए CMake में
gtest_discover_tests()का उपयोग करें - ASan मेमोरी एरर, UBSan अपरिभाषित व्यवहार और TSan डेटा रेस पकड़ता है — सबको CI में चलाएँ
- स्टैटिक एनालिसिस (clang-tidy, cppcheck) कोड चलाए बिना बग पकड़ता है
- व्यवहार को टेस्ट करें, इम्प्लीमेंटेशन को नहीं — और एज केस तथा एरर पाथ हमेशा टेस्ट करें
- ऑप्टिमाइज़ेशन पर निर्भर बग पकड़ने के लिए टेस्ट को Debug और Release दोनों बिल्ड के साथ चलाएँ
क्विज़ — अपना ज्ञान परखें
(15 XP)1. GoogleTest में `EXPECT_EQ` और `ASSERT_EQ` में क्या अंतर है?
2. कौन-सा सैनिटाइज़र use-after-free और बफ़र ओवरफ़्लो बग पकड़ता है?
3. आपको टेस्ट Debug और Release दोनों बिल्ड के साथ क्यों चलाने चाहिए?