الاختبار والقياس والمطهرات
اكتب اختبارات الوحدة باستخدام GoogleTest، وقياس الأداء باستخدام Google Benchmark، واكتشف الأخطاء باستخدام المطهرات (ASan، UBSan، TSan)، وقم بدمج التحليل الثابت في مسار CI/CD الخاص بك.
هرم الاختبار لـ C++
يعتمد تطوير C++ الاحترافي على طبقات متعددة من ضمان الجودة: اختبارات الوحدة التحقق من الوظائف والفئات الفردية، اختبارات التكامل التحقق من تفاعلات المكونات، المطهرات تلتقط أخطاء الذاكرة والسلوك غير المحدد في وقت التشغيل، التحليل الثابت يعثر على الأخطاء دون تشغيل التعليمات البرمجية، و المعايير تضمن أن الأداء يفي بالمتطلبات. وتشكل هذه الأدوات معًا شبكة أمان تكتشف الأخطاء قبل أن تصل إلى مرحلة الإنتاج.
إطارا اختبار C++ الأكثر شيوعًا هما GoogleTest (من Google، ويستخدم على نطاق واسع في الصناعة) وCatch2 (تركيب جملة على نمط BDD للرأس فقط). كلاهما يتكامل بشكل جيد مع أنظمة 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
قم بإعداد 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)المطهرات: الكشف عن الأخطاء في وقت التشغيل
المطهرات عبارة عن عمليات فحص وقت تشغيل يتم تنفيذها بواسطة برنامج التحويل البرمجي والتي تلتقط فئات كاملة من الأخطاء. قم بتمكينها باستخدام إشارات المترجم - فهي تضيف تباطؤًا يصل إلى 2x ولكنها تكتشف الأخطاء التي لا يمكن لأي قدر من الاختبارات اكتشافها بدونها:
# 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. قياس الأداء قبل التحسين — قم بالقياس أولاً، ثم قم بالتحسين. لا تخمن أين توجد الاختناقات.
اختبار التنفيذ، وليس السلوك: تنقطع الاختبارات التي تعتمد على بنيات البيانات الداخلية عند إعادة البناء. اختبار واجهة برمجة التطبيقات العامة والسلوك الملحوظ، وليس الطرق الخاصة.
تجاهل نتائج المطهر: تقارير المطهر ليست نتائج إيجابية كاذبة (مع استثناءات نادرة جدًا). يشير كل تقرير Asan أو UBSan إلى سلوك حقيقي غير محدد من شأنه أن يسبب مشاكل على بعض الأنظمة الأساسية أو مستويات التحسين.
عدم الاختبار باستخدام التحسينات : تظهر بعض الأخطاء فقط مع-O2أو-O3(خاصة UB الذي يستغله المحسن). قم بتشغيل مجموعة الاختبار الخاصة بك باستخدام إصداراتDebugوRelease.
الاختبارات غير المستقرة: الاختبارات التي تنجح أحيانًا وتفشل أحيانًا تكون أسوأ من عدم وجود اختبارات، فهي تؤدي إلى تآكل الثقة في مجموعة الاختبارات. قم بإصلاح أو حذف الاختبارات غير المستقرة على الفور.
- يوفر GoogleTest وحدات الماكرو والتركيبات والاختبارات ذات المعلمات
EXPECT_*(غير فادحة) وASSERT_*(فادحة) - استخدم
gtest_discover_tests()في CMake لتسجيل الاختبارات تلقائيًا باستخدام CTest - يرصد ASan أخطاء الذاكرة، ويكتشف UBSan سلوكًا غير محدد، ويكتشف Tsan سباقات البيانات - قم بتشغيل كل شيء في CI
- يقوم التحليل الثابت (clang-tidy، cppcheck) باكتشاف الأخطاء دون تشغيل التعليمات البرمجية
- اختبار السلوك، وليس التنفيذ - واختبر دائمًا حالات الحافة ومسارات الخطأ
- Run tests with both Debug and Release builds to catch optimization-dependent bugs
اختبار — اختبر معرفتك
(15 نقطة خبرة)1. What is the difference between`EXPECT_EQ`and`ASSERT_EQ`in GoogleTest?
2. ما هو المطهر الذي يكتشف أخطاء الاستخدام بعد الاستخدام المجاني وتجاوز سعة المخزن المؤقت؟
3. لماذا يجب عليك إجراء الاختبارات باستخدام كل من إصداري Debug وRelease؟