اختبار C++ المتقدم: من اختبارات الوحدة إلى النهاية إلى النهاية
في تطوير C++ الحديث، لا يتم تحديد جودة الكود من خلال أدائه فحسب، بل أيضًا من خلال موثوقيته. يتجاوز الاختبار المتقدم بكثير عمليات التحقق البسيطة - إنه نهج معماري يشمل اختبارات الوحدة واختبارات التكامل واختبارات النهاية إلى النهاية (e2e). في هذه المقالة، سنشرح كيفية بناء هرم الاختبار بشكل صحيح لمشاريع C++ باستخدام Google Test وGoogle Mock والأطر الخاصة.
1. اختبار الوحدة: العزل والـ mocks
تتحقق اختبارات الوحدة من الوحدات الفردية (الدوال، الفئات) في عزلة. على المستوى المتقدم، المهمة الرئيسية هي تعلم كيفية mock التبعيات بشكل صحيح. لنلق نظرة على مثال باستخدام Google Mock.
لنفترض أن لدينا فئة DatabaseConnector تعتمد على API خارجي. نريد اختبار منطق معالجة البيانات دون الاتصال بقاعدة البيانات الحقيقية.
#include <gmock/gmock.h>#include <gtest/gtest.h>
class DatabaseInterface {public: virtual ~DatabaseInterface() = default; virtual std::string fetchData(int id) = 0;};
class MockDatabase : public DatabaseInterface {public: MOCK_METHOD(std::string, fetchData, (int id), (override));};
class DataProcessor { DatabaseInterface& db;public: explicit DataProcessor(DatabaseInterface& db) : db(db) {} std::string process(int id) { auto raw = db.fetchData(id); if (raw.empty()) return "EMPTY"; return "PROCESSED: " + raw; }};
TEST(DataProcessorTest, ProcessWithValidData) { MockDatabase mockDb; EXPECT_CALL(mockDb, fetchData(42)) .WillOnce(testing::Return("Hello")); DataProcessor processor(mockDb); EXPECT_EQ(processor.process(42), "PROCESSED: Hello");}يسمح استخدام MOCK_METHOD بالتحكم الدقيق في سلوك التبعيات. من المهم تذكر strict mocks (عبر NiceMock أو StrictMock) لالتقاط الاستدعاءات غير المتوقعة.
2. اختبار التكامل: اختبار التفاعلات
تتحقق اختبارات التكامل من كيفية عمل الوحدات معًا. في C++، غالبًا ما يتضمن ذلك اختبار العمل مع نظام الملفات أو بروتوكولات الشبكة أو قواعد البيانات. النهج المتقدم هو استخدام test fixtures و parameterized tests.
مثال: نختبر فئة FileManager التي تقرأ وتكتب في الملفات. نستخدم أدلة مؤقتة.
class FileManagerTest : public ::testing::Test {protected: std::filesystem::path tempDir; void SetUp() override { tempDir = std::filesystem::temp_directory_path() / "test_files"; std::filesystem::create_directories(tempDir); } void TearDown() override { std::filesystem::remove_all(tempDir); }};
TEST_F(FileManagerTest, WriteAndReadFile) { FileManager fm(tempDir.string()); fm.write("test.txt", "data"); auto content = fm.read("test.txt"); EXPECT_EQ(content, "data");}للاختبارات المعلمية، نستخدم TEST_P مع INSTANTIATE_TEST_SUITE_P. يتيح ذلك تشغيل نفس الاختبار ببيانات إدخال مختلفة.
class MathTest : public ::testing::TestWithParam<std::tuple<int, int, int>> {};TEST_P(MathTest, Add) { auto [a, b, expected] = GetParam(); EXPECT_EQ(add(a, b), expected);}INSTANTIATE_TEST_SUITE_P(AddTests, MathTest, ::testing::Values( std::make_tuple(1, 1, 2), std::make_tuple(-1, 1, 0), std::make_tuple(0, 0, 0) ));3. اختبار النهاية إلى النهاية: التحقق من الدورة الكاملة
تحاكي اختبارات e2e سلوك المستخدم النهائي. بالنسبة لتطبيقات C++، قد يعني هذا تشغيل الملف الثنائي الكامل، والتحقق من مخرجاته، أو التفاعل عبر API. المستوى المتقدم