في الجزء السابق ، ركزت على حقيقة أن الفريق الذي أقوم بتطويره ينفذ السلوك الذي يمكن وصفه بهذا الاختبار:
it('execute should return promise', () => { request.configure(options); request.execute().then((result) => { expect(result.Id).toEqual(1); expect(result.Name).toEqual('Jack'); }); });
يبدو لي الآن أن الحصول على Promise
نتيجة لذلك ومعالجته ليس هو ما أريده تمامًا. سيكون من الأفضل أن يؤدي الفريق نفسه هذا العمل الروتيني ، وسيتم وضع النتيجة ، على سبيل المثال ، في مستودع Redux
. سأحاول إعادة كتابة الاختبار الحالي للتعبير عن توقعاتي الجديدة فيه:
const store = require('../../src/store'); const DbMock = require('../mocks/DbMock'); const db = new DbMock(); const Request = require('../../src/storage/Request'); const options = { tableName: 'users', query: { Id: 1 } }; let request = null; beforeEach(() => { request = new Request(db, store); }); it('should dispatch action if record exists', () => { request.configure(options); request.execute(() => { const user = store.getState().user; expect(user.Id).toEqual(options.query.Id); expect(user.Name).toEqual('Jack'); }); });
قد يكون هذا أكثر ملاءمة ، على الرغم من أنني الآن مضطر لتدريس طريقة execute
الخاصة بفئة Request
لتنفيذ طريقة رد الاتصال إذا قام المستخدم بتمريرها كوسيطة. لا يمكنك الاستغناء عنها ، لأنني أفترض داخل التطبيق استخدام مكالمات غير متزامنة ، ولا يمكن اختبار نتائج الاختبار إلا إذا كانوا مقتنعين بأن تنفيذها قد اكتمل.
علاوة على ذلك ... بالنظر إلى السطر الأول من التعليمات البرمجية ، أدرك أنه قبل أن أتمكن من العودة إلى تحرير رمز فئة Request
، أحتاج إلى إضافة حزمة Redux
إلى المشروع ، وتنفيذ
واحد على الأقل
وحزم المخفض بشكل منفصل في Store
. سيكون الاختبار الأول لعلبة التروس:
const reduce = require('../../src/reducers/user'); it('should return new state', () => { const user = { Id: 1, Name: 'Jack'}; const state = reduce(null, {type: 'USER', user }); expect(state).toEqual(user); });
../../src/reducers/user
الاختبارات وأوافق مع Jasmine
أنه بالإضافة إلى جميع الأخطاء السابقة ، ../../src/reducers/user
يتم العثور على وحدة تحمل الاسم ../../src/reducers/user
. لذلك ، سأكتبها ، خاصةً لأنها تعد بأن تكون صغيرة ويمكن التنبؤ بها بشكل رهيب:
const user = (state = null, action) => { switch (action.type) { case 'USER': return action.user; default: return state; } }; module.exports = user;
أركض الاختبارات ولا أرى تحسينات جذرية. هذا لأن الوحدة النمطية ../../src/store
، التي افترضت وجودها في اختبار فئة " Request
، لم ../../src/store
بعد. وليس هناك أيضًا اختبار له بعد. سأبدأ بالطبع مع الاختبار:
describe('store', () => { const store = require('../src/store'); it('should reduce USER', () => { const user = { Id: 1, Name: 'Jack' }; store.dispatch({type: 'USER', user }); const state = store.getState(); expect(state.user).toEqual(user); }); });
الاختبارات؟ هناك المزيد من التقارير حول عدم وجود وحدة store
، لذلك سأتعامل معها على الفور.
const createStore = require('redux').createStore; const combineReducers = require('redux').combineReducers; const user = require('./reducers/user'); const reducers = combineReducers({user}); const store = createStore(reducers); module.exports = store;
مع إدراك أنه سيكون لدي أكثر من صندوق تروس واحد ، فإنني أتقدم قليلاً في تنفيذ
combineReducers
طريقة combineReducers
الأساليب combineReducers
عند تجميعه. أقوم بإجراء الاختبارات مرة أخرى وأرى رسالة خطأ جديدة تخبرني أن طريقة execute
لفئة Request
بي لا تعمل كما يوحي اختباري. نتيجة لطريقة التنفيذ ، لا يظهر سجل المستخدم في
. حان الوقت لإعادة تشكيل فئة Request
.
أذكر كيف يبدو الآن اختبار طريقة execute
:
it('should dispatch action if record exists', () => { request.configure(options); request.execute(() => { const user = store.getState().user; expect(user.Id).toEqual(options.query.Id); expect(user.Name).toEqual('Jack'); }); });
وسأصلح شفرة الطريقة نفسها بحيث يكون للاختبار فرصة للتنفيذ:
execute(callback){ const table = this.db.Model.extend({ tableName: this.options.tableName }); table.where(this.options.query).fetch().then((item) => { this.store.dispatch({ type: 'USER', user: item }); if(typeof callback === 'function') callback(); }); }
npm test
وحدة التحكم في npm test
و ... Bingo! لقد تعلمت طلبي ليس فقط تلقي البيانات من قاعدة البيانات ، ولكن أيضًا حفظها في
لعملية المعالجة المستقبلية ، بحيث يمكن للعمليات اللاحقة تلقي هذه البيانات دون مشاكل.
لكن! يستطيع معالجي إرسال نوع واحد فقط من الإجراءات في
، وهذا يحد بشكل كبير من قدراته. لكنني أرغب في استخدام هذا الرمز مرارًا وتكرارًا كلما احتجت إلى استخراج بعض السجلات من قاعدة البيانات وإرسالها إلى خلية
لمزيد من المعالجة تحت المفتاح الذي أحتاج إليه. ولذا أبدأ إعادة اختبار الاختبار مرة أخرى:
const options = { tableName: 'users', query: { Id : 1 }, success (result, store) { const type = 'USER'; const action = { type , user: result }; store.dispatch(action); } }; it('should dispatch action if record exists', () => { request.configure(options); request.execute(() => { const user = store.getState().user; expect(user.Id).toEqual(options.query.Id); expect(user.Name).toEqual('Jack'); }); });
حدث لي أنه سيكون من الجيد حفظ فئة Request
من وظيفة غير عادية لمعالجة نتائج الاستعلام. Request
نصف دلالة هو طلب. استيفاء الطلب ، وتلقى الجواب ، اكتملت المهمة ، واحترام مبدأ مسؤولية الفصل وحده. ودع شخصًا مدربًا بشكل خاص في هذه العملية يتعامل مع النتائج ، التي من المفترض أن تكون مسؤوليتها الوحيدة هي نسخة معينة من المعالجة نفسها. لذلك ، قررت نقل طريقة success
إلى إعدادات الطلب ، وهي مهمة معالجة البيانات التي تم إرجاعها بنجاح بواسطة الطلب.
الاختبارات ، والآن لا يمكنك تشغيل. أنا أفهم ذلك فكريا. لم أصلح أي شيء في الاختبار نفسه ولم أغير أي شيء في التطبيق ، ويجب أن تستمر الاختبارات في العمل بنجاح. لكن عاطفياً ، أحتاج إلى تشغيل أمر npm test
، npm test
في تحرير execute
طريقة التنفيذ الخاصة بي في فئة Request
لاستبدال السطر باستدعاء store.dispatch(...)
بالسطر مع استدعاء this.options.success(...)
:
execute(callback){ const table = this.db.Model.extend({ tableName: this.options.tableName }); table.where(this.options.query).fetch().then((item) => { this.options.success(item, this.store); if(typeof callback !== 'undefined') callback(); }); }
أركض الاختبارات. فويلا! الاختبارات خضراء تماما. الحياة تتحسن! ما التالي؟ على الفور أرى أنك بحاجة إلى تغيير عنوان الاختبار ، لأنه لا يتوافق تمامًا مع الواقع. لا يتحقق الاختبار من أن إرسال الطريقة يحدث نتيجة للطلب ، ولكن الاستعلام يقوم بتحديث الحالة في الحاوية. لذلك ، أقوم بتغيير عنوان الاختبار إلى ... جيدًا ، على سبيل المثال:
it('should update store user state if record exists', () => { request.configure(options); request.execute(() => { const user = store.getState().user; expect(user.Id).toEqual(options.query.Id); expect(user.Name).toEqual('Jack'); }); });
ما التالي؟ ثم أعتقد أن الوقت قد حان لإيلاء الاهتمام وليس للحالة ، فبدلاً من البيانات المطلوبة ، سيعود طلبي خطأ. هذا ليس مثل هذا السيناريو المستحيل. صحيح؟ والشيء الرئيسي هو أنه في هذه الحالة ، لن أكون قادرًا على إعداد وإرسال مجموعة البيانات المطلوبة إلى مشغل KYC الخاص بي ، من أجل التكامل الذي أكتب به كل هذا الرمز. هل هذا صحيح؟ اذن أولاً ، سأكتب اختبارًا:
it('should add item to store error state', () => { options.query = { Id: 555 }; options.error = (error, store) => { const type = 'ERROR'; const action = { type, error }; store.dispatch(action); }; request.configure(options); request.execute(() => { const error = store.getState().error; expect(Array.isArray(error)).toBeTruthy(); expect(error.length).toEqual(1); expect(error[0].message).toEqual('Something goes wrong!'); }); });
لا أعرف ما إذا كان هيكل الاختبار يوضح أنني قررت توفير الوقت والمال وكتابة الحد الأدنى من الشفرة للتحقق مما إذا كان الطلب يُرجع خطأً؟ لم ير؟
لا أريد تضييع الوقت في ترميز تطبيقات إضافية لـ TableMock
، والتي TableMock
الأخطاء. قررت أنه في الوقت الحالي ، سيكون عدد من الإنشاءات الشرطية في التطبيق الحالي كافياً بالنسبة لي ، واقترح تعديل ذلك من خلال معلمات query
. لذلك افتراضاتي هي:
- إذا كان الرقم
Id
في استعلام options.query
هو 1 ، فعندئذٍ يُرجع الجدول الزائف دومًا Promise
المسموح به مع السجل الأول من المجموعة. - إذا كان الرقم
Id
في استعلام options.query
هو 555 ، فعندئذٍ يُرجع الجدول الزائف دومًا وعدًا مرفوضًا مع وجود مثيل Error
بداخله ، ومحتوى message
خطأ ما! .
بالطبع ، هذا أبعد ما يكون عن المثالية. سيكون أكثر قابلية للقراءة وملاءمة للإدراك لتطبيق مثيلات DbMock
المقابلة ، وكذلك ، على سبيل المثال ، HealthyDbMock
، FaultyDbMock
، EmptyDbMock
. من الأسماء التي يتضح على الفور أن الأول سيعمل دائمًا بشكل صحيح ، والثاني سيعمل دائمًا بشكل غير صحيح ، أما بالنسبة للثالث ، فيمكننا افتراض أنه سيعود دائمًا null
بدلاً من النتيجة. ربما ، بعد التحقق من افتراضاتي الأولى بالطريقة المذكورة أعلاه ، والتي يبدو لي أنها تستغرق وقتًا كحد أدنى ، سوف أشارك DbMock
تنفيذ حالتين إضافيتين من DbMock
السلوك غير الصحي .
أركض الاختبارات. أحصل على الخطأ المتوقع في نقص الخاصية التي أحتاجها في
و ... أنا أكتب اختبارًا آخر. هذه المرة لصندوق التروس الذي سيتعامل مع الإجراءات مع نوع ERROR
.
describe('error', () => { const reduce = require('../../src/reducers/error'); it('should add error to state array', () => { const type = 'ERROR'; const error = new Error('Oooops!'); const state = reduce(undefined, { type, error }); expect(Array.isArray(state)).toBeTruthy(); expect(state.length).toEqual(1); expect(state.includes(error)).toBeTruthy(); }); });
تشغيل الاختبارات مرة أخرى. كل شيء متوقع ، تمت إضافة واحد آخر إلى الأخطاء الموجودة. أنا أدرك
:
const error = (state = [], action) => { switch (action.type) { case 'ERROR': return state.concat([action.error]); default: return state; } }; module.exports = error;
أقوم بإجراء الاختبارات مرة أخرى. يعمل المخفض الجديد كما هو متوقع ، لكن ما زلت بحاجة للتأكد من أنه متصل بالمستودع ويعالج الإجراءات التي تم إعدادها من أجله. لذلك ، أكتب اختبارًا إضافيًا لمجموعة اختبار التخزين الحالية:
it('should reduce error', () => { const type = 'ERROR'; const error = new Error('Oooops!'); store.dispatch({ type, error }); const state = store.getState(); expect(Array.isArray(state.error)).toBeTruthy(); expect(state.error.length).toEqual(1); expect(state.error.includes(error)).toBeTruthy(); });
أركض الاختبارات. كل شيء متوقع. لا يؤدي إجراء من النوع ERROR
معالجة التخزين الموجود. تعديل كود تهيئة مستودع التخزين الحالي:
const createStore = require('redux').createStore; const combineReducers = require('redux').combineReducers; const user = require('./reducers/user'); const error = require('./reducers/error'); const reducers = combineReducers({ error, user }); const store = createStore(reducers); module.exports = store;
للمرة مائة رمي شبكة ... جيد جدا! المخزون الآن يتراكم رسائل الخطأ المستلمة في خاصية حاوية منفصلة.
الآن سوف أعرض اثنين من التركيبات الشرطية في تطبيق TableMock
الحالي ، TableMock
بعض الاستعلامات بهذه الطريقة ، وإرجاع خطأ. يبدو الرمز المحدث كما يلي:
class TableMock { constructor(array){ this.container = array; } where(query){ this.query = query; return this; } fetch(){ return new Promise((resolve, reject) => { if(this.query.Id === 1) return resolve(this.container[0]); if(this.query.Id === 555) return reject(new Error('Something goes wrong!')); }); } } module.exports = TableMock;
أقوم بإجراء الاختبارات وأحصل على رسالة حول الرفض غير المعالج لـ Promise
في طريقة execute
لفئة Request
. أضف الكود المفقود:
execute(callback){ const table = this.db.Model.extend({ tableName: this.options.tableName }); table.where(this.options.query).fetch().then((item) => { this.options.success(item, this.store); if(typeof callback === 'function') callback(); }).catch((error) => { this.options.error(error, this.store); if(typeof callback === 'function') callback(); }); }
وأجري الاختبارات مرة أخرى. و ؟؟ في الواقع لا يوجد اختبار لطريقة execute
لفئة Request
، هذا واحد:
it('should add item to store error state', () => { options.query = { Id: 555 }; options.error = (error, store) => { const type = 'ERROR'; const action = { type, error }; store.dispatch(action); }; request.configure(options); request.execute(() => { const error = store.getState().error; expect(Array.isArray(error)).toBeTruthy(); expect(error.length).toEqual(1); expect(error[0].message).toEqual('Something goes wrong!'); }); });
أكمل بنجاح. لذلك يمكن اعتبار وظيفة الاستعلام من حيث معالجة الأخطاء مطبقة. سقط اختبار آخر ، أحد الاختبارات الذي يتفحص المستودع لمعالجة الأخطاء. المشكلة هي أن وحدة تنفيذ التخزين الخاصة بي تقوم بإرجاع نفس مثيل التخزين الثابت لجميع المستهلكين في جميع الاختبارات. في هذا الصدد ، بما أن إرسال الأخطاء يحدث بالفعل في اختبارين ، في أحدهما لا يتحقق التحقق من عدد الأخطاء في الحاوية بالضرورة. لأنه بحلول وقت بدء الاختبار ، يوجد بالفعل خطأ واحد في الحاوية ويضاف خطأ آخر هناك أثناء إطلاق الاختبار. حتى هنا هذا الرمز:
const error = store.getState().error; expect(error.length).toEqual(1);
يلقي استثناءًا ، حيث يبلغ أن التعبير عن error.length
هو فعليًا 2 ، وليس 1. هذه المشكلة الآن error.length
ببساطة عن طريق نقل رمز تهيئة التخزين مباشرةً إلى رمز تهيئة اختبار التخزين:
describe('store', () => { const createStore = require('redux').createStore; const combineReducers = require('redux').combineReducers; const user = require('../src/reducers/user'); const error = require('../src/reducers/error'); const reducers = combineReducers({ error, user }); const store = createStore(reducers); it('should reduce USER', () => { const user = { Id: 1, Name: 'Jack' }; store.dispatch({type: 'USER', user }); const state = store.getState(); expect(state.user).toEqual(user); }); it('should reduce error', () => { const type = 'ERROR'; const error = new Error('Oooops!'); store.dispatch({ type, error }); const state = store.getState(); expect(Array.isArray(state.error)).toBeTruthy(); expect(state.error.length).toEqual(1); expect(state.error.includes(error)).toBeTruthy(); }); });
يبدو رمز التهيئة للاختبار منتفخًا بعض الشيء ، لكن يمكنني العودة إلى إعادة بيعه لاحقًا.
أركض الاختبارات. فويلا! تم الانتهاء من جميع الاختبارات ويمكنك أن تأخذ استراحة.