<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>TDD on kenji.blog</title><link>http://kenji.blog/ar/tags/tdd/</link><description>Recent content in TDD on kenji.blog</description><generator>Hugo -- gohugo.io</generator><language>ar</language><copyright>kenjinote</copyright><lastBuildDate>Sat, 12 Sep 2026 21:00:00 +0900</lastBuildDate><atom:link href="http://kenji.blog/ar/tags/tdd/index.xml" rel="self" type="application/rss+xml"/><item><title>كيفية تقديم التطوير الموجه بالاختبار (TDD) إلى مشاريع C++ (مثل GoogleTest)</title><link>http://kenji.blog/ar/p/tdd-cpp-googletest-introduction/</link><pubDate>Sat, 12 Sep 2026 21:00:00 +0900</pubDate><guid>http://kenji.blog/ar/p/tdd-cpp-googletest-introduction/</guid><description>&lt;img src="http://kenji.blog/p/tdd-cpp-googletest-introduction/img/eyecatch.jpg" alt="Featured image of post كيفية تقديم التطوير الموجه بالاختبار (TDD) إلى مشاريع C++ (مثل GoogleTest)" />&lt;p>في تطوير البرمجيات الحديثة، الحفاظ على جودة الكود مع إضافة الميزات بسرعة هو هدف أسمى. خصوصاً في لغات معقدة وتتطلب أداءً عالياً مثل C++، الأخطاء في إدارة الذاكرة والسلوكيات غير المحددة (Undefined Behavior) يمكن أن تؤدي بسهولة إلى أعطال فادحة، مما يجعل أهمية الاختبار أعلى من اللغات الأخرى.&lt;/p>
&lt;p>في هذه المقالة، سنشرح بشكل مفصل وعملي جداً كيفية تقديم &lt;strong>التطوير الموجه بالاختبار (Test-Driven Development: TDD)&lt;/strong> في مشاريع C++. سنغطي بشكل شامل كيفية استخدام إطار عمل اختبار الوحدة &lt;strong>GoogleTest&lt;/strong> وإطار عمل الـ Mock &lt;strong>GoogleMock&lt;/strong>، بالإضافة إلى طريقة التكوين الحديثة باستخدام نظام البناء &lt;strong>CMake&lt;/strong>، وطريقة قياس تغطية الكود (Code Coverage).&lt;/p>
&lt;h2 id="1-فلسفة-التطوير-الموجه-بالاختبار-tdd-وفوائده">1. فلسفة التطوير الموجه بالاختبار (TDD) وفوائده
&lt;/h2>&lt;p>التطوير الموجه بالاختبار (TDD) هو منهجية تطوير برمجيات تقوم على &amp;ldquo;كتابة الاختبار قبل كتابة التنفيذ&amp;rdquo;. إنها ليست مجرد منهجية اختبار، بل تعمل أيضاً كـ &lt;strong>منهجية تصميم&lt;/strong>. من خلال كتابة الاختبارات أولاً، يصبح المطورون واعين بشكل طبيعي بـ &amp;ldquo;الواجهات سهلة الاستخدام&amp;rdquo; و &amp;ldquo;التصميم غير المترابط (Loosely coupled)&amp;rdquo;.&lt;/p>
&lt;h3 id="11-دورة-red-green-refactor">1.1 دورة Red-Green-Refactor
&lt;/h3>&lt;p>يكمن جوهر TDD في دورة &amp;ldquo;Red-Green-Refactor&amp;rdquo; التالية.&lt;/p>
&lt;pre class="mermaid">
flowchart TD
Start[&amp;#34;بدء التطوير&amp;#34;] --&amp;gt; Red[&amp;#34;Red: كتابة اختبار يفشل&amp;#34;]
Red --&amp;gt; Green[&amp;#34;Green: كتابة الحد الأدنى من التنفيذ لاجتياز الاختبار&amp;#34;]
Green --&amp;gt; Refactor[&amp;#34;Refactor: تحسين الكود (إعادة الهيكلة)&amp;#34;]
Refactor --&amp;gt; Red
Refactor -.-&amp;gt; End[&amp;#34;اكتمال الميزة&amp;#34;]
&lt;/pre>
&lt;ol>
&lt;li>&lt;strong>Red (أحمر)&lt;/strong>: في حالة عدم وجود تنفيذ، يتم كتابة اختبار يحدد السلوك المتوقع. نظراً لعدم وجود تنفيذ في هذه المرحلة، سيفشل الاختبار بالضرورة (Red).&lt;/li>
&lt;li>&lt;strong>Green (أخضر)&lt;/strong>: يتم كتابة الحد الأدنى من الكود فقط لجعل الاختبار ينجح (Green). في هذه المرحلة، لا يتم إعطاء الأولوية لجمال الكود أو الأداء.&lt;/li>
&lt;li>&lt;strong>Refactor (إعادة الهيكلة)&lt;/strong>: مع الحفاظ على حالة نجاح الاختبار، يتم التخلص من التكرار وتحسين تصميم الكود. وجود الاختبارات يسمح بتعديل الكود بأمان.&lt;/li>
&lt;/ol>
&lt;h3 id="12-زيادة-التكلفة-بسبب-تأخر-اكتشاف-الأخطاء">1.2 زيادة التكلفة بسبب تأخر اكتشاف الأخطاء
&lt;/h3>&lt;p>في هندسة البرمجيات، من المعروف أنه كلما تم اكتشاف الخطأ في مرحلة متأخرة من عملية التطوير، زادت تكلفة إصلاحه بشكل أُسّي. يمكن تقريب هذا النموذج لزيادة التكلفة باستخدام المعادلة الرياضية التالية.&lt;/p>
$$ Cost(t) = C_0 \times e^{k \cdot t} $$&lt;p>حيث $Cost(t)$ هي تكلفة الإصلاح في الوقت $t$، و $C_0$ هي تكلفة الإصلاح فور إدخال الخطأ (خط الأساس)، و $k$ هو ثابت. من خلال تقديم TDD، يمكن الحفاظ على $t$ عند الحد الأدنى ومنع الزيادة الأُسّية في التكلفة بشكل استباقي.&lt;/p>
&lt;h2 id="2-اختيار-أدوات-الاختبار-في-c-وتكوين-cmake-الحديث">2. اختيار أدوات الاختبار في C++ وتكوين CMake الحديث
&lt;/h2>&lt;p>يوجد العديد من أطر عمل الاختبار في C++. هناك Catch2، Boost.Test، doctest وغيرها، ولكن المعيار الصناعي الأكثر استخداماً على نطاق واسع هو &lt;strong>GoogleTest (gtest)&lt;/strong>. يتميز GoogleTest بتأكيداته الغنية (assertions)، وإطار عمل Mock قوي (GoogleMock)، وقابلية التوسعة العالية.&lt;/p>
&lt;h3 id="21-إدخال-googletest-باستخدام-fetchcontent-في-cmake">2.1 إدخال GoogleTest باستخدام &lt;code>FetchContent&lt;/code> في CMake
&lt;/h3>&lt;p>في تطوير C++ الحديث، أصبحت إدارة التبعيات الخارجية باستخدام وحدة &lt;code>FetchContent&lt;/code> الخاصة بـ CMake هي الطريقة السائدة. هذا يوفر عناء إدارة الوحدات الفرعية (submodules) أو تثبيت المكتبات مسبقاً.&lt;/p>
&lt;p>يتم كتابة &lt;code>CMakeLists.txt&lt;/code> في جذر المشروع على النحو التالي:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;span class="lnt">37
&lt;/span>&lt;span class="lnt">38
&lt;/span>&lt;span class="lnt">39
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-cmake" data-lang="cmake">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cmake_minimum_required&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">VERSION&lt;/span> &lt;span class="s">3.14&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">project&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">TddCppExample&lt;/span> &lt;span class="s">CXX&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># تحديد معيار C++
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">&lt;/span>&lt;span class="nb">set&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">CMAKE_CXX_STANDARD&lt;/span> &lt;span class="s">17&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">set&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">CMAKE_CXX_STANDARD_REQUIRED&lt;/span> &lt;span class="s">ON&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># جعل كود الإنتاج كمكتبة
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">&lt;/span>&lt;span class="nb">add_library&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">core_lib&lt;/span> &lt;span class="s">src/Calculator.cpp&lt;/span> &lt;span class="s">src/StringUtils.cpp&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">target_include_directories&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">core_lib&lt;/span> &lt;span class="s">PUBLIC&lt;/span> &lt;span class="s">include&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># تفعيل الاختبارات
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">&lt;/span>&lt;span class="nb">enable_testing&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># الحصول على GoogleTest
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">&lt;/span>&lt;span class="nb">include&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">FetchContent&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">FetchContent_Declare&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">googletest&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">URL&lt;/span> &lt;span class="s">https://github.com/google/googletest/archive/refs/tags/v1.14.0.zip&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># لتجنب تحذيرات البناء في بيئة Windows
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">&lt;/span>&lt;span class="nb">set&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">gtest_force_shared_crt&lt;/span> &lt;span class="s">ON&lt;/span> &lt;span class="s">CACHE&lt;/span> &lt;span class="s">BOOL&lt;/span> &lt;span class="s2">&amp;#34;&amp;#34;&lt;/span> &lt;span class="s">FORCE&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">FetchContent_MakeAvailable&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">googletest&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># إعداد ملف تشغيل الاختبار
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">&lt;/span>&lt;span class="nb">add_executable&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">unit_tests&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">tests/CalculatorTest.cpp&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">tests/StringUtilsTest.cpp&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">target_link_libraries&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">unit_tests&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">PRIVATE&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">core_lib&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">gtest_main&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">gmock&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># التسجيل في CTest
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">&lt;/span>&lt;span class="nb">include&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">GoogleTest&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">gtest_discover_tests&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">unit_tests&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>بفضل هذا الإعداد، سيقوم CMake تلقائياً بتنزيل كود المصدر لـ GoogleTest ودمجه في المشروع.&lt;/p>
&lt;h2 id="3-التطبيق-العملي-دورة-red-green-refactor-باستخدام-googletest">3. التطبيق العملي: دورة Red-Green-Refactor باستخدام GoogleTest
&lt;/h2>&lt;p>من هنا، دعونا نطبق دورة TDD عملياً باستخدام فئة &lt;code>Calculator&lt;/code> بسيطة كمثال.&lt;/p>
&lt;h3 id="31-المرحلة-1-red-كتابة-اختبار-يفشل">3.1 المرحلة 1: Red (كتابة اختبار يفشل)
&lt;/h3>&lt;p>أولاً، نكتب هيكل ملف الترويسة &lt;code>include/Calculator.h&lt;/code> وكود الاختبار.&lt;/p>
&lt;p>&lt;strong>include/Calculator.h (هيكل)&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#pragma once
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">Calculator&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">public&lt;/span>&lt;span class="o">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">int&lt;/span> &lt;span class="n">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="n">a&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">b&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;strong>tests/CalculatorTest.cpp (كود الاختبار)&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;gtest/gtest.h&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;#34;Calculator.h&amp;#34;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">TEST&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">CalculatorTest&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">AddsTwoPositiveNumbers&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Calculator&lt;/span> &lt;span class="n">calc&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">int&lt;/span> &lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">calc&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">EXPECT_EQ&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">result&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">5&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>إذا حاولنا البناء في هذه المرحلة، فسنواجه خطأ في الربط (link error) بسبب عدم وجود تنفيذ لـ &lt;code>Calculator::Add&lt;/code>، أو ستكون الحالة هي تشغيل الاختبار وفشله (Red).&lt;/p>
&lt;h3 id="32-المرحلة-2-green-الحد-الأدنى-من-التنفيذ">3.2 المرحلة 2: Green (الحد الأدنى من التنفيذ)
&lt;/h3>&lt;p>نكتب الكود فقط لاجتياز الاختبار.&lt;/p>
&lt;p>&lt;strong>src/Calculator.cpp&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;#34;Calculator.h&amp;#34;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">int&lt;/span> &lt;span class="n">Calculator&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="n">a&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">b&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">a&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">b&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// الحد الأدنى من التنفيذ لاجتياز الاختبار
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>عند البناء وتشغيل الاختبار الآن، سينجح الاختبار (Green).&lt;/p>
&lt;h3 id="33-المرحلة-3-refactor-إعادة-الهيكلة">3.3 المرحلة 3: Refactor (إعادة الهيكلة)
&lt;/h3>&lt;p>في هذا المثال، الكود بسيط جداً، ولكن مع زيادة تعقيد المتطلبات، نقوم بزيادة قابلية قراءة الكود أو تحسين الأداء في مرحلة إعادة الهيكلة. كود الاختبار نفسه يخضع أيضاً لإعادة الهيكلة. على سبيل المثال، يمكن النظر في تقديم تركيبات الاختبار (&lt;code>testing::Test&lt;/code> - Test Fixtures) لتوحيد عملية الإعداد (setup).&lt;/p>
&lt;h2 id="4-الفرق-بين-expect_eq-و-assert_eq">4. الفرق بين &lt;code>EXPECT_EQ&lt;/code> و &lt;code>ASSERT_EQ&lt;/code>
&lt;/h2>&lt;p>عند استخدام GoogleTest، يوجد نوعان من وحدات الماكرو للتأكيدات (assertions) وهما &lt;code>EXPECT_*&lt;/code> و &lt;code>ASSERT_*&lt;/code>. من المهم جداً فهم الفرق بينهما لكتابة اختبارات متينة.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>&lt;code>EXPECT_EQ(expected, actual)&lt;/code>&lt;/strong>: حتى إذا فشل الاختبار، فإنه &lt;strong>يستمر&lt;/strong> في تنفيذ دالة الاختبار الحالية. هذا مناسب عندما تريد التحقق من حالات متعددة داخل اختبار واحد.&lt;/li>
&lt;li>&lt;strong>&lt;code>ASSERT_EQ(expected, actual)&lt;/code>&lt;/strong>: إذا فشل الاختبار، فإنه &lt;strong>يوقف (فشل ذريع)&lt;/strong> تنفيذ دالة الاختبار الحالية على الفور. يتم استخدامه عندما لا يكون للتحققات اللاحقة أي معنى (مثال: إلغاء مرجعية مؤشر - dereferencing - مباشرة بعد التحقق من أنه ليس &lt;code>nullptr&lt;/code>).&lt;/li>
&lt;/ul>
&lt;h2 id="5-حقن-التبعية-di-و-mocking-باستخدام-googlemock">5. حقن التبعية (DI) و Mocking باستخدام GoogleMock
&lt;/h2>&lt;p>في مشاريع C++ الفعلية، تحدث التبعيات للأنظمة الخارجية مثل الوصول إلى قواعد البيانات، أو الاتصالات الشبكية، أو التحكم في الأجهزة بشكل حتمي. إذا تُركت هذه التبعيات كما هي، فإن اختبار الوحدة يصبح صعباً للغاية.&lt;/p>
&lt;p>هنا يأتي دور &lt;strong>حقن التبعية (Dependency Injection: DI)&lt;/strong> وإنشاء Mock للواجهات باستخدام &lt;strong>GoogleMock&lt;/strong>.&lt;/p>
&lt;pre class="mermaid">
flowchart LR
Test[&amp;#34;اختبار الوحدة&amp;#34;] --&amp;gt;|يحقن Mock| Target[&amp;#34;الخدمة المستهدفة&amp;#34;]
Target --&amp;gt;|تعتمد على| Interface[&amp;#34;الواجهة (IUserRepository)&amp;#34;]
Mock[&amp;#34;MockUserRepository&amp;#34;] -.-&amp;gt;|ينفذ| Interface
Test --&amp;gt;|يهيئ| Mock
&lt;/pre>
&lt;h3 id="51-تعريف-الواجهة-وتنفيذ-الفئة-المستهدفة">5.1 تعريف الواجهة وتنفيذ الفئة المستهدفة
&lt;/h3>&lt;p>أولاً، نحدد واجهة (فئة تحتوي على دوال افتراضية بحتة - pure virtual functions) تجرد المكونات التي يتم الاعتماد عليها.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;span class="lnt">9
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// include/IUserRepository.h
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="cp">#pragma once
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;string&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">IUserRepository&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">public&lt;/span>&lt;span class="o">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">virtual&lt;/span> &lt;span class="o">~&lt;/span>&lt;span class="n">IUserRepository&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">default&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">virtual&lt;/span> &lt;span class="kt">bool&lt;/span> &lt;span class="nf">SaveUser&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="n">id&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="k">const&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">string&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">name&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>بعد ذلك، ننشئ فئة خدمة (الهدف من الاختبار) تعتمد على هذه الواجهة. يتم حقن التبعية عن طريق المنشئ (Constructor Injection).&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// include/UserService.h
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="cp">#pragma once
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;#34;IUserRepository.h&amp;#34;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;string&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">UserService&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">private&lt;/span>&lt;span class="o">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">IUserRepository&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">repository_&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">public&lt;/span>&lt;span class="o">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">UserService&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">IUserRepository&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">repository&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">:&lt;/span> &lt;span class="n">repository_&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">repository&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">bool&lt;/span> &lt;span class="nf">RegisterUser&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="n">id&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="k">const&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">string&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">name&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">empty&lt;/span>&lt;span class="p">())&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="nb">false&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">repository_&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">SaveUser&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">name&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="52-إنشاء-فئة-mock-والاختبار-باستخدام-googlemock">5.2 إنشاء فئة Mock والاختبار باستخدام GoogleMock
&lt;/h3>&lt;p>نستخدم ماكرو &lt;code>MOCK_METHOD&lt;/code> الخاص بـ GoogleMock لعمل Mock للواجهة.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;span class="lnt">37
&lt;/span>&lt;span class="lnt">38
&lt;/span>&lt;span class="lnt">39
&lt;/span>&lt;span class="lnt">40
&lt;/span>&lt;span class="lnt">41
&lt;/span>&lt;span class="lnt">42
&lt;/span>&lt;span class="lnt">43
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// tests/UserServiceTest.cpp
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;gtest/gtest.h&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;gmock/gmock.h&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;#34;UserService.h&amp;#34;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;#34;IUserRepository.h&amp;#34;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="o">::&lt;/span>&lt;span class="n">testing&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">Return&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="o">::&lt;/span>&lt;span class="n">testing&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">_&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// تعريف فئة Mock
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">class&lt;/span> &lt;span class="nc">MockUserRepository&lt;/span> &lt;span class="o">:&lt;/span> &lt;span class="k">public&lt;/span> &lt;span class="n">IUserRepository&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">public&lt;/span>&lt;span class="o">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">MOCK_METHOD&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">bool&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">SaveUser&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="n">id&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="k">const&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">string&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">name&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="k">override&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">TEST&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">UserServiceTest&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">RegistersValidUserSuccessfully&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">MockUserRepository&lt;/span> &lt;span class="n">mockRepo&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">UserService&lt;/span> &lt;span class="nf">service&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">mockRepo&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// إعداد القيم المتوقعة: نتوقع أن يتم استدعاء SaveUser مرة واحدة بـ (1, &amp;#34;Kenji&amp;#34;) وأن يرجع true
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">EXPECT_CALL&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">mockRepo&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">SaveUser&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;Kenji&amp;#34;&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">Times&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">WillOnce&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Return&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">true&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// تنفيذ الهدف المراد اختباره
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kt">bool&lt;/span> &lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">service&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">RegisterUser&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;Kenji&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// التأكيد (Assertion)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">EXPECT_TRUE&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">result&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">TEST&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">UserServiceTest&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">RejectsEmptyNameWithoutCallingRepository&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">MockUserRepository&lt;/span> &lt;span class="n">mockRepo&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">UserService&lt;/span> &lt;span class="nf">service&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">mockRepo&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// نتوقع ألا يتم استدعاء SaveUser إطلاقاً في حالة الاسم الفارغ
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">EXPECT_CALL&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">mockRepo&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">SaveUser&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">_&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">_&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">Times&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">bool&lt;/span> &lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">service&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">RegisterUser&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">EXPECT_FALSE&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">result&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>باستخدام GoogleMock بهذه الطريقة، يمكننا التحقق بدقة من &amp;ldquo;ما إذا كانت الفئة المستهدفة تتفاعل بشكل صحيح مع تبعياتها (التفاعل)&amp;rdquo;.&lt;/p>
&lt;h2 id="6-قياس-وتصور-تغطية-الكود">6. قياس وتصور تغطية الكود
&lt;/h2>&lt;p>بعد كتابة الاختبارات، لتقييم الجزء الذي يتم تنفيذه (تغطيته) من المشروع بواسطة الاختبارات بشكل موضوعي، نقوم بقياس &lt;strong>تغطية الكود (Code Coverage)&lt;/strong>. يتم التعبير عن تغطية الكود ($Coverage$) بالمعادلة التالية:&lt;/p>
$$ Coverage = \left( \frac{L_{executed}}{L_{total}} \right) \times 100 \ (\%) $$&lt;p>حيث $L_{executed}$ هو عدد أسطر الكود التي تم تنفيذها أثناء الاختبار، و $L_{total}$ هو إجمالي عدد أسطر الكود للمشروع بأكمله.&lt;/p>
&lt;p>إذا كنت تستخدم GCC أو Clang، فيمكنك استخدام أداتي &lt;code>gcov&lt;/code> و &lt;code>lcov&lt;/code> لقياس التغطية.&lt;/p>
&lt;h3 id="61-إضافة-خيارات-التغطية-إلى-cmake">6.1 إضافة خيارات التغطية إلى CMake
&lt;/h3>&lt;p>لقياس التغطية، تحتاج إلى علامات (flags) مترجم مخصصة. أضف الإعدادات التالية إلى &lt;code>CMakeLists.txt&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-cmake" data-lang="cmake">&lt;span class="line">&lt;span class="cl">&lt;span class="c"># خيارات بناء التغطية
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">&lt;/span>&lt;span class="nb">option&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">ENABLE_COVERAGE&lt;/span> &lt;span class="s2">&amp;#34;Enable coverage reporting&amp;#34;&lt;/span> &lt;span class="s">OFF&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">if&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">ENABLE_COVERAGE&lt;/span> &lt;span class="s">AND&lt;/span> &lt;span class="s">CMAKE_CXX_COMPILER_ID&lt;/span> &lt;span class="s">MATCHES&lt;/span> &lt;span class="s2">&amp;#34;GNU|Clang&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span> &lt;span class="nb">message&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">STATUS&lt;/span> &lt;span class="s2">&amp;#34;Coverage enabled&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span> &lt;span class="nb">target_compile_options&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">core_lib&lt;/span> &lt;span class="s">PRIVATE&lt;/span> &lt;span class="s">--coverage&lt;/span> &lt;span class="s">-O0&lt;/span> &lt;span class="s">-g&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span> &lt;span class="nb">target_link_options&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">core_lib&lt;/span> &lt;span class="s">PRIVATE&lt;/span> &lt;span class="s">--coverage&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span> &lt;span class="nb">target_compile_options&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">unit_tests&lt;/span> &lt;span class="s">PRIVATE&lt;/span> &lt;span class="s">--coverage&lt;/span> &lt;span class="s">-O0&lt;/span> &lt;span class="s">-g&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span> &lt;span class="nb">target_link_options&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">unit_tests&lt;/span> &lt;span class="s">PRIVATE&lt;/span> &lt;span class="s">--coverage&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">endif&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="62-خطوات-إنشاء-تقرير-التغطية">6.2 خطوات إنشاء تقرير التغطية
&lt;/h3>&lt;p>قم بتفعيل العلامة عند البناء، وبعد تشغيل الاختبارات، استخدم &lt;code>lcov&lt;/code> لإخراج تقرير HTML.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 1. البناء مع تفعيل خيارات التغطية&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mkdir build &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="nb">cd&lt;/span> build
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cmake .. -DENABLE_COVERAGE&lt;span class="o">=&lt;/span>ON
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">make
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 2. تشغيل الاختبارات&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ctest
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 3. جمع بيانات التغطية (تشغيل lcov)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">lcov --capture --directory . --output-file coverage.info
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 4. استبعاد ترويسات النظام والمكتبات الخارجية (مثل GoogleTest وما إلى ذلك)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">lcov --remove coverage.info &lt;span class="s1">&amp;#39;/usr/*&amp;#39;&lt;/span> &lt;span class="s1">&amp;#39;*/_deps/*&amp;#39;&lt;/span> &lt;span class="s1">&amp;#39;*/tests/*&amp;#39;&lt;/span> --output-file coverage.info
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 5. إنشاء تقرير HTML&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">genhtml coverage.info --output-directory coverage_report
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>من خلال فتح &lt;code>coverage_report/index.html&lt;/code> المُنشأ في المتصفح، سيتم إبراز الأسطر التي تم تنفيذها بصرياً باللونين الأخضر والأحمر على مستوى كود المصدر، مما يساعد في تحديد الاختبارات الناقصة (تحديد فجوات التغطية).&lt;/p>
&lt;h2 id="7-تحديات-tdd-في-مشاريع-c-وأفضل-الممارسات">7. تحديات TDD في مشاريع C++ وأفضل الممارسات
&lt;/h2>&lt;p>عند تقديم TDD في مشاريع C++، توجد تحديات محددة.&lt;/p>
&lt;h3 id="71-زيادة-وقت-البناء-وقت-الترجمة---compile-time">7.1 زيادة وقت البناء (وقت الترجمة - Compile Time)
&lt;/h3>&lt;p>تميل لغة C++ إلى استغراق أوقات ترجمة أطول بسبب الاستخدام المكثف للقوالب (templates) وتضمين الترويسات الكبيرة (headers). نظراً لأن دورة &amp;ldquo;Red-Green-Refactor&amp;rdquo; في TDD يجب أن تتم بسرعة، فإن التأخير في وقت البناء يعد أمراً قاتلاً.
&lt;strong>الحل&lt;/strong>: استخدم الإعلانات الأمامية (Forward Declaration) ومصطلح Pimpl (Pointer to implementation) لتقليل تبعيات ملفات الترويسة إلى الحد الأدنى. بالإضافة إلى ذلك، فإن تقديم أدوات التخزين المؤقت للبناء مثل Ccache فعال جداً.&lt;/p>
&lt;h3 id="72-إدخال-tdd-في-الكود-القديم-legacy-code">7.2 إدخال TDD في الكود القديم (Legacy Code)
&lt;/h3>&lt;p>من الصعب جداً تطبيق TDD لاحقاً على كود أحادي متآلف (Monolithic) ضخم موجود مسبقاً.
&lt;strong>الحل&lt;/strong>: بدلاً من إعادة كتابة كل شيء من البداية، يُنصح بإضافة الاختبارات تدريجياً بدءاً من الأجزاء التي تتم فيها إضافة ميزات جديدة أو الأجزاء التي يتم فيها إصلاح الأخطاء (قاعدة الكشافة - Boy Scout Rule)، ووضع قاعدة الكود تدريجياً تحت تحكم TDD (منهجية العمل بفعالية مع الكود القديم - Working Effectively with Legacy Code).&lt;/p>
&lt;h2 id="8-tdd-كـ-تصميم-برمجي">8. TDD كـ تصميم برمجي
&lt;/h2>&lt;p>لا يُعد TDD شبكة أمان للحفاظ على جودة الكود فحسب، بل هو أيضاً محرك لتحسين تصميم كود C++. نتيجة للإجبار على استخدام حقن التبعية (DI) من أجل كتابة الاختبارات، يقل مستوى الارتباط (Coupling) بين الفئات ويزداد مستوى التماسك (Cohesion).&lt;/p>
&lt;p>في إعادة الهيكلة، من المهم أيضاً أن نكون واعين بالتعقيد الدوري (McCabe&amp;rsquo;s Cyclomatic Complexity).&lt;/p>
$$ M = E - N + 2P $$&lt;p>($M$: التعقيد، $E$: عدد الحواف (Edges)، $N$: عدد العقد (Nodes)، $P$: عدد المكونات المتصلة)&lt;/p>
&lt;p>بوجود الاختبارات، يصبح من الممكن تقسيم الدوال أو استبدالها بتعدد الأشكال (Polymorphism) لتقليل هذا التعقيد دون الخوف من إجراء تغييرات تكسر الكود (Breaking changes).&lt;/p>
&lt;h2 id="الخلاصة">الخلاصة
&lt;/h2>&lt;p>في هذا المقال، شرحنا بالتفصيل كيفية تقديم التطوير الموجه بالاختبار (TDD) باستخدام GoogleTest و GoogleMock في مشاريع C++.&lt;/p>
&lt;ol>
&lt;li>تكوين مشروع حديث باستخدام &lt;strong>CMake FetchContent&lt;/strong>&lt;/li>
&lt;li>التطبيق العملي لدورة &lt;strong>Red-Green-Refactor&lt;/strong>&lt;/li>
&lt;li>عمل Mock للواجهات باستخدام &lt;strong>GoogleMock و حقن التبعية (DI)&lt;/strong>&lt;/li>
&lt;li>تصور تغطية الاختبار باستخدام &lt;strong>gcov/lcov&lt;/strong>&lt;/li>
&lt;/ol>
&lt;p>على الرغم من أن TDD هو نهج يستغرق وقتاً لإتقانه، إلا أن عائد الاستثمار الخاص به لا يُحصى في برمجة الأنظمة التي تتطلب توازناً بين الأداء والأمان مثل لغة C++. نرجو منك البدء في ممارسة TDD تدريجياً من مشروعك القادم، والحصول على كود C++ متين وسهل الصيانة.&lt;/p></description></item></channel></rss>