الاختبار فقط من خلال الأساليب العامة أمر سيئ

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

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

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

ناقص 1: التكوين الزائد لاختبار الوحدة


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

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

ناقص 2: تغطية غير مكتملة


مزيد من العامل البشري مثل الكسل يمكن أن تتدخل. على سبيل المثال ، قد تبدو طريقة خاصة ذات منطق إشارة غير تافهة في هذا المثال.

private bool HasShifts(DateTime date, int tolerance, bool clockIn, Shift[] shifts, int[] locationIds) { bool isInLimit(DateTime date1, DateTime date2, int limit) => Math.Abs(date2.Subtract(date1).TotalMinutes) <= limit; var shiftsOfLocations = shifts.Where(x => locationIds.Contains(x.LocationId)); return clockIn ? shiftsOfLocations.Any(x => isInLimit(date, x.StartDate, tolerance)) : shiftsOfLocations.Any(x => isInLimit(date, x.EndDate, tolerance)); } 

تتطلب هذه الطريقة 10 فحوصات لتغطية جميع الحالات ، 8 منها مهمة.

فك 8 حالات مهمة
  • shiftsOfLocations - 2 القيم - أم لا
  • clockIn - 2 قيم - صواب أم خطأ
  • التسامح - 2 معاني مختلفة

المجموع: 2 × 2 × 2 = 8

عند كتابة اختبارات الوحدة لاختبار هذا المنطق ، سيتعين على المطور كتابة 8 اختبارات وحدة كبيرة على الأقل. لقد صادفت حالات عندما استغرق تكوين اختبار الوحدة أكثر من 50 سطرًا من التعليمات البرمجية ، مع 4 أسطر من المكالمة المباشرة. على سبيل المثال فقط حوالي 10٪ من الكود يحمل حمولة. في هذه الحالة ، يكون الإغراء رائعًا لتقليل كمية العمل عن طريق كتابة عدد أقل من اختبارات الوحدة. ونتيجة لذلك ، من أصل 8 ، على سبيل المثال ، لا يزال هناك اختباران فقط للوحدة لكل قيمة على مدار الساعة. يؤدي هذا الموقف إلى حقيقة أنه إما ، مرة أخرى ، إنه أمر شاق وطويل كتابة جميع الاختبارات اللازمة ، وإنشاء التكوين (يعمل Ctrl + C ، V ، حيث بدونها) ، أو أن الطريقة لا تزال مغطاة جزئيًا فقط. كل خيار له عواقبه غير السارة.

الحلول الممكنة


بالإضافة إلى مبدأ "سلوك الاختبار" ، لا يزال هناك OCP (مبدأ مفتوح / مغلق). بتطبيقه بشكل صحيح ، يمكنك نسيان "الاختبارات الهشة" ، اختبار السلوك الداخلي للوحدة. إذا كنت بحاجة إلى سلوك وحدة نمطية جديد ، فسوف تكتب اختبارات وحدة جديدة لفئة الخلف الجديدة التي سيتم فيها تغيير السلوك الذي تحتاجه. ثم لن تضطر إلى قضاء بعض الوقت في إعادة فحص وتحديث الاختبارات الحالية. في هذه الحالة ، يمكن الإعلان عن هذه الطريقة على أنها داخلية ، أو داخلية محمية ، واختبارها عن طريق إضافة InternalsVisibleTo إلى التجميع. في هذه الحالة ، لن تعاني واجهة IClass الخاصة بك ، وستكون الاختبارات الأكثر اختصارًا ، ولا تخضع للتغييرات المتكررة.

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

الملخص


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

Source: https://habr.com/ru/post/ar419873/


All Articles