التطوير من خلال الاختبار: تحسين المهارات

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

الصورة

لنقم بتنفيذ بسيط للمكدس في 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', () => { // By default, the stack is empty expect(stack.pop()).toBe('Empty') }) }) describe('Peek', () => { it('Should returns the element at the top of the stack', () => { stack.items = [1, 2, 3] let element = stack.peek() expect(element).toBe(3) }) it('Should return Empty if one tries to peek an empty stack', () => { // By default, the stack is empty expect(stack.peek()).toBe('Empty') }) }) }) 

إذا نظرت إلى الملفات ، سترى أنني استخدمت الشرط الثلاثي في ​​pop and peek. هذا ما أعيد بناءه. بدا التنفيذ القديم مثل هذا:

 if (this.isEmpty()) { return 'Empty' } return this.items.pop() 

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

شغّل الاختبار مرة أخرى:


الصورة

الاختبارات تحسن جودة الكود. آمل أن تفهم الآن الفوائد الكاملة للاختبار وأن تستخدم TDD في كثير من الأحيان.

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

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


All Articles