كتابة كود قابل للاختبار
نشرت: 2022-11-03اختبار الوحدة هو أداة أساسية في صندوق الأدوات لأي مطور برامج. تعد اختبارات وحدة الكتابة سهلة نسبيًا عند التعامل مع قاعدة بيانات تتبع أفضل ممارسات وأنماط ومبادئ تصميم البرامج. تنشأ المشكلة الحقيقية عند محاولة اختبار الوحدة كود سيء التصميم وغير قابل للاختبار.
ستناقش هذه المدونة كيفية كتابة المزيد من التعليمات البرمجية القابلة للاختبار ، والأنماط والممارسات السيئة التي يجب تجنبها لتحسين قابلية الاختبار.
كود قابل للاختبار وغير قابل للاختبار
عند العمل على التطبيقات واسعة النطاق التي تحتاج إلى أن تكون قابلة للصيانة على المدى الطويل ، يجب أن نعتمد على الاختبارات الآلية للحفاظ على الجودة الإجمالية للنظام عالية. مقارنة باختبارات التكامل ، حيث تختبر وحدات متعددة ككل ، فإن اختبارات الوحدة لها فائدة كونها سريعة ومستقرة. سريع لأننا نقوم بإنشاء مثيل ، بشكل مثالي ، فقط للفئة قيد الاختبار ، ومستقرة لأننا عادة ما نسخر من التبعيات الخارجية ، على سبيل المثال ، اتصال قاعدة البيانات أو الشبكة.
إذا لم تكن على دراية بالفرق الدقيق بين اختبارات الوحدة واختبارات التكامل ، فيمكنك قراءة المزيد حول هذا الموضوع في مقدمة مدونة الاختبار الخاصة بنا.
يمكن عزل الكود القابل للاختبار عن بقية قاعدة الكود لدينا. بمعنى آخر ، يمكن اختبار الوحدات الأصغر بشكل مستقل. تمت كتابة الكود غير القابل للاختبار بطريقة تجعل من الصعب ، أو حتى المستحيل ، كتابة اختبار وحدة جيد له.
دعنا نراجع بعض الأنماط المضادة والممارسات السيئة التي يجب أن نتجنبها عند كتابة تعليمات برمجية قابلة للاختبار.
تمت كتابة الأمثلة بلغة Java ، لكن اصطلاحات الترميز المذكورة هنا تنطبق على أي لغة برمجة موجهة للكائنات وإطار عمل للاختبار. سنستخدم assertj و JUnit5 للحصول على أمثلة في منشور المدونة هذا.
حقن التبعية
يعد حقن التبعية أحد أهم أنماط التصميم لتحقيق عزل الاختبار. حقن التبعية هو نمط تصميم يتلقى فيه كائن ما كائنات أخرى (تبعيات) من خلال معلمات المُنشئ أو المحددات بدلاً من الاضطرار إلى بنائها بنفسها.
من خلال حقن التبعية ، يمكننا بسهولة عزل الفئة قيد الاختبار عن طريق الاستهزاء بتبعية الكائن.
لنلق نظرة على مثال بدون حقن التبعية:

نظرًا لأنه يتم إنشاء تبعية المحرك في مُنشئ فئة السيارة ، يمكنك القول إن فئتي السيارة والمحرك مرتبطتان بإحكام. إنهم يعتمدون بشكل كبير على بعضهم البعض - تغيير أحدهم يتطلب تغييرًا في الآخر.
من منظور الاختبار ، لا يمكنك اختبار فئة السيارة بمعزل عن غيرها لأن المثال أعلاه لا يمكن أن يحل محل تنفيذ المحرك الخرساني بمضاعفة الاختبار.
ومع ذلك ، يمكننا تحقيق العزلة باستخدام حقن التبعية وتعدد الأشكال:

يمكننا الآن إنشاء تطبيقات متعددة للمحركات ، وبالتالي ، سيارات بمحركات مختلفة:

أصبح الاختبار بمعزل عن الآخرين ممكنًا الآن لأننا نستطيع إنشاء تنفيذ ساخر لتجريد المحرك ونقله إلى فئة السيارات لدينا:

عند التعامل مع كائنات تتطلب كائنات أخرى (تبعيات) ، يجب عليك توفيرها من خلال معلمات الباني (حقن التبعية) ، المخفية بشكل مثالي خلف بعض التجريد.
باتباع هذا النمط ، يصبح الرمز الخاص بك أكثر قابلية للقراءة وقابلية للتكيف مع التغيير بمرور الوقت. أيضًا ، يجب تجنب القيام بالعمل الفعلي في المُنشئين - أي شيء أكثر من التعيينات الميدانية هو عمل فعلي. تعد الكلمة الرئيسية "new" في المُنشئ دائمًا علامة تحذير على رمز غير قابل للاختبار.
شيء واحد يجب ملاحظته هنا هو أنه في بعض المواقف ، لا يعد الاقتران الوثيق (على سبيل المثال ، الكلمات الرئيسية الجديدة في المنشئات ، والفئات الداخلية للعزل المنطقي ، ورسم خرائط الكائنات) ممارسة سيئة - الفئات التي لن يكون لها معنى باعتبارها فئة "قائمة بذاتها".
دولة عالمية
غالبًا ما تؤدي مشاركة حالة عالمية إلى إجراء اختبارات غير مستقرة (تمر أحيانًا ، وأحيانًا تفشل) ، خاصة في البيئات متعددة مؤشرات الترابط.
تخيل سيناريو تشترك فيه كائنات متعددة قيد الاختبار في نفس الحالة العامة - إذا أدت طريقة في أحد الكائنات إلى حدوث تأثير جانبي يغير قيمة الحالة العامة المشتركة ، يصبح الإخراج من طريقة في الكائن الآخر غير متوقع. تجنب استخدام الأساليب الثابتة غير النقية لأنها تحوِّل الحالة العالمية بطريقة ما أو تكون وكيلة لدولة عالمية ما.
لنلقِ نظرة على هذه الطريقة الثابتة غير النقية:

