في المقالة السابقة ، درسنا الجوانب النظرية. حان الوقت لبدء الممارسة.

لنقم بتنفيذ بسيط للمكدس في JavaScript باستخدام التطوير من خلال الاختبار.
المكدس - هيكل بيانات منظم وفقًا لمبدأ LIFO: Last In ، First Out. هناك ثلاث عمليات رئيسية على المكدس:
دفع : إضافة عنصر
pop : حذف عنصر
نظرة خاطفة : إضافة عنصر الرأس
قم بإنشاء فصل دراسي وسمه المكدس. لتعقيد المهمة ، افترض أن المكدس لديه سعة ثابتة. فيما يلي الخصائص ووظائف التنفيذ لمكدسنا:
العناصر : العناصر الموجودة على المكدس. سنستخدم مصفوفة لتنفيذ المكدس.
القدرة :
سعة المكدس.
isEmpty () : إرجاع true إذا كان المكدس فارغًا ، أو false.
isFull () : إرجاع true إذا وصلت الحزمة إلى السعة القصوى ، أي عندما لا يمكنك إضافة عنصر آخر. خلاف ذلك ، إرجاع خطأ.
push (element) : يضيف عنصرًا. إرجاع كامل إذا كان المكدس ممتلئًا.
pop : يزيل العنصر. إرجاع فارغ إذا كان المكدس فارغًا.
نظرة خاطفة () : يضيف عنصر الرأس.
سنقوم بإنشاء ملفين
stack.js و
stack.spec.js . لقد استخدمت امتداد
.spec.js لأنني
معتاد عليه ، ولكن يمكنك استخدام .test.js أو إعطائه اسمًا مختلفًا ونقله إلى
__tests__ .
نظرًا لأننا نمارس التطوير من خلال الاختبار ، سنكتب اختبارًا فاشلًا.
أولاً ، تحقق من المنشئ. لاختبار الملف ، تحتاج إلى استيراد ملف المكدس:
const Stack = require('./stack')
بالنسبة لأولئك المهتمين بسبب عدم استخدامي
للاستيراد هنا ، لا يدعم أحدث إصدار ثابت من Node.js هذه الميزة اليوم. يمكنني أن أضيف بابل ، لكني لا أريد أن أفرط في تحميلك.
عندما تقوم باختبار فصل دراسي أو وظيفة ، قم بإجراء اختبار ووصف الملف أو الفصل الدراسي الذي تختبره. هذا عن المكدس:
describe('Stack', () => { })
ثم نحتاج إلى التحقق من أنه عند تهيئة المكدس ، يتم إنشاء صفيف فارغ ، ونقوم بتعيين السعة الصحيحة. لذا نكتب الاختبار التالي:
it('Should constructs the stack with a given capacity', () => { let stack = new Stack(3) expect(stack.items).toEqual([]) expect(stack.capacity).toBe(3) })
لاحظ أننا نستخدم
toEqual ولا نستخدم
toBe لـ
stack.items ، لأنها لا تشير إلى نفس المصفوفة.
الآن قم بتشغيل
stack.spec.js اختبار الغزل . نقوم بتشغيل
Jest في ملف معين لأننا لا نريد أن تتلف الاختبارات الأخرى. ها هي النتيجة:
المكدس ليس مُنشئ . بالطبع ما زلنا لم ننشئ مجموعة المكدس الخاصة بنا ولم نصنع منشئًا.
في
stack.js ، قم بإنشاء الفصل الدراسي
ومنشئ الصف وتصديره:
class Stack { constructor() { } } module.exports = Stack
شغّل الاختبار مرة أخرى:

نظرًا لأننا لم نقم بتعيين العناصر في المُنشئ ، توقع
Jest أن تكون العناصر الموجودة في الصفيف مساوية لـ [] ، ولكن لم يتم تحديدها. ثم يجب عليك تهيئة العناصر:
constructor() { this.items = [] }
إذا قمت بتشغيل الاختبار مرة أخرى ، فستحصل على نفس الخطأ
للسعة ، لذلك ستحتاج أيضًا إلى تعيين السعة:
constructor(capacity) { this.items = [] this.capacity = capacity }
قم بإجراء الاختبار:

