التحول من اختبار الإعادة الملحمة

يوفر إطار عمل redux-saga مجموعة من الأنماط المثيرة للاهتمام للعمل مع الآثار الجانبية ، ولكن ، مثل مطوري المؤسسات الدامياء الحقيقيين ، نحتاج إلى تغطية الكود بالكامل مع الاختبارات. دعونا معرفة كيف سنقوم باختبار الملحمات لدينا.



خذ أبسط الفرس كمثال. سيكون تدفق البيانات ومعنى التطبيق:

  1. المستخدم الوخزة زر.
  2. يتم إرسال طلب إلى الخادم ، مع العلم أن المستخدم قد ضغط زر.
  3. يعرض الخادم عدد النقرات التي تمت.
  4. تسجل الحالة عدد النقرات التي تمت.
  5. يتم تحديث واجهة المستخدم ، ويرى المستخدم أن عدد النقرات قد زاد.
  6. ...
  7. PROFIT.

في عملنا ، نستخدم Typescript ، لذلك ستكون جميع الأمثلة في هذه اللغة.

كما قد تكون خمنت بالفعل ، سوف ننفذ كل هذا مع redux-saga . إليك رمز ملف sagas بأكمله:

 export function* processClick() { const result = yield call(ServerApi.SendClick) yield put(Actions.clickSuccess(result)) } export function* watchClick() { yield takeEvery(ActionTypes.CLICK, processClick) } 

في هذا المثال البسيط ، نعلن عن عملية saga processClick ، والتي تعالج مباشرة الإجراء و the watchClick ، والتي تنشئ حلقة لمعالجة action' .

مولدات


لذلك لدينا أبسط الملحمة. يرسل طلبًا إلى الخادم ( call) ، ويستقبل النتيجة ويمررها إلى المخفض ( put) الموضع ( put) . نحن بحاجة إلى اختبار ما إذا كانت الملحمة تنقل ما تحصل عليه بالضبط من الخادم. لنبدأ.

للاختبار ، نحتاج إلى قفل مكالمة الخادم والتحقق بطريقة أو بأخرى مما إذا كان ما جاء بالضبط من الخادم قد دخل إلى المخفض.

نظرًا لأن sagas عبارة عن وظائف منشئ ، فإن الطريقة next() الموجودة في النموذج الأولي للمولد ، ستكون الطريقة الأكثر وضوحًا للاختبار. عند استخدام هذه الطريقة ، لدينا الفرصة لتلقي القيمة التالية من المولد ونقل القيمة إلى المولد. وبالتالي ، نخرج من الصندوق الفرصة للحصول على مكالمات مبللة. ولكن هل كل شيء وردية؟ إليك اختبار كتبته على المولدات العارية:

 it('should increment click counter (behaviour test)', () => { const saga = processClick() expect(saga.next().value).toEqual(call(ServerApi.SendClick)) expect(saga.next(10).value).toEqual(put(Actions.clickSuccess(10))) }) 

كان الاختبار موجزا ، ولكن ماذا اختبار؟ في الحقيقة ، إنه ببساطة يكرر رمز طريقة الملحمة ، أي أن أي تغيير في الملحمة سوف يضطر إلى تغيير الاختبار.

مثل هذا الاختبار لا يساعد في التنمية.

مسترجع الملحمة اختبار خطة


بعد مواجهة هذه المشكلة ، قررنا google ذلك وأدركنا فجأة أننا لسنا الوحيدين وبعيداً عن الأول. مباشرة في الوثائق الخاصة بـ redux-saga يقدم المطورون نظرة على العديد من المكتبات التي تم إنشاؤها خصيصًا لإرضاء المعجبين بالاختبار.

من القائمة المقترحة ، أخذنا مكتبة redux-saga-test-plan الإعادة. إليك رمز الإصدار الأول من الاختبار الذي كتبته به:

 it('should increment click counter (behaviour test with test-plan)', () => { return expectSaga(processClick) .provide([ call(ServerApi.SendClick), 2] ]) .dispatch(Actions.click()) .call(ServerApi.SendClick) .put(Actions.clickSuccess(2)) .run() }) 

مُنشئ الاختبار في redux-saga-test-plan هو redux-saga-test-plan expectSaga ، والتي تُرجع الواجهة التي تصف الاختبار. يتم تمرير ملحمة الاختبار ( processClick من القائمة الأولى) إلى الوظيفة نفسها.

باستخدام طريقة التقديم ، يمكنك حظر مكالمات الخادم أو التبعيات الأخرى. StaticProvider' مجموعة من StaticProvider' ، والتي تصف الطريقة التي يجب أن تعود.

في كتلة Act ، لدينا طريقة واحدة - dispatch . يتم تمرير إجراء إلى ذلك ، والتي سوف تستجيب الملحمة.

تتكون كتلة assert من أساليب call put ، والتي تتحقق مما إذا كانت التأثيرات المقابلة قد حدثت أثناء عمل الملحمة.

كل هذا ينتهي بالطريقة run() . هذه الطريقة تدير الاختبار مباشرة.

مزايا هذا النهج:

  • إنه يتحقق مما إذا كانت الطريقة تسمى ، وليس تسلسل المكالمات ؛
  • وصف moki بوضوح الوظيفة التي تصبح رطبة وما الذي يتم إرجاعه.

