
في هذه المقالة ، سننظر في كيفية كتابة تعليمات برمجية نظيفة وقابلة للاختبار بسهولة بأسلوب وظيفي باستخدام نمط برمجة Dependency Injection. المكافأة هي تغطية اختبار 100 ٪ وحدة.
المصطلحات التي سيتم استخدامها في المقال
سيضع مؤلف المقال في الاعتبار بدقة مثل هذا التفسير للمصطلحات التالية ، مع العلم أن هذه ليست الحقيقة المطلقة وأن التفسيرات الأخرى ممكنة.
- حقن التبعية
هذا هو نمط البرمجة الذي يفترض أن التبعيات الخارجية للوظائف ومصانع الكائنات تأتي من الخارج في شكل وسيطات لهذه الوظائف. حقن التبعية هو بديل لاستخدام التبعيات من سياق عالمي. - وظيفة صافي
هذه دالة تعتمد نتائجه فقط على وسيطاتها. أيضا ، لا ينبغي أن يكون لهذه الوظيفة آثار جانبية.
أريد أن أبدي تحفظًا على الفور بأن الوظائف التي ندرسها ليس لها آثار جانبية ، لكن لا يزال بإمكانها الحصول على الوظائف التي أتت إلينا من خلال Dependency Injection. لذلك نقاء الوظائف لدينا مع تحفظ كبير. - اختبار الوحدة
اختبار الوظيفة الذي يتحقق من أن جميع الشوكات داخل هذه الوظيفة تعمل تمامًا مثل مؤلف الرمز المقصود. في هذه الحالة ، بدلاً من استدعاء أي وظائف أخرى ، يتم استخدام مكالمة إلى moks.
ونحن نفهم في الممارسة العملية
النظر في مثال. مصنع من العدادات التي تحسب tick
. يمكن إيقاف العداد باستخدام طريقة cancel
.
const createCounter = ({ ticks, onTick }) => { const state = { currentTick: 1, timer: null, canceled: false } const cancel = () => { if (state.canceled) { throw new Error('"Counter" already canceled') } clearInterval(state.timer) } const onInterval = () => { onTick(state.currentTick++) if (state.currentTick > ticks) { cancel() } } state.timer = setInterval(onInterval, 200) const instance = { cancel } return instance } export default createCounter
نرى رمزًا مقروءًا إنسانيًا وقابلًا للفهم. لكن هناك اختبارًا واحدًا - لا يمكن كتابة اختبارات الوحدة العادية عليه. دعونا نرى ما هو في الطريق؟
1) لا يمكنك الوصول إلى الوظائف داخل cancel
، onInterval
واختبارها بشكل منفصل.
2) لا يمكن اختبار وظيفة onInterval
بشكل منفصل عن وظيفة cancel
، لأن الأول له صلة مباشرة بالثانية.
3) التبعيات الخارجية setInterval
، clearInterval
.
4) لا يمكن اختبار وظيفة createCounter
بشكل منفصل عن الوظائف الأخرى ، مرة أخرى بسبب الروابط المباشرة.
دعونا نحل المشاكل 1) 2) - نزيل onInterval
cancel
onInterval
من الإغلاق onInterval
الروابط المباشرة بينهما من خلال كائن pool
.
export const cancel = pool => { if (pool.state.canceled) { throw new Error('"Counter" already canceled') } clearInterval(pool.state.timer) } export const onInterval = pool => { pool.config.onTick(pool.state.currentTick++) if (pool.state.currentTick > pool.config.ticks) { pool.cancel() } } const createCounter = config => { const pool = { config, state: { currentTick: 1, timer: null, canceled: false } } pool.cancel = cancel.bind(null, pool) pool.onInterval = onInterval.bind(null, pool) pool.state.timer = setInterval(pool.onInterval, 200) const instance = { cancel: pool.cancel } return instance } export default createCounter
نحن نحل المشكلة 3). نحن نستخدم نمط حقن التبعية على setInterval
، clearInterval
أيضًا إلى كائن pool
.
export const cancel = pool => { const { clearInterval } = pool if (pool.state.canceled) { throw new Error('"Counter" already canceled') } clearInterval(pool.state.timer) } export const onInterval = pool => { pool.config.onTick(pool.state.currentTick++) if (pool.state.currentTick > pool.config.ticks) { pool.cancel() } } const createCounter = (dependencies, config) => { const pool = { ...dependencies, config, state: { currentTick: 1, timer: null, canceled: false } } pool.cancel = cancel.bind(null, pool) pool.onInterval = onInterval.bind(null, pool) const { setInterval } = pool pool.state.timer = setInterval(pool.onInterval, 200) const instance = { cancel: pool.cancel } return instance } export default createCounter.bind(null, { setInterval, clearInterval })
الآن كل شيء على ما يرام تقريبا ، ولكن لا تزال هناك مشكلة 4). في الخطوة الأخيرة ، نطبق Dependency Injection على كل من وظائفنا ونقطع الاتصالات المتبقية بينهما من خلال كائن pool
. في الوقت نفسه ، نقسم ملفًا كبيرًا واحدًا إلى العديد من الملفات ، وبالتالي سيكون من السهل في وقت لاحق كتابة اختبارات الوحدة.
الخاتمة
ماذا لدينا في النهاية؟ مجموعة من الملفات ، يحتوي كل منها على وظيفة واحدة نظيفة. تدهورت البساطة وشمولية الكود قليلاً ، ولكن هذا يعوض أكثر من صورة تغطية 100٪ في اختبارات الوحدة.

أريد أيضًا أن أشير إلى أنه لكتابة اختبارات الوحدة ، لا نحتاج إلى القيام بأي معالجات require
والحصول على نظام الملفات Node.js.
فقط عن طريق فتح جميع الوظائف حتى النهاية ، نحن نكتسب الحرية.