سؤال

لدي فهم أساسي للأشياء الوهمية والمزيفة، لكنني لست متأكدًا من أن لدي شعورًا بشأن متى/أين أستخدم السخرية - خاصة أنه سينطبق على هذا السيناريو هنا.

هل كانت مفيدة؟

المحلول

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

عند اختبار مسار التعليمات البرمجية هذا باستخدام التبعية الفعلية، فأنت لا تقوم باختبار الوحدة؛أنت تقوم باختبار التكامل.على الرغم من أن هذا أمر جيد وضروري، إلا أنه ليس اختبارًا للوحدة.

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

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

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

ليرة تركية؛دكتور:سخر من كل تبعية يمسها اختبار الوحدة الخاصة بك.

نصائح أخرى

تكون الكائنات الوهمية مفيدة عندما تريد ذلك تفاعلات الاختبار بين فئة قيد الاختبار وواجهة معينة.

على سبيل المثال، نريد اختبار هذه الطريقة sendInvitations(MailServer mailServer) المكالمات MailServer.createMessage() مرة واحدة بالضبط، ويدعو أيضا MailServer.sendMessage(m) مرة واحدة بالضبط، ولا يتم استدعاء أي طرق أخرى على MailServer واجهه المستخدم.هذا هو الوقت الذي يمكننا فيه استخدام كائنات وهمية.

مع كائنات وهمية، بدلا من تمرير حقيقي MailServerImpl, ، أو اختبار TestMailServer, ، يمكننا تمرير تنفيذ وهمي لـ MailServer واجهه المستخدم.قبل أن نمر وهمية MailServer, ، نحن "ندربها" حتى تعرف الطريقة التي يجب توقعها وقيم الإرجاع التي يجب إرجاعها.في النهاية، يؤكد الكائن الوهمي أنه تم استدعاء جميع الأساليب المتوقعة كما هو متوقع.

يبدو هذا جيدًا من الناحية النظرية، ولكن هناك أيضًا بعض الجوانب السلبية.

نواقص وهمية

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

إليك مثال في الكود الكاذب.لنفترض أننا أنشأنا ملفًا MySorter الفصل ونريد اختباره:

// the correct way of testing
testSort() {
    testList = [1, 7, 3, 8, 2] 
    MySorter.sort(testList)

    assert testList equals [1, 2, 3, 7, 8]
}


// incorrect, testing implementation
testSort() {
    testList = [1, 7, 3, 8, 2] 
    MySorter.sort(testList)

    assert that compare(1, 2) was called once 
    assert that compare(1, 3) was not called 
    assert that compare(2, 3) was called once 
    ....
}

(في هذا المثال، نفترض أننا لا نريد اختبار خوارزمية فرز معينة، مثل الفرز السريع؛وفي هذه الحالة، سيكون الاختبار الأخير صالحًا بالفعل.)

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

يسخر كما بذرة

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

لنفترض أن لدينا طريقة sendInvitations(PdfFormatter pdfFormatter, MailServer mailServer) التي نريد اختبارها.ال PdfFormatter يمكن استخدام الكائن لإنشاء الدعوة.وهنا الاختبار:

testInvitations() {
   // train as stub
   pdfFormatter = create mock of PdfFormatter
   let pdfFormatter.getCanvasWidth() returns 100
   let pdfFormatter.getCanvasHeight() returns 300
   let pdfFormatter.addText(x, y, text) returns true 
   let pdfFormatter.drawLine(line) does nothing

   // train as mock
   mailServer = create mock of MailServer
   expect mailServer.sendMail() called exactly once

   // do the test
   sendInvitations(pdfFormatter, mailServer)

   assert that all pdfFormatter expectations are met
   assert that all mailServer expectations are met
}

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

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

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

كيفية اصلاح ذلك؟بسهولة:

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

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

لمزيد من التفاصيل حول أوجه القصور في المحاكاة، انظر أيضًا كائنات وهمية:أوجه القصور وحالات الاستخدام.

بحكم التجربة:

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

يجب عليك أن تسخر من كائن ما عندما يكون لديك تبعية في وحدة من التعليمات البرمجية التي تحاول اختبارها والتي يجب أن تكون "تمامًا".

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

يمكن العثور على بودكاست رائع حول هذا الموضوع هنا

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top