بشكل أساسي ، تقرأ هذه الطريقة تاريخ النظام الحالي ووقته وتعيد نتيجة بناءً على تلك القيمة. سيكون من الصعب جدًا كتابة اختبار وحدة مناسب قائم على الحالة لهذه الطريقة لأن المكالمة الثابتة LocalDateTime.now () ستؤدي إلى نتائج مختلفة أثناء تنفيذ اختباراتنا. كتابة الاختبارات لهذه الطريقة مستحيلة دون تغيير تاريخ النظام ووقته.
لإصلاح ذلك ، سنمرر تاريخ الوقت إلى طريقة timeOfDay كوسيطة:


أصبحت الطريقة الثابتة timeOfDay نقية الآن - تنتج نفس المدخلات دائمًا نفس النتائج. الآن يمكننا بسهولة تمرير كائنات dateTime المعزولة كوسيطات في اختباراتنا:

قانون ديميتر
ينص قانون ديميتر ، أو مبدأ أقل قدر من المعرفة ، على أن الأشياء يجب أن تعرف فقط عن الأشياء التي ترتبط ارتباطًا وثيقًا بالكائن الأول. بمعنى آخر ، يجب أن يكون لكائن واحد فقط إمكانية الوصول إلى الأشياء التي يحتاجها. على سبيل المثال ، لدينا طريقة تقبل كائن سياق كوسيطة:

هذه الطريقة تنتهك قانون ديميتر لأنها تحتاج إلى السير في رسم بياني للكائن للحصول على المعلومات المطلوبة للقيام بعملها. إن تمرير المعلومات غير الضرورية إلى الفئات والطرق يضر بقابلية الاختبار.
تخيل كائن BillingContext ضخم يحتوي على إشارات إلى كائنات أخرى:

كما نرى ، فإن اختبارنا مليء بالمعلومات غير الضرورية. يصعب قراءة الاختبارات التي تنشئ رسومًا بيانية معقدة للكائنات وتؤدي إلى تعقيد غير ضروري.
دعنا نصلح مثالنا السابق:

يجب عليك دائمًا تمرير التبعيات المباشرة إلى فئاتك وطرقك. ومع ذلك ، فإن تمرير العديد من الوسائط إلى أساليب ليس ممارسة جيدة أيضًا - من الناحية المثالية ، يجب عليك تمرير وسيطتين بحد أقصى أو التفاف الوسائط ذات الصلة الوثيقة في كائنات بيانات.
يعترض الله
غرض الله هو الشيء الذي يشير إلى العديد من الأشياء المميزة الأخرى ، وله أكثر من مسؤولية واحدة ، وله أسباب متعددة للتغيير. إذا كان من الصعب تلخيص ما يفعله الفصل ، أو إذا تضمن التلخيص كلمة "و" ، فمن المحتمل أن يتحمل الفصل أكثر من مسؤولية واحدة.
يصعب اختبار كائنات الله لأننا نتعامل مع تبعيات متعددة غير مرتبطة ، ونخلط مستويات مختلفة من التجريدات والمخاوف ، وتنتج الكثير من الآثار الجانبية. وبالتالي ، من الصعب تحقيق الحالة المرغوبة لحالات الاختبار لدينا.
فمثلا:

تتحمل UserService أكثر من مسؤولية - تسجيل مستخدمين جدد وإرسال رسائل بريد إلكتروني. أثناء اختبار تسجيل المستخدم ، نحتاج إلى التعامل مع خدمة البريد الإلكتروني والعكس صحيح:

تخيل خدمة مستخدم بها أكثر من تبعيتين غير مرتبطين. هذه التبعيات لها تبعياتها الخاصة ، وما إلى ذلك. سننتهي باختبار غير قابل للقراءة ، ومليء بمعلومات غير ذات صلة ، ويصعب فهمه. لذلك ، يجب أن يكون لكل فصل مسؤولية واحدة فقط وسبب واحد للتغيير. الفصل الذي لديه سبب واحد فقط للتغيير هو أحد مبادئ تصميم البرامج الخمسة التي تسمى مبدأ المسؤولية الفردية.
يمكنك قراءة المزيد عن مبادئ SOLID هنا.
استنتاج
قاعدة الكود التي تتبع أفضل ممارسات تصميم البرامج تجعل اختبارات وحدة الكتابة أكثر قابلية للإدارة.
من ناحية أخرى ، قد يكون من الصعب للغاية ، أو حتى من المستحيل في بعض الأحيان ، كتابة اختبار وحدة العمل لقاعدة كود باستخدام الأنماط المضادة المذكورة. تتطلب كتابة كود جيد قابل للاختبار الكثير من الممارسة والانضباط والجهد الإضافي. الميزة الأكثر أهمية للشفرة القابلة للاختبار هي سهولة الاختبار والقدرة على فهم هذه الشفرة والحفاظ عليها وتوسيعها.
نأمل أن تساعدك هذه المدونة في كتابة كود قابل للاختبار.