وتكون الاختبارات الآلية في الأساس مجرد رموز برمجية تؤدي إلى حدوث خطأ أو قد تتسبب في حدوث خطأ إذا حدث خطأ ما. توفر معظم المكتبات أو أطر الاختبار مجموعة متنوعة من الأساسية التي تجعل كتابة الاختبارات أسهل.
كما ذكرنا في القسم السابق، تتضمن هذه الأساسيات دائمًا لتحديد الاختبارات المستقلة (يُشار إليها باسم حالات الاختبار) وتوفير التأكيدات. التأكيدات هي وسيلة لدمج التحقق من نتيجة وطرح إذا كان هناك خطأ ما، ويمكن اعتباره النقطة الأساسية لجميع لمبادئ الاختبار.
تتناول هذه الصفحة منهجًا عامًا لهذه المبادئ الأساسية. إطار العمل الذي اخترته يتضمن شيئًا من هذا القبيل، لكنه ليس مرجعًا دقيقًا.
على سبيل المثال:
import { fibonacci, catalan } from '../src/math.js';
import { assert, test, suite } from 'a-made-up-testing-library';
suite('math tests', () => {
test('fibonacci function', () => {
// check expected fibonacci numbers against our known actual values
// with an explanation if the values don't match
assert.equal(fibonacci(0), 0, 'Invalid 0th fibonacci result');
assert.equal(fibonacci(13), 233, 'Invalid 13th fibonacci result');
});
test('relationship between sequences', () => {
// catalan numbers are greater than fibonacci numbers (but not equal)
assert.isAbove(catalan(4), fibonacci(4));
});
test('bugfix: check bug #4141', () => {
assert.isFinite(fibonacci(0)); // fibonacci(0) was returning NaN
})
});
ينشئ هذا المثال مجموعة من الاختبارات (تُسمّى أحيانًا حزمة) تُسمى "الرياضيات" الاختبارات"، وتحدد ثلاث حالات اختبار مستقلة تُجري كل منها بعض التأكيدات. ويمكن معالجة حالات الاختبار هذه أو إجراؤها بشكل فردي، على سبيل المثال، عن طريق علامة التصفية في أداة تشغيل الاختبار.
التطبيقات المساعدة في التأكيد كأساسيات
تتضمن معظم أطر الاختبار، بما في ذلك Vitest، مجموعة من التأكيدات
عناصر المساعدة في كائن assert
والتي تتيح لك التحقق سريعًا من القيم أو
الولايات الأخرى مقابل بعض التوقعات. غالبًا ما يكون هذا التوقع "جيدًا معروفًا"
القيم. في المثال السابق، نعرف أن رقم فيبوناتشي الثالث عشر يجب أن يكون
233، حتى نتمكّن من تأكيد ذلك مباشرةً باستخدام assert.equal
.
قد يكون لديك أيضًا توقعات بأن القيمة تتخذ شكلاً معينًا، أو أكبر من قيمة أخرى، أو يضم بعض الخصائص الأخرى. هذه الدورة لن تغطي النطاق الكامل لعوامل مساعدة التأكيد المحتملة، ولكن إطارات عمل الاختبار تقديم عمليات التحقق الأساسية التالية على الأقل:
"سلامة" شيك، موصوف غالبًا على أنه "موافق" تتحقق من صحة أحد الشروط، بمطابقة الطريقة التي يمكنك بها كتابة
if
تتحقق مما إذا كان هناك شيء ناجح أو صحيح. يتم تقديم هذه السمة عادةً على أنّهاassert(...)
أوassert.ok(...)
على قيمة واحدة بالإضافة إلى تعليق اختياري.تحقق المساواة، كما هو الحال في مثال اختبار الرياضيات، الذي تتوقع فيه القيمة المعروضة أو الحالة الخاصة بكائن تساوي قيمة جيدة معروفة. هذه مخصصة المساواة الأولية (مثل الأرقام والسلاسل) أو المساواة المرجعية (هل هذه هي نفس الكائن). ما يجري وضع علامة في المربّع من خلال مقارنة
==
أو===
.- يميّز JavaScript بين تساوي القيم غير القابلة للتعديل (
==
) والمساواة الصارمة (===
). توفِّر لك معظم مكتبات الاختبارات الطريقتَينassert.equal
assert.strictEqual
، على التوالي.
- يميّز JavaScript بين تساوي القيم غير القابلة للتعديل (
عمليات التحقق من المساواة العميقة، التي توسّع عمليات التحقق من المساواة لتشمل التحقق من محتويات الكائنات والصفائف وأنواع البيانات الأخرى الأكثر تعقيدًا، فضلاً عن والمنطق الداخلي لاجتياز الكائنات لمقارنتها. هذه العناصر مهمة لأن جافا سكريبت لا توجد به طريقة مضمنة لمقارنة محتويات كائنين أو صفائف. على سبيل المثال، تكون قيمة
[1,2,3] == [1,2,3]
دائمًا false. اختبار غالبًا ما تتضمن أطر العمل المساعدةdeepEqual
أوdeepStrictEqual
.
عبارات مساعِدة تقارِن بين قيمتين (بدلاً من التحقّق من الصحة فقط) عادةً ما تأخذ وسيطتين أو ثلاث وسيطات:
- هي القيمة الفعلية كما يتم إنشاؤها من التعليمة البرمجية قيد الاختبار أو وصف التحقق من صحتها.
- والقيمة المتوقعة، وعادةً ما تكون غير قابلة للتغيير (على سبيل المثال، رقم حرفي أو سلسلة).
- تعليق اختياري يصف ما هو متوقع أو ما ربما فشل، والتي سيتم تضمينها في حال فشل هذا السطر.
كما أنه من الشائع إلى حد ما دمج التأكيدات لإنشاء مجموعة متنوعة من عمليات التحقق، لأنه من النادر جدًا أن يؤكد أي شخص حالة في حد ذاته. على سبيل المثال:
test('JWT parse', () => {
const json = decodeJwt('eyJieSI6InNhbXRob3Ii…');
assert.ok(json.payload.admin, 'user should be admin');
assert.deepEqual(json.payload.groups, ['role:Admin', 'role:Submitter']);
assert.equal(json.header.alg, 'RS265')
assert.isAbove(json.payload.exp, +new Date(), 'expiry must be in future')
});
يستخدم Vitest مكتبة تأكيدات شاي داخليًا لتوفير مساعديه، وقد يكون من المفيد النظر في مرجعه لمعرفة التأكيدات والمساعدات التي قد تناسب التعليمات البرمجية الخاصة بك.
تأكيدات طليقة وتأكيدات BDD
يفضّل بعض المطوّرين استخدام أسلوب تأكيد يستند إلى السلوك
التطوير (BDD) أو
أسلوب فصيح
التأكيدات. وتسمى هذه أيضًا "توقع" المساعدة، لأن نقطة الدخول إلى
التحقق من التوقعات هي طريقة تسمى expect()
.
توقَّع أن يتصرف الأشخاص المساعدون بالطريقة نفسها التي تتّبعها التأكيدات المكتوبة كطريقة بسيطة.
مثل assert.ok
أو assert.strictDeepEquals
، إلا أن بعض المطوّرين يجدونها أسهل في القراءة.
قد يظهر تأكيد BDD على النحو التالي:
// A failure here would generate "Expect result to be an array that does include 42"
const result = await possibleMeaningsOfLife();
expect(result).to.be.an('array').that.does.include(42);
// or a simpler form
expect(result).toBe('array').toContainEqual(42);
// the same in assert might be
assert.typeOf(result, 'array', 'Expected the result to be an array');
assert.include(result, 42, 'Expected the result to include 42');
يعمل هذا النمط من التأكيدات بسبب أسلوب يسمى تسلسل الأساليب،
حيث يمكن الربط المستمر مع الكائن الذي تم إرجاعه من خلال expect
المزيد من استدعاءات الطريقة. بعض أجزاء المكالمة، بما في ذلك to.be
وthat.does
في المثال السابق، لا تحتوي على دالة ويتم تضمينها فقط لإجراء استدعاء
قد يكون أسهل في القراءة وربما إنشاء تعليق تلقائي إذا كان الاختبار
فشل. (جدير بالذكر أن expect
لا يدعم عادةً التعليق الاختياري، لأنه
ينبغي أن يصف التسلسل الفشل بوضوح).
تتوافق العديد من أُطر عمل الاختبار مع كل من التأكيدات Fluent/BDD والتأكيدات العادية. رائع، على سبيل المثال، يصدِّر كل من يمتلك تشاي نهجه ونهجه الأكثر إيجازًا إلى حد ما بشأن BDD. يا للهراء ومن ناحية أخرى، تتضمن فقط توقع تلقائيًا.
تجميع الاختبارات على مستوى الملفات
عند كتابة الاختبارات، نميل بالفعل إلى تقديم مجموعات ضمنية — بدلاً من من كون جميع الاختبارات في ملف واحد، من الشائع كتابة اختبارات عبر عدة الملفات. في الواقع، لا يعرف منفّذو الاختبار عادةً إلا أن الملف مخصّص للاختبار لأن فلتر محدد مسبقًا أو تعبير عادي - يمكن أن يتضمن، على سبيل المثال، جميع الملفات في مشروعك التي تنتهي بامتداد مثل "test.jsx". أو " .spec.ts" ("test" و".spec" بالإضافة إلى عدد من الإضافات الصالحة).
تميل اختبارات المكونات إلى وضعها في ملف نظير للمكون قيد الاختبار، كما هو الحال في بنية الدليل التالية:
وبالمثل، تميل اختبارات الوحدة إلى وضعها بجوار الرمز قيد الاختبار. قد تكون كل اختبارات شاملة في ملفها الخاص، وقد تكون اختبارات التكامل وضعها في مجلداتها الفريدة. يمكن أن تكون هذه الهياكل مفيدة عندما تتزايد حالات الاختبار المعقدة لتتطلب ملفات دعم غير اختبارية، مثل دعم المكتبات اللازمة للاختبار فقط.
تجميع الاختبارات ضمن الملفات
كما هو موضح في الأمثلة السابقة، من الشائع إجراء اختبارات داخل طلب
suite()
التي تجمع الاختبارات التي أعددتها باستخدام "test()
" ولا تشمل الأجنحة عادةً
للاختبارات نفسها، ولكنها تساعد في تنظيم البنية من خلال تجميع الاختبارات ذات الصلة
أو الأهداف من خلال استدعاء الطريقة التي تم تمريرها. بالنسبة إلى test()
، تصف الطريقة التي تم تمريرها
إجراءات الاختبار نفسه.
وكما هو الحال مع التأكيدات، هناك تكافؤ قياسي إلى حد ما في Fluent/BDD اختبارات التجميع. تتم مقارنة بعض الأمثلة النموذجية في التعليمة البرمجية التالية:
// traditional/TDD
suite('math tests', () => {
test('handle zero values', () => {
assert.equal(fibonacci(0), 0);
});
});
// Fluent/BDD
describe('math tests', () => {
it('should handle zero values', () => {
expect(fibonacci(0)).toBe(0);
});
})
في معظم أُطر العمل، تعمل السياستان suite
وdescribe
بشكل مشابه لسلوك test
it
، على عكس الاختلافات الكبيرة بين استخدام expect
وassert
لكتابة التأكيدات.
تتمتع الأدوات الأخرى بأساليب مختلفة قليلاً لترتيب الأجنحة والاختبارات. بالنسبة
على سبيل المثال، يتوافق مشغِّل الاختبار المدمَج في Node.js مع تضمين الاستدعاءات لـ test()
من أجل
إنشاء تسلسل هرمي اختباري ضمنيًا. ومع ذلك، فإن Vitest لا تسمح إلا بهذا النوع من
الدمج باستخدام suite()
ولن يتم تشغيل test()
تم تحديدها داخل test()
أخرى.
وكما هو الحال مع التأكيدات، تذكر أن الدمج الدقيق لتجميع الطرق التي توفّرها حزمة التكنولوجيا ليست بهذه الأهمية. ستغطي هذه الدورة التدريبية بل عليك معرفة كيفية تطبيقها على الأدوات المختلفة.
طرق مراحل النشاط
أحد أسباب تجميع اختباراتك، حتى بشكل ضمني على المستوى الأعلى داخل هو توفير طرق الإعداد والتقسيم التي تعمل لكل اختبار، أو بمجرد لمجموعة من الاختبارات. توفّر معظم أطر العمل أربع طرق:
لكل "test()" أو `it()` | مرة واحدة في الجناح | |
---|---|---|
قبل إجراء الاختبارات | `beforeafter()` | `beforeAll()` |
بعد إجراء الاختبارات | `afterafter()` | "afterAll() " |
على سبيل المثال، قد ترغب في الملء المسبق لقاعدة بيانات مستخدم افتراضية قبل كل واختباره، ومحوه بعد ذلك:
suite('user test', () => {
beforeEach(() => {
insertFakeUser('bob@example.com', 'hunter2');
});
afterEach(() => {
clearAllUsers();
});
test('bob can login', async () => { … });
test('alice can message bob', async () => { … });
});
ويمكن أن يفيد ذلك في تبسيط اختباراتك. يمكنك مشاركة خطوات الإعداد بدلاً من تكراره في كل اختبار. بالإضافة إلى ذلك، إذا كانت إلا أن التعليمات البرمجية الخاصة بالإعداد والإلغاء تؤدي إلى ظهور خطأ، وقد يشير إلى وجود المشكلات التي لا تنطوي على فشل الاختبارات نفسها.
نصائح عامة
فيما يلي بعض النصائح التي يجب تذكرها عند التفكير في هذه الأساسيات.
الأساسيات عبارة عن دليل
تذكر أن الأدوات والأساسيات هنا، وفي الصفحات القليلة التالية، لن تتطابق تمامًا مع Vitest أو Jest أو Mocha أو Web Test Runner أو أي إطار عمل محدد. وعلى الرغم من أننا استخدمنا Vitest كدليل عام، تأكد من تعيين وفقًا لإطار العمل الذي تختاره.
مزج التأكيدات ومطابقتها حسب الحاجة
تُعدّ الاختبارات في الأساس رموزًا برمجية قد تؤدي إلى حدوث أخطاء. سيوفر كل عداء
أساسي، ويرجح أن يكون test()
، لوصف حالات الاختبار المميزة.
وإذا كان مسار السحب هذا يوفّر أيضًا أدوات مساعدة التأكيد والترميز assert()
وexpect()
،
تذكر أن هذا الجزء يتعلق بالراحة ويمكنك تخطيه إذا
تحتاجون إليها. يمكنك تشغيل أي رمز قد يؤدي إلى حدوث خطأ، بما في ذلك الرموز
مكتبات التأكيدات أو عبارة if
قديمة الطراز.
يمكن أن يكون إعداد IDE منقذًا
إن التأكد من أن IDE، مثل VSCode، لديها إمكانية الوصول إلى الإكمال التلقائي
والوثائق المتعلقة بأدوات الاختبار التي تختارها أن تزيد إنتاجيتك. بالنسبة
هناك أكثر من 100 طريقة على assert
في تأكيد Chai
المكتبة، والحصول على وثائق
والخيار الأيمن تبدو مضمّنة.
يمكن أن يكون ذلك مهمًا بشكل خاص لبعض أطر العمل الاختبارية التي تملأ مساحة الاسم العامة مع طرق الاختبار الخاصة بهم. هذا فرق دقيق، لكنه ومن الممكن غالبًا استخدام اختبار المكتبات بدون استيرادها إذا كانت تتم إضافته تلقائيًا إلى مساحة الاسم العامة:
// some.test.js
test('using test as a global', () => { … });
ننصحك باستيراد أدوات المساعدة حتى لو كانت متوافقة تلقائيًا،
لأن ذلك يعطي IDE طريقة واضحة للبحث عن هذه الطرق. (قد يكون لديك
هذه المشكلة عند إنشاء React، حيث إن بعض قواعد التعليمات البرمجية
React
عامة، لكن بعضها لا ينطبق عليه ذلك، ويجب استيراده في جميع الملفات باستخدام
React.)
// some.test.js
import { test } from 'vitest';
test('using test as an import', () => { … });