مقدمة: لماذا الاختبار في C++ ليس ترفًا، بل ضرورة
التطوير بلغة C++ هو دائمًا توازن بين الأداء والموثوقية. أخطاء إدارة الذاكرة، السلوك غير المحدد (UB)، والتبعيات المعقدة بين الوحدات تجعل لغة C++ واحدة من أكثر اللغات تطلبًا من حيث جودة الكود. بدون منهجية منهجية للاختبار، يتحول المشروع بسرعة إلى "صندوق أسود"، حيث يؤدي إصلاح خطأ واحد إلى ظهور خطأين جديدين.
في هذه المقالة، سنقوم بتفصيل ثلاثة مستويات رئيسية للاختبار في لغة C++: اختبارات الوحدة، اختبارات التكامل، واختبارات الطرف إلى الطرف (e2e). ستتعرف على الأدوات التي يجب استخدامها، وكيفية كتابة اختبارات فعالة، وكيفية بناء هرم الاختبار لمشروع C++.
1. اختبار الوحدة: التحقق من كل لبنة
اختبارات الوحدة هي أساس هرم الاختبار. فهي تعزل وتتحقق من وحدة منطقية واحدة من الكود: دالة، أو طريقة من صنف، أو بنية صغيرة. في لغة C++، يجب أن تنفذ اختبارات الوحدة بسرعة (أقل من ثانية واحدة لمئات الاختبارات) ويجب ألا تعتمد على موارد خارجية (نظام الملفات، الشبكة، قواعد البيانات).
أدوات اختبار الوحدة في C++
- Google Test (gtest) — الإطار الأكثر شهرة من Google. يدعم وحدات التأكيد (assertion macros)، التجهيزات (fixtures)، والاختبارات المعلمية (parameterized tests).
- Catch2 — بديل حديث مع بناء جملة موجز ودعم لأسلوب BDD.
- Boost.Test — جزء من مكتبة Boost، أداة ناضجة مع إمكانيات واسعة.
مثال على اختبار وحدة في Google Test
لنفترض أن لدينا دالة لحساب المضروب (factorial):
// math_utils.h#pragma onceunsigned long long factorial(unsigned int n);// math_utils.cpp#include "math_utils.h"
unsigned long long factorial(unsigned int n) { if (n == 0) return 1; unsigned long long result = 1; for (unsigned int i = 1; i <= n; ++i) { result *= i; } return result;}نكتب اختبار وحدة:
// test_math_utils.cpp#include <gtest/gtest.h>#include "math_utils.h"
TEST(FactorialTest, HandlesZero) { EXPECT_EQ(factorial(0), 1);}
TEST(FactorialTest, HandlesPositiveNumbers) { EXPECT_EQ(factorial(1), 1); EXPECT_EQ(factorial(5), 120); EXPECT_EQ(factorial(10), 3628800);}
TEST(FactorialTest, HandlesLargeInput) { // 20! fits in unsigned long long EXPECT_EQ(factorial(20), 2432902008176640000ULL);}أفضل الممارسات لاختبار الوحدة في C++:
- اختبار واحد — فحص واحد. استخدم
EXPECT_EQللفحوصات المرنة وASSERT_EQللفحوصات الحرجة. - عزل التبعيات باستخدام الكائنات الوهمية (Google Mock).
- اختبار الحالات الحدية: القيم الصفرية، التجاوز (overflow)، الحاويات الفارغة.
- استخدم
TEST_Fللتهيئة المتكررة (التجهيزات).
2. اختبار التكامل: كيف تعمل الوحدات معًا
تتحقق اختبارات التكامل من التفاعل بين وحدات أو مكونات متعددة. في لغة C++، هذا مهم بشكل خاص لأن الأخطاء تحدث غالبًا عند الحدود: إدارة غير صحيحة للذاكرة عند تمرير الكائنات، عدم تطابق الواجهات، أو مشاكل تعدد الخيوط.
على عكس اختبارات الوحدة، يمكن لاختبارات التكامل العمل مع قواعد بيانات حقيقية، ملفات، أو مآخذ شبكة (sockets)، ولكن عادةً في بيئة اختبار (مثل الملفات المؤقتة أو حاويات Docker).
مثال على اختبار تكامل
لنفترض أن لدينا وحدة Logger ووحدة FileManager. نريد التحقق من أن السجلات تُكتب بشكل صحيح إلى ملف.
// file_logger.h#pragma once#include <string>
class FileLogger {public: FileLogger(const std::string& filename); vo