سيتم استخدام الاختبار الآلي في مهام أخرى، كما أنه يستخدم على نطاق واسع في المشاريع الحقيقية.
عندما نكتب دالة، يمكننا عادة أن نتخيل ما يجب أن تفعله: ما هي المعلمات التي تعطي النتائج.
أثناء التطوير، يمكننا التحقق من الوظيفة عن طريق تشغيلها ومقارنة النتيجة بالنتيجة المتوقعة. على سبيل المثال، يمكننا أن نفعل ذلك في وحدة التحكم.
إذا كان هناك خطأ ما – نقوم بإصلاح الكود، وتشغيله مرة أخرى، والتحقق من النتيجة – وهكذا حتى يعمل.
لكن عمليات "إعادة التشغيل" اليدوية هذه غير كاملة.
عند اختبار التعليمات البرمجية عن طريق إعادة التشغيل يدويًا، فمن السهل أن يفوتك شيء ما.
على سبيل المثال، نقوم بإنشاء دالة f
. كتب بعض التعليمات البرمجية، للاختبار: f(1)
يعمل، لكن f(2)
لا يعمل. قمنا بإصلاح الكود والآن يعمل f(2)
. تبدو كاملة؟ لكننا نسينا إعادة الاختبار f(1)
. قد يؤدي ذلك إلى خطأ.
هذا نموذجي للغاية. عندما نقوم بتطوير شيء ما، فإننا نضع الكثير من حالات الاستخدام المحتملة في الاعتبار. لكن من الصعب أن نتوقع من المبرمج أن يقوم بفحصها جميعًا يدويًا بعد كل تغيير. لذلك يصبح من السهل إصلاح شيء وكسر شيء آخر.
الاختبار الآلي يعني أن الاختبارات مكتوبة بشكل منفصل، بالإضافة إلى الكود. إنهم يديرون وظائفنا بطرق مختلفة ويقارنون النتائج مع المتوقع.
لنبدأ بتقنية تسمى التطوير الموجه بالسلوك أو باختصار BDD.
BDD عبارة عن ثلاثة أشياء في واحد: الاختبارات والوثائق والأمثلة.
لفهم اضطراب التشوه الجسمي (BDD)، سندرس حالة عملية للتطوير.
لنفترض أننا نريد إنشاء دالة pow(x, n)
ترفع x
إلى عدد صحيح n
. نحن نفترض أن n≥0
.
هذه المهمة مجرد مثال: يوجد عامل تشغيل **
في JavaScript يمكنه القيام بذلك، ولكننا نركز هنا على تدفق التطوير الذي يمكن تطبيقه على المهام الأكثر تعقيدًا أيضًا.
قبل إنشاء كود pow
، يمكننا أن نتخيل ما يجب أن تفعله الوظيفة ووصفها.
يُطلق على هذا الوصف اسم المواصفات ، أو باختصار، المواصفات، ويحتوي على أوصاف لحالات الاستخدام مع اختبارات لها، كما يلي:
وصف ("الأسرى"، وظيفة () { it ("يرفع إلى القوة n"، function() { Assur.equal(pow(2, 3), 8); }); });
تحتوي المواصفات على ثلاث وحدات أساسية يمكنك رؤيتها أعلاه:
describe("title", function() { ... })
ما هي الوظيفة التي نصفها؟ في حالتنا نحن نصف الدالة pow
. تستخدم لتجميع "العمال" - it
.
it("use case description", function() { ... })
في it
نصف بطريقة يمكن قراءتها من قبل الإنسان حالة الاستخدام المحددة، والوسيطة الثانية هي وظيفة تختبرها.
assert.equal(value1, value2)
يجب تنفيذ الكود الموجود داخل it
، إذا كان التنفيذ صحيحًا، بدون أخطاء.
يتم استخدام الدالات assert.*
للتحقق مما إذا كان pow
يعمل كما هو متوقع. نحن هنا نستخدم واحدًا منهم assert.equal
، فهو يقارن بين الوسائط وينتج خطأ إذا لم تكن متساوية. يتم هنا التحقق من أن نتيجة pow(2, 3)
تساوي 8
. وهناك أنواع أخرى من المقارنات والفحوصات التي سنضيفها لاحقًا.
يمكن تنفيذ المواصفات، وسيتم تشغيل الاختبار المحدد في الكتلة it
. سنرى ذلك لاحقا.
عادةً ما يبدو تدفق التطوير كما يلي:
تتم كتابة المواصفات الأولية، مع اختبارات للوظائف الأساسية.
يتم إنشاء التنفيذ الأولي.
للتحقق من نجاحه، نقوم بتشغيل إطار عمل الاختبار Mocha (مزيد من التفاصيل قريبًا) الذي يقوم بتشغيل المواصفات. في حين أن الوظيفة ليست كاملة، يتم عرض الأخطاء. نقوم بإجراء التصحيحات حتى يعمل كل شيء.
الآن لدينا تنفيذ أولي عملي مع الاختبارات.
نضيف المزيد من حالات الاستخدام إلى المواصفات، وربما لم يتم دعمها بعد من قبل التطبيقات. تبدأ الاختبارات بالفشل.
انتقل إلى 3، قم بتحديث التنفيذ حتى لا تعطي الاختبارات أي أخطاء.
كرر الخطوات من 3 إلى 6 حتى تصبح الوظيفة جاهزة.
لذا فإن التطوير متكرر . نكتب المواصفات، وننفذها، ونتأكد من اجتياز الاختبارات، ثم نكتب المزيد من الاختبارات، ونتأكد من عملها وما إلى ذلك. وفي النهاية لدينا تطبيق عملي واختبارات لها.
دعونا نرى تدفق التطور هذا في حالتنا العملية.
اكتملت الخطوة الأولى بالفعل: لدينا مواصفات أولية لـ pow
. الآن، قبل إجراء التنفيذ، دعونا نستخدم بعض مكتبات JavaScript لتشغيل الاختبارات، فقط للتأكد من أنها تعمل (ستفشل جميعها).
سنستخدم هنا في البرنامج التعليمي مكتبات JavaScript التالية لإجراء الاختبارات:
Mocha – الإطار الأساسي: يوفر وظائف اختبار شائعة بما في ذلك describe
it
الرئيسية التي تجري الاختبارات.
شاي – المكتبة التي تحتوي على العديد من التأكيدات. فهو يسمح باستخدام الكثير من التأكيدات المختلفة، في الوقت الحالي نحتاج فقط إلى assert.equal
.
Sinon – مكتبة للتجسس على الوظائف ومحاكاة الوظائف المضمنة والمزيد، سنحتاج إليها لاحقًا.
هذه المكتبات مناسبة للاختبار داخل المتصفح وعلى جانب الخادم. هنا سننظر في متغير المتصفح.
صفحة HTML الكاملة مع هذه الأطر ومواصفات pow
:
<!DOCTYPE html> <أتش تي أم أل> <الرأس> <!-- أضف mocha css لإظهار النتائج --> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/mocha/3.2.0/mocha.css"> <!-- إضافة كود إطار عمل موكا --> <script src="https://cdnjs.cloudflare.com/ajax/libs/mocha/3.2.0/mocha.js"></script> <النص البرمجي> mocha.setup('bdd'); // الحد الأدنى من الإعداد </script> <!-- أضف شاي --> <script src="https://cdnjs.cloudflare.com/ajax/libs/chai/3.5.0/chai.js"></script> <النص البرمجي> // تشاي لديه الكثير من الأشياء، فلنجعل التأكيد عالميًا دع التأكيد = chai.assert؛ </script> </الرأس> <الجسم> <النص البرمجي> وظيفة الأسرى (س، ن) { /* كود الوظيفة المراد كتابته، فارغ الآن */ } </script> <!-- البرنامج النصي مع الاختبارات (وصفه...) --> <script src="test.js"></script> <!-- سيحتوي العنصر ذو id="mocha" على نتائج الاختبار --> <div id="mocha"></div> <!-- إجراء الاختبارات! --> <النص البرمجي> mocha.run(); </script> </الجسم> </html>
يمكن تقسيم الصفحة إلى خمسة أجزاء:
<head>
– إضافة مكتبات وأنماط خارجية للاختبارات.
<script>
مع الوظيفة المراد اختبارها، في حالتنا – مع رمز pow
.
الاختبارات – في حالتنا test.js
البرنامج النصي الخارجي.js الذي تم describe("pow", ...)
من الأعلى.
سيتم استخدام عنصر HTML <div id="mocha">
بواسطة Mocha لإخراج النتائج.
تبدأ الاختبارات بالأمر mocha.run()
.
النتيجة:
اعتبارًا من الآن، فشل الاختبار، وهناك خطأ. هذا أمر منطقي: لدينا رمز دالة فارغ في pow
، لذلك يُرجع pow(2,3)
undefined
بدلاً من 8
.
بالنسبة للمستقبل، دعونا نلاحظ أن هناك المزيد من اختبارات الاختبار عالية المستوى، مثل Karma وغيرها، مما يجعل من السهل التشغيل التلقائي للعديد من الاختبارات المختلفة.
لنقم بتنفيذ بسيط لـ pow
لاجتياز الاختبارات:
وظيفة الأسرى (س، ن) { العودة 8؛ // :) نحن نغش! }
واو، الآن يعمل!
ما فعلناه هو بالتأكيد غش. الدالة لا تعمل: محاولة حساب pow(3,4)
قد تعطي نتيجة غير صحيحة، لكن الاختبارات تنجح.
…لكن الوضع نموذجي تمامًا، ويحدث في الممارسة العملية. تم اجتياز الاختبارات، لكن الوظيفة تعمل بشكل خاطئ. المواصفات لدينا غير كاملة. نحن بحاجة إلى إضافة المزيد من حالات الاستخدام إليها.
دعونا نضيف اختبارًا آخر للتحقق من ذلك pow(3, 4) = 81
.
يمكننا اختيار إحدى طريقتين لتنظيم الاختبار هنا:
المتغير الأول - أضف assert
آخر في نفس it
:
وصف ("الأسرى"، وظيفة () { it ("يرفع إلى القوة n"، function() { Assur.equal(pow(2, 3), 8); Assur.equal(pow(3, 4), 81); }); });
الثاني – إجراء اختبارين:
وصف ("الأسرى"، وظيفة () { it ("2 مرفوع للأس 3 يساوي 8"، function() { Assur.equal(pow(2, 3), 8); }); it("3 مرفوع للقوة 4 يساوي 81"، function() { Assur.equal(pow(3, 4), 81); }); });
والفرق الرئيسي هو أنه عندما يؤدي assert
إلى حدوث خطأ، يتم إنهاء كتلة it
على الفور. لذلك، في المتغير الأول إذا فشل assert
الأول، فلن نرى أبدًا نتيجة assert
الثاني.
يعد فصل الاختبارات أمرًا مفيدًا للحصول على مزيد من المعلومات حول ما يحدث، لذا فإن الخيار الثاني هو الأفضل.
وإلى جانب ذلك، هناك قاعدة أخرى من الجيد اتباعها.
اختبار واحد يتحقق من شيء واحد.
إذا نظرنا إلى الاختبار ورأينا فيه فحصين مستقلين، فمن الأفضل تقسيمه إلى فحصين أبسط.
لذلك دعونا نواصل مع البديل الثاني.
النتيجة:
وكما يمكن أن نتوقع، فشل الاختبار الثاني. بالتأكيد، تُرجع وظيفتنا دائمًا 8
، بينما يتوقع assert
81
.
دعنا نكتب شيئًا أكثر واقعية لاجتياز الاختبارات:
وظيفة الأسرى (س، ن) { دع النتيجة = 1؛ لـ (دع i = 0; i < n; i++) { النتيجة *= س؛ } نتيجة الإرجاع؛ }
للتأكد من أن الدالة تعمل بشكل جيد، فلنختبرها لمزيد من القيم. بدلًا من it
الكتل يدويًا، يمكننا إنشاءها من for
:
وصف ("الأسرى"، وظيفة () { دالة makeTest(x) { دع المتوقع = x * x * x؛ it(`${x} في القوة 3 هو ${expect}`, function() { تأكيد.equal(pow(x, 3), متوقع); }); } لـ (دع x = 1; x <= 5; x++) { makeTest(x); } });
النتيجة:
ونحن في طريقنا لإضافة المزيد من الاختبارات. ولكن قبل ذلك، دعونا نلاحظ أنه يجب تجميع الدالة المساعدة makeTest
و for
معًا. لن نحتاج إلى makeTest
في الاختبارات الأخرى، فهو مطلوب فقط من for
: مهمتهم المشتركة هي التحقق من كيفية رفع pow
إلى القوة المعطاة.
يتم التجميع باستخدام describe
متداخل:
وصف ("الأسرى"، وظيفة () { وصف ("يرفع x إلى القوة 3"، function() { دالة makeTest(x) { دع المتوقع = x * x * x؛ it(`${x} في القوة 3 هو ${expect}`, function() { تأكيد.equal(pow(x, 3), متوقع); }); } لـ (دع x = 1; x <= 5; x++) { makeTest(x); } }); // ... المزيد من الاختبارات التي يجب اتباعها هنا، كلاهما وصف ويمكن إضافتهما });
يحدد describe
المتداخل "مجموعة فرعية" جديدة من الاختبارات. في الإخراج يمكننا أن نرى المسافة البادئة بعنوان:
في المستقبل، يمكننا إضافة المزيد it
على المستوى الأعلى باستخدام وظائف مساعدة خاصة بها describe
ولن يتمكنوا من رؤية makeTest
.
before/after
وقبل beforeEach/afterEach
يمكننا إعداد الوظائف before/after
التي يتم تنفيذها قبل/بعد إجراء الاختبارات، وأيضًا الوظائف beforeEach/afterEach
التي يتم تنفيذها قبل/بعد كل it
.
على سبيل المثال:
وصف ("اختبار"، وظيفة () { before(() => تنبيه("بدأ الاختبار - قبل جميع الاختبارات")); after(() => تنبيه("انتهى الاختبار - بعد كل الاختبارات")); beforeEach(() => تنبيه("قبل الاختبار - أدخل الاختبار")); afterEach(() => تنبيه("بعد الاختبار - اخرج من الاختبار")); it('اختبار 1', () => تنبيه(1)); it('اختبار 2', () => تنبيه(2)); });
سيكون تسلسل التشغيل:
بدأ الاختبار – قبل جميع الاختبارات (قبل) قبل الاختبار - أدخل الاختبار (قبل كل) 1 بعد الاختبار – الخروج من الاختبار (afterEach) قبل الاختبار - أدخل الاختبار (قبل كل) 2 بعد الاختبار – الخروج من الاختبار (afterEach) انتهى الاختبار - بعد كل الاختبارات (بعد)
افتح المثال في وضع الحماية.
عادةً ما يتم استخدام beforeEach/afterEach
و before/after
لإجراء التهيئة أو التخلص من العدادات أو القيام بشيء آخر بين الاختبارات (أو مجموعات الاختبار).
اكتملت الوظيفة الأساسية لـ pow
. تم الانتهاء من التكرار الأول للتطوير. عندما ننتهي من الاحتفال وشرب الشمبانيا - فلنواصل تحسينه.
كما قيل، فإن الدالة pow(x, n)
تهدف إلى العمل مع قيم الأعداد الصحيحة الموجبة n
.
للإشارة إلى خطأ رياضي، عادةً ما تُرجع وظائف JavaScript NaN
. لنفعل الشيء نفسه بالنسبة لقيم n
غير الصالحة.
دعنا أولاً نضيف السلوك إلى المواصفات (!):
وصف ("الأسرى"، وظيفة () { // ... it ("بالنسبة إلى n السالبة تكون النتيجة NaN"، function() { Assur.isNaN(pow(2, -1)); }); it ("للعدد غير الصحيح n تكون النتيجة NaN"، function() { Assur.isNaN(pow(2, 1.5)); }); });
النتيجة مع الاختبارات الجديدة:
تفشل الاختبارات المضافة حديثًا، لأن تطبيقنا لا يدعمها. هذه هي الطريقة التي يتم بها BDD: أولاً نكتب الاختبارات الفاشلة، ثم نقوم بتنفيذها.
تأكيدات أخرى
برجاء ملاحظة التأكيد assert.isNaN
: فهو يتحقق من وجود NaN
.
هناك تأكيدات أخرى في شاي أيضا، على سبيل المثال:
assert.equal(value1, value2)
- يتحقق من قيمة المساواة value1 == value2
.
assert.strictEqual(value1, value2)
– يتحقق من قيمة المساواة الصارمة value1 === value2
.
assert.notEqual
و assert.notStrictEqual
– عمليات التحقق العكسية لتلك المذكورة أعلاه.
assert.isTrue(value)
– يتحقق من أن value === true
assert.isFalse(value)
– يتحقق من أن value === false
…القائمة الكاملة موجودة في المستندات
لذلك يجب أن نضيف سطرين إلى pow
:
وظيفة الأسرى (س، ن) { إذا (n < 0) قم بإرجاع NaN؛ if (Math.round(n) != n) return NaN; دع النتيجة = 1؛ لـ (دع i = 0; i < n; i++) { النتيجة *= س؛ } نتيجة الإرجاع؛ }
الآن يعمل، تم اجتياز جميع الاختبارات:
افتح المثال النهائي الكامل في وضع الحماية.
في BDD، تبدأ المواصفات أولاً، يليها التنفيذ. في النهاية لدينا كل من المواصفات والرمز.
يمكن استخدام المواصفات بثلاث طرق:
كاختبارات – فهي تضمن أن الكود يعمل بشكل صحيح.
مثل المستندات - describe
عناوين هذه الوظيفة it
ما تفعله.
كأمثلة - الاختبارات هي في الواقع أمثلة عملية توضح كيفية استخدام الوظيفة.
باستخدام المواصفات، يمكننا تحسين الوظيفة وتغييرها وحتى إعادة كتابتها بأمان من البداية والتأكد من أنها لا تزال تعمل بشكل صحيح.
وهذا مهم بشكل خاص في المشاريع الكبيرة عندما يتم استخدام الوظيفة في العديد من الأماكن. عندما نغير مثل هذه الوظيفة، لا توجد طريقة للتحقق يدويًا مما إذا كان كل مكان يستخدمها لا يزال يعمل بشكل صحيح.
بدون اختبارات، الناس أمام طريقين:
لتنفيذ التغيير مهما كان. ومن ثم يواجه مستخدمونا أخطاء، حيث من المحتمل أننا نفشل في التحقق من شيء ما يدويًا.
أو، إذا كانت عقوبة الأخطاء قاسية، حيث لا توجد اختبارات، يصبح الناس خائفين من تعديل مثل هذه الوظائف، ثم تصبح التعليمات البرمجية قديمة، ولا أحد يريد الدخول فيها. ليست جيدة للتنمية.
الاختبار التلقائي يساعد على تجنب هذه المشاكل!
إذا كان المشروع مغطى بالاختبارات، فلا توجد مشكلة من هذا القبيل. بعد إجراء أي تغييرات، يمكننا إجراء الاختبارات ورؤية الكثير من عمليات التحقق التي تم إجراؤها في غضون ثوانٍ.
علاوة على ذلك، فإن الكود الذي تم اختباره جيدًا يتمتع ببنية أفضل.
وبطبيعة الحال، يرجع ذلك إلى أن التعليمات البرمجية التي تم اختبارها تلقائيًا تكون أسهل في التعديل والتحسين. ولكن هناك أيضًا سبب آخر.
لكتابة الاختبارات، يجب تنظيم الكود بحيث يكون لكل وظيفة مهمة موصوفة بوضوح، ومدخلات ومخرجات محددة جيدًا. وهذا يعني بنية جيدة من البداية.
في الحياة الواقعية، الأمر ليس بهذه السهولة في بعض الأحيان. في بعض الأحيان يكون من الصعب كتابة مواصفات قبل الكود الفعلي، لأنه ليس من الواضح بعد كيف يجب أن يتصرف. لكن بشكل عام، اختبارات الكتابة تجعل التطوير أسرع وأكثر استقرارًا.
لاحقًا في البرنامج التعليمي، ستواجه العديد من المهام مع الاختبارات المدمجة. لذلك سترى المزيد من الأمثلة العملية.
تتطلب اختبارات الكتابة معرفة جيدة بجافا سكريبت. لكننا بدأنا للتو في تعلم ذلك. لذا، لتسوية كل شيء، حتى الآن ليس مطلوبًا منك كتابة الاختبارات، ولكن يجب أن تكون قادرًا بالفعل على قراءتها حتى لو كانت أكثر تعقيدًا قليلاً مما كانت عليه في هذا الفصل.
الأهمية: 5
ما هو الخطأ في اختبار pow
أدناه؟
it ("يرفع x إلى القوة n"، function() { دع س = 5؛ دع النتيجة = س؛ تأكيد.equal(pow(x, 1), result); النتيجة *= س؛ تأكيد.equal(pow(x, 2),result); النتيجة *= س؛ تأكيد.equal(pow(x, 3),result); });
PS من الناحية النحوية الاختبار صحيح ويمر.
يوضح الاختبار إحدى الإغراءات التي يواجهها المطور عند كتابة الاختبارات.
ما لدينا هنا هو في الواقع 3 اختبارات، ولكن تم وضعها كدالة واحدة تحتوي على 3 تأكيدات.
في بعض الأحيان يكون من الأسهل الكتابة بهذه الطريقة، ولكن في حالة حدوث خطأ، يكون الخطأ الذي حدث أقل وضوحًا.
إذا حدث خطأ في منتصف تدفق التنفيذ المعقد، فسيتعين علينا معرفة البيانات عند تلك النقطة. سيتعين علينا في الواقع تصحيح الاختبار .
سيكون من الأفضل تقسيم الاختبار إلى it
كتل بمدخلات ومخرجات مكتوبة بوضوح.
مثله:
وصف ("يرفع x إلى القوة n"، function() { ذلك ("5 في قوة 1 يساوي 5"، function() { Assur.equal(pow(5, 1), 5); }); it ("5 في قوة 2 يساوي 25"، function() { Assur.equal(pow(5, 2), 25); }); it("5 في قوة 3 يساوي 125"، function() { Assur.equal(pow(5, 3), 125); }); });
لقد استبدلنا it
describe
ومجموعة it
. الآن إذا فشل شيء ما فسنرى بوضوح ما هي البيانات.
يمكننا أيضًا عزل اختبار واحد وتشغيله في الوضع المستقل عن طريق كتابته it.only
بدلاً من it
:
وصف ("يرفع x إلى القوة n"، function() { ذلك ("5 في قوة 1 يساوي 5"، function() { Assur.equal(pow(5, 1), 5); }); // سيتم تشغيل Mocha هذه الكتلة فقط it.only("5 في قوة 2 يساوي 25"، function() { Assur.equal(pow(5, 2), 25); }); it("5 في قوة 3 يساوي 125"، function() { Assur.equal(pow(5, 3), 125); }); });