نعم فعلا!
مرت الاختبار . هنا ما هو TDD. آمل أن يكون الاختبار الآن أكثر منطقية بالنسبة لك! تواصل؟
فارغ
لاختبار
isEmpty ، سنقوم بتهيئة مكدس فارغ ، واختبار ما إذا كانت isEmpty ترجع true ، وإضافة عنصر والتحقق منه مرة أخرى.
it('Should have an isEmpty function that returns true if the stack is empty and false otherwise', () => { let stack = new Stack(3) expect(stack.isEmpty()).toBe(true) stack.items.push(2) expect(stack.isEmpty()).toBe(false) })
عند تشغيل الاختبار ، يجب أن تحصل على الخطأ التالي:
TypeError: stack.isEmpty is not a function
لحل هذه المشكلة ، نحتاج إلى إنشاء
isEmpty داخل فئة
Stack :
isEmpty () { }
إذا قمت بإجراء الاختبار ، يجب أن تحصل على خطأ آخر:
Expected: true Received: undefined
لم تتم إضافة أي شيء إلى
فارغ .
المكدس فارغ إذا لم يكن هناك عناصر فيه:
isEmpty () { return this.items.length === 0 }
isFull
هذا هو نفس
isEmpty ، حيث يختبر هذا التمرين هذه الوظيفة باستخدام TDD. ستجد الحل في نهاية المقالة.
دفع
هنا نحتاج إلى اختبار
ثلاثة أشياء:
- يجب إضافة عنصر جديد إلى أعلى المكدس.
- تقوم دالة push بإرجاع "ممتلئ" إذا كانت الحزمة مكدسة ؛
- يجب إعادة عنصر تمت إضافته مؤخرًا.
إنشاء كتلة أخرى باستخدام
وصف للدفع . نضع هذه الكتلة داخل الكتلة الرئيسية.
describe('Stack.push', () => { })
أضف البند
قم بإنشاء رصة جديدة وإضافة عنصر. يجب أن يكون العنصر الأخير في صفيف
العناصر هو العنصر الذي أضفته للتو.
describe('Stack.push', () => { it('Should add a new element on top of the stack', () => { let stack = new Stack(3) stack.push(2) expect(stack.items[stack.items.length - 1]).toBe(2) }) })
إذا قمت بإجراء الاختبار ، سترى أن
الدفع غير محدد وهذا أمر طبيعي. ستحتاج
push إلى معلمة لإضافة شيء إلى المكدس:
push (element) { this.items.push(element) }
مرت الاختبار مرة أخرى. هل لاحظت شيئًا؟ نحتفظ بنسخة من هذا السطر:
let stack = new Stack(3)
هذا أمر مزعج للغاية. لحسن الحظ ، لدينا طريقة
beforeEach تسمح لك بإجراء بعض التخصيص قبل كل تشغيل تجريبي. لماذا لا تستغل هذا؟
let stack beforeEach(() => { stack = new Stack(3) })
هام:
يجب التصريح عن المكدس من قبل قبل
كل . في الواقع ، إذا قمت بتعريفه في طريقة
beforeEach ، فلن يتم تحديد متغير المكدس في جميع الاختبارات لأنه ليس في المنطقة الصحيحة.
أكثر أهمية
من المهم: يجب علينا أيضًا إنشاء طريقة
afterEach . سيتم الآن استخدام مثيل المكدس لجميع الاختبارات. هذا قد يسبب بعض الصعوبات. فورًا قبل
قبل إضافة كل هذه الطريقة:
afterEach(() => { stack.items = [] })
يمكنك الآن إزالة تهيئة المكدس في جميع الاختبارات. هنا هو ملف الاختبار الكامل:
const Stack = require('./stack') describe('Stack', () => { let stack beforeEach(() => { stack = new Stack(3) }) afterEach(() => { stack.items = [] }) it('Should constructs the stack with a given capacity', () => { expect(stack.items).toEqual([]) expect(stack.capacity).toBe(3) }) it('Should have an isEmpty function that returns true if the stack is empty and false otherwise', () => { stack.items.push(2) expect(stack.isEmpty()).toBe(false) }) describe('Stack.push', () => { it('Should add a new element on top of the stack', () => { stack.push(2) expect(stack.items[stack.items.length - 1]).toBe(2) }) }) })
اختبار القيمة المعادة
هناك اختبار:
it('Should return the new element pushed at the top of the stack', () => { let elementPushed = stack.push(2) expect(elementPushed).toBe(2) })
عند إجراء الاختبار ، ستحصل على:
Expected: 2 Received: undefined
لا شيء يعود في الداخل! يجب علينا إصلاح هذا:
push (element) { this.items.push(element) return element }
قم بالعودة بالكامل إذا كان المكدس ممتلئًا
في هذا الاختبار ، نحتاج أولاً إلى ملء المكدس ، وإضافة عنصر ، والتأكد من عدم إضافة أي شيء غير ضروري إلى المكدس وأن القيمة
المرتجعة ممتلئة .
it('Should return full if one tries to push at the top of the stack while it is full', () => { stack.items = [1, 2, 3] let element = stack.push(4) expect(stack.items[stack.items.length - 1]).toBe(3) expect(element).toBe('Full') })
سترى هذا الخطأ عند تشغيل الاختبار:
Expected: 3 Received: 4
لذلك تمت إضافة العنصر. هذا ما أردناه. تحتاج أولاً إلى التحقق مما إذا كان المكدس ممتلئًا قبل إضافة شيء ما:
push (element) { if (this.isFull()) { return 'Full' } this.items.push(element) return element }
مرت الاختبار.التمرين: البوب والنظرةحان الوقت للممارسة. اختبار وتنفيذ
pop and
peek .
نصائح:
- البوب مشابه جدًا للدفع
- نظرة خاطفة هي أيضا مثل دفع
- حتى الآن ، لم نعيد صياغة الرمز ، لأن هذا لم يكن ضروريًا. قد تكون هناك طريقة في هذه الوظائف لإعادة بناء الكود بعد كتابة الاختبارات واجتيازها. لا تقلق ، تغيير الكود ، الاختبارات مطلوبة لذلك.
لا تنظر إلى الحل أدناه دون محاولة القيام بذلك بنفسك. الطريقة الوحيدة للتقدم هي المحاولة والتجربة والممارسة.
الحل
حسنا ، كيف هو التمرين؟ هل فعلتها؟ إذا لم يكن كذلك ، لا تثبط عزيمتك. يستغرق الاختبار وقتًا وجهدًا.
class Stack { constructor (capacity) { this.items = [] this.capacity = capacity } isEmpty () { return this.items.length === 0 } isFull () { return this.items.length === this.capacity } push (element) { if (this.isFull()) { return 'Full' } this.items.push(element) return element } pop () { return this.isEmpty() ? 'Empty' : this.items.pop() } peek () { return this.isEmpty() ? 'Empty' : this.items[this.items.length - 1] } } module.exports = Stack const Stack = require('./stack') describe('Stack', () => { let stack beforeEach(() => { stack = new Stack(3) }) afterEach(() => { stack.items = [] }) it('Should construct the stack with a given capacity', () => { expect(stack.items).toEqual([]) expect(stack.capacity).toBe(3) }) it('Should have an isEmpty function that returns true if the stack is empty and false otherwise', () => { expect(stack.isEmpty()).toBe(true) stack.items.push(2) expect(stack.isEmpty()).toBe(false) }) it('Should have an isFull function that returns true if the stack is full and false otherwise', () => { expect(stack.isFull()).toBe(false) stack.items = [4, 5, 6] expect(stack.isFull()).toBe(true) }) describe('Push', () => { it('Should add a new element on top of the stack', () => { stack.push(2) expect(stack.items[stack.items.length - 1]).toBe(2) }) it('Should return the new element pushed at the top of the stack', () => { let elementPushed = stack.push(2) expect(elementPushed).toBe(2) }) it('Should return full if one tries to push at the top of the stack while it is full', () => { stack.items = [1, 2, 3] let element = stack.push(4) expect(stack.items[stack.items.length - 1]).toBe(3) expect(element).toBe('Full') }) }) describe('Pop', () => { it('Should removes the last element at the top of a stack', () => { stack.items = [1, 2, 3] stack.pop() expect(stack.items).toEqual([1, 2]) }) it('Should returns the element that have been just removed', () => { stack.items = [1, 2, 3] let element = stack.pop() expect(element).toBe(3) }) it('Should return Empty if one tries to pop an empty stack', () => {
إذا نظرت إلى الملفات ، سترى أنني استخدمت الشرط الثلاثي في pop and peek. هذا ما أعيد بناءه. بدا التنفيذ القديم مثل هذا:
if (this.isEmpty()) { return 'Empty' } return this.items.pop()
يتيح لنا التطوير من خلال الاختبار إعادة برمجة الرمز بعد كتابة الاختبارات ، وقد وجدت تنفيذًا أقصر ، دون القلق بشأن سلوك اختباراتي.
شغّل الاختبار مرة أخرى:

الاختبارات تحسن جودة الكود. آمل أن تفهم الآن الفوائد الكاملة للاختبار وأن تستخدم TDD في كثير من الأحيان.
هل أقنعناك بأن الاختبار مهم ويحسن جودة الشفرة؟ بدلاً من ذلك ، اقرأ الجزء 2 من المقالة حول التطوير من خلال الاختبار. ومن لم يقرأ الأول ،
اتبع الرابط .