ومع ذلك ، هناك عمل للقيام به:

  • هناك المزيد من التعليمات البرمجية ؛
  • الاختبار صعب القراءة ؛
  • هذا اختبار للسلوك ، مما يعني أنه لا يزال مرتبطًا بتطبيق الملحمة.

السكتات الدماغية الأخيرة اثنين


اختبار الحالة


أولاً ، نصلح آخر اختبار: نجري اختبار حالة من اختبار للسلوك. حقيقة أن test-plan تسمح لك بتعيين state الأولية reducer ، والتي ينبغي أن تستجيب للآثار put الناتجة عن الملحمة ، سوف تساعدنا في هذا. يبدو مثل هذا:

 it('should increment click counter (state test with test-plan)', () => { const initialState = { clickCount: 11, return expectSaga(processClick) .provide([ call(ServerApi.SendClick), 14] ]) .withReducer(rootReducer, initialState) .dispatch(Actions.click()) .run() .then(result => expect(result.storeState.clickCount).toBe(14)) }) 

في هذا الاختبار ، لم نعد نتحقق من أنه قد تم تشغيل أي تأثيرات. نتحقق من الحالة النهائية بعد التنفيذ ، وهذا جيد.

لقد نجحنا في التخلص من تطبيق الملحمة ، فلنحاول الآن جعل الاختبار أكثر قابلية للفهم. يكون ذلك سهلاً إذا استبدلت then() بـ async/await :

 it('should increment click counter (state test with test-plan async-way)', async () => { const initialState = { clickCount: 11, } const saga = expectSaga(processClick) .provide([ call(ServerApi.SendClick), 14] ]) .withReducer(rootReducer, initialState) const result = await saga.dispatch(Actions.click()).run() expect(result.storeState.clickCount).toBe(14) }) 

اختبارات التكامل


ولكن ماذا لو حصلنا أيضًا على عملية النقر العكسي (دعنا نسميها إلغاء النقر) ، والآن يبدو ملف الترهل لدينا كما يلي:

 export function* processClick() { const result = yield call(ServerApi.SendClick) yield put(Actions.clickSuccess(result)) } export function* processUnclick() { const result = yield call(ServerApi.SendUnclick) yield put(Actions.clickSuccess(result)) } function* watchClick() { yield takeEvery(ActionTypes.CLICK, processClick) } function* watchUnclick() { yield takeEvery(ActionTypes.UNCLICK, processUnclick) } export default function* mainSaga() { yield all([watchClick(), watchUnclick()]) } 

لنفترض أننا بحاجة إلى اختبار أنه عند استدعاء إجراءات النقر وإلغاء النقر في الحالة ، تتم كتابة نتيجة الرحلة الأخيرة إلى الخادم إلى الحالة. يمكن أيضًا إجراء مثل هذا الاختبار بسهولة باستخدام redux-saga-test-plan :

 it('should change click counter (integration test)', async () => { const initialState = { clickCount: 11, } const saga = expectSaga(mainSaga) .provide([ call(ServerApi.SendClick), 14], call(ServerApi.SendUnclick), 18] ]) .withReducer(rootReducer, initialState) const result = await saga .dispatch(Actions.click()) .dispatch(Actions.unclick()) .run() expect(result.storeState.clickCount).toBe(18) }) 

يرجى ملاحظة أننا الآن mainSaga ، وليس معالجات mainSaga الفردية.

ومع ذلك ، إذا أجرينا هذا الاختبار كما هو ، فسوف نحصل على Vorning:



هذا بسبب تأثير takeEvery - هذا هو حلقة معالجة الرسائل التي ستعمل بينما تطبيقنا مفتوح. وفقًا لذلك ، فإن الاختبار الذي يُطلق عليه takeEvery يكون قادرًا على إكمال العمل دون مساعدة خارجية ، كما أن redux-saga-test-plan إعادة redux-saga-test-plan تنهي هذه الآثار بالقوة بعد 250 مللي ثانية بعد بدء الاختبار. يمكن تغيير هذه المهلة عن طريق الاتصال expectSaga.DEFAULT_TIMEOUT = 50.
إذا كنت لا ترغب في تلقي مثل هذه vorings ، واحدة لكل اختبار ذات تأثير معقد ، ببساطة استخدم طريقة silentRun() بدلاً من طريقة run() .



المزالق


حيث دون المزالق ... في وقت كتابة هذا التقرير ، أحدث إصدار من redux-saga: 1.0.2. في نفس الوقت ، redux-saga-test-plan قادرة على العمل معها فقط على JS.

إذا كنت تريد برنامج TypeScript ، فيجب عليك تثبيت الإصدار من القناة التجريبية:
npm install redux-saga-test-plan@beta
وإيقاف الاختبارات من البناء. للقيام بذلك ، في ملف tsconfig.json ، تحتاج إلى تحديد المسار "./src/**/*.spec.ts" في حقل "الاستبعاد".

على الرغم من ذلك ، فإننا نعتبر redux-saga-test-plan أفضل مكتبة لاختبار redux-saga . إذا كان لديك رد redux-saga في مشروعك ، فربما يكون هذا اختيارًا جيدًا لك.

شفرة المصدر للمثال على جيثب .

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


All Articles