حاليا أنا منخرط في تنفيذ التفاعل مع مزود خدمة KYC. كالعادة ، لا شيء كوني. ما عليك سوى اختيار مجموعة كبيرة إلى حد ما من نسخ السجلات المختلفة من قاعدة البيانات الخاصة بك ، وتحميلها إلى مزود الخدمة واطلب من مزود السجلات التحقق من هذه السجلات.
تحتوي المرحلة الأولية للمعالجة على 12 عملية مماثلة مع إرسال طلبات لاسترداد البيانات من مستخدم معين من جداول قاعدة البيانات المختلفة. هناك افتراض أنه في هذه الحالة ، يمكن إعادة استخدام جزء كبير إلى حد ما من الشفرة كملخص Request
. سأحاول أن أقترح كيف يمكن استخدام هذا. سأكتب الاختبار الأول:
describe('Request', () => { const Request = require('./Request'); it('execute should return promise', () => { const request = new Request(); request.execute().then((result) => { expect(result).toBeNull(); }); }); });
تبدو جيدة جدا؟ ربما تكون ناقصة ، ولكن للوهلة الأولى يبدو أن Request
هو في الأساس
يُرجع Promise
بالنتيجة؟ من هذا فمن الممكن أن تبدأ. سوف أرسم الكود حتى يمكن تشغيل الاختبار.
class Request { constructor(){} execute(){ return new Promise((resolve, reject) => { resolve(null); }); } } module.exports = Request;
أقوم npm test
النقطة الخضراء للاختبار المكتمل في وحدة التحكم.
هكذا لدي طلب ، ويمكن تنفيذه. في الواقع ، ومع ذلك ، سأحتاج إلى إبلاغ طلبي بطريقة أو بأخرى بالجدول الذي يجب أن يبحث فيه عن البيانات الضرورية والمعايير التي يجب أن تفي بها هذه البيانات. سأحاول كتابة اختبار جديد:
it('should configure request', () => { const options = { tableName: 'users', query: { id: 1 } }; request.configure(options); expect(request.options).toEqual(options); });
حسنا؟ في رأيي ، تماما. نظرًا لأن لدي بالفعل اختبارين يستخدمان مثيل متغير request
، فسوف أقوم بتهيئة هذا المتغير بطريقة خاصة يتم تنفيذها قبل بدء كل اختبار. وبالتالي ، في كل اختبار ، سيكون لدي مثيل جديد لكائن الطلب:
let request = null; beforeEach(() => { request = new Request(); });
أقوم بتطبيق هذه الوظيفة في فئة الطلب ، وأضف طريقة إليها لحفظ الإعدادات في متغير مثيل الفئة ، كما يوضح الاختبار.
configure(options){ this.options = options; }
أجري الاختبارات والآن أرى نقطتين خضراء. تم الانتهاء من اختبارين بنجاح. ومع ذلك. ومع ذلك ، من المفترض أن يتم توجيه استفساراتي إلى قاعدة البيانات. الآن قد يكون من المفيد محاولة معرفة الجانب الذي سيتلقى الطلب معلومات حول قاعدة البيانات. سأعود إلى الاختبارات وأكتب رمزًا:
const DbMock = require('./DbMock'); let db = null; beforeEach(() => { db = new DbMock(); request = new Request(db); });
يبدو لي أن مثل هذا الإصدار الكلاسيكي من التهيئة من خلال المنشئ يفي تمامًا بمتطلباتي الحالية.
بطبيعة الحال ، لن أستخدم اختبارات الوحدة لاستخدام الواجهة لقاعدة بيانات MySQL الحقيقية التي يعمل بها مشروعنا. لماذا؟ لأن:
- إذا احتاج أحد زملائي ، بدلاً مني ، إلى العمل في هذا الجزء من المشروع وإجراء اختبارات الوحدات ، فقبل أن يتمكنوا من فعل أي شيء ، سيتعين عليهم قضاء بعض الوقت والجهد في تثبيت وإعداد مثيلهم الخاص بخادم MySQL.
- يعتمد نجاح اختبارات الوحدات على صحة ملء البيانات الأولية المستخدمة في قاعدة بيانات خادم MySQL.
- سيكون وقت إجراء الاختبارات باستخدام قاعدة بيانات MySQL أطول بكثير.
حسنا ولماذا ، على سبيل المثال ، لا تستخدم أي قاعدة بيانات في الذاكرة في اختبارات الوحدة؟ ستعمل بسرعة ، ويمكن أتمتة عملية التكوين والتهيئة. هذا صحيح ، لكن في الوقت الحالي لا أرى أي فوائد من استخدام هذه الأداة الإضافية. يبدو لي أن احتياجاتي الحالية أسرع وأرخص (لا داعي لقضاء بعض الوقت في الدراسة) باستخدام فصول وأساليب
وأهداف وهمية ، والتي ستحاكي فقط سلوك الواجهات المفترض استخدامها في ظروف القتال.
بالمناسبة. في القتال ، أقترح استخدام رف الكتب بالاقتران مع knex . لماذا؟ نظرًا لتوثيق الوثائق المتعلقة بتثبيت هاتين الأداتين وتكوينهما واستخدامهما ، تمكنت من إنشاء استعلام وتنفيذه في قاعدة البيانات في بضع دقائق.
ما يلي من هذا؟ يتبع ذلك أنه يجب عليّ تعديل رمز فئة Request
حتى يتطابق تنفيذ الطلب مع الواجهات المصدرة بواسطة أدوات القتال الخاصة بي. لذا يجب أن يبدو الرمز الآن كالتالي:
class Request { constructor(db){ this.db = db; } configure(options){ this.options = options; } execute(){ const table = this.db.Model.extend({ tableName: this.options.tableName }); return table.where(this.options.query).fetch(); } } module.exports = Request;
سأجري الاختبارات ونرى ما سيحدث. نعم بالطبع ، ليس لدي وحدة DbMock
، لذا فإن أول شيء أقوم به هو تطبيق كعب روتين له:
class DbMock { constructor(){} } module.exports = DbMock;
سأجري الاختبارات مرة أخرى. ماذا الان أخبرني الأميرة Jasmine
أن DbMock
بي لا يقوم بتطبيق خاصية Model
. سأحاول التوصل إلى شيء:
class DbMock { constructor(){ this.Model = { extend: () => {} }; } } module.exports = DbMock;
تشغيل الاختبارات مرة أخرى. الخطأ الآن هو أنه في اختبار وحدتي ، أقوم بتشغيل الاستعلام دون تحديد المعلمات أولاً باستخدام طريقة configure
. أصلح هذا:
const options = { tableName: 'users', query: { id: 1 } }; it('execute should return promise', () => { request.configure(options); request.execute().then((result) => { expect(result).toBeNull(); }); });
نظرًا لأنني قمت بالفعل باستخدام مثيل لمتغير options
في اختبارين ، فقد قمت بوضعه في رمز التهيئة لمجموعة الاختبار بالكامل وأجري الاختبارات مرة أخرى.
كما هو متوقع ، فإن طريقة extend
، خصائص Model
، لفئة DbMock
undefined
، وبالتالي ، بطبيعة الحال ، طلبنا ليس لديه وسيلة لاستدعاء طريقة where
.
لقد فهمت بالفعل أنه يجب تنفيذ خاصية Model
لفئة DbMock
خارج فئة DbMock
. أولاً وقبل كل شيء ، نظرًا لحقيقة أن تنفيذ
ضروري لتشغيل الاختبارات الحالية ، سيتطلب الكثير من النطاقات المتداخلة عند تهيئة خاصية Model
مباشرة في فئة DbMock
. سيكون من المستحيل تمامًا القراءة والفهم ... ولكن هذا لن يمنعني من هذه المحاولة ، لأنني أريد أن أتأكد من أنه لا يزال لدي الفرصة لأكتب فقط أسطرًا قليلة من التعليمات البرمجية واجتياز الاختبارات بنجاح.
هكذا يستنشق ، يخرج ، خافت القلب يغادر الغرفة. استكمال تطبيق منشئ DbMock
. Ta-daaammmmm ....
class DbMock { constructor(){ this.Model = { extend: () => { return { where: () => { return { fetch: () => { return new Promise((resolve, reject) => { resolve(null); }); } }; } }; } }; } } module.exports = DbMock;
القصدير! ومع ذلك ، بأيد قوية ، قم بإجراء الاختبارات وتأكد من أن Jasmine
تظهر لنا مرة أخرى النقاط الخضراء. وهذا يعني أننا لا نزال على المسار الصحيح ، على الرغم من أن هناك شيئًا ما قد تورم بشكل غير لائق.
ما التالي؟ يمكن أن يرى بالعين المجردة أنه يجب تنفيذ خاصية Model
لقاعدة بيانات زائفة كشيء منفصل تمامًا. على الرغم من أنه غير مرغوب فيه ، فليس من الواضح كيف ينبغي تنفيذه.
لكنني أعلم بالتأكيد أنني سأقوم بتخزين السجلات في قاعدة البيانات الزائفة هذه الآن في المصفوفات الأكثر شيوعًا. ولأن الاختبارات الحالية لا أحتاج إلا إلى محاكاة جدول users
، فبداية ، سأنفذ مجموعة من المستخدمين ، بسجل واحد. لكن أولاً ، سأكتب اختبارًا:
describe('Users', () => { const users = require('./Users'); it('should contain one user', () => { expect(Array.isArray(users)).toBeTruthy(); expect(users.length).toEqual(1); const user = users[0]; expect(user.Id).toEqual(1); expect(user.Name).toEqual('Jack'); }); });
أركض الاختبارات. أتأكد من أنها لا تمر ، وأنا أطبق الحاوية البسيطة الخاصة بي مع المستخدم:
const Users = [ { Id: 1, Name: 'Jack' } ]; module.exports = Users;
الآن يتم تنفيذ الاختبارات ، ويحدث بالنسبة لي أن Model
، في حزمة bookshell
، هو المزود للواجهة للوصول إلى محتويات الجدول في قاعدة البيانات. ليس لشيء أن نمر كائن مع اسم الجدول إلى طريقة extend
. لماذا يسمى extend
، وليس على سبيل المثال get
، وأنا لا أعرف. ربما يكون هذا مجرد نقص في المعرفة حول واجهة برمجة التطبيقات bookshell
.
حسنًا ، رضي الله عنه ، لدي الآن فكرة في ذهني حول موضوع الاختبار التالي:
describe('TableMock', () => { const container = require('./Users'); const Table = require('./TableMock'); const users = new Table(container); it('should return first item', () => { users.fetch({ Id: 1 }).then((item) => { expect(item.Id).toEqual(1); expect(item.Name).toEqual('Jack'); }); }); });
نظرًا لأنني أحتاج في الوقت الحالي إلى تطبيق يحاكي فقط وظيفة برنامج تشغيل تخزين حقيقي ، فإنني أسمي الفئات وفقًا لذلك ، مضيفًا اللاحقة Mock
:
class TableMock { constructor(container){ this.container = container; } fetch() { return new Promise((resolve, reject) => { resolve(this.container[0]); }); } } module.exports = TableMock;
لكن fetch
ليس fetch
الطريقة الوحيدة التي أنوي استخدامها في الإصدار القتالي ، لذلك أضيف اختبارًا آخر:
it('where-fetch chain should return first item', () => { users.where({ Id: 1 }).fetch().then((item)=> { expect(item.Id).toEqual(1); expect(item.Name).toEqual('Jack'); }); });
يعرض الإطلاق ، كما هو متوقع ، رسالة خطأ لي. لذلك TableMock
تنفيذ TableMock
مع الطريقة where
:
where(){ return this; }
الآن يتم إجراء الاختبارات ويمكنك الانتقال إلى موضوع تطبيق خاصية Model
في فئة DbMock
. كما اقترحت بالفعل ، سيكون هذا موفرًا معينًا لمثيلات الكائنات من نوع TableMock
:
describe('TableMockMap', () => { const TableMock = require('./TableMock'); const TableMockMap = require('./TableMockMap'); const map = new TableMockMap(); it('extend should return existent TableMock', () => { const users = map.extend({tableName: 'users'}); expect(users instanceof TableMock).toBeTruthy(); }); });
لماذا TableMockMap
، لأن هذا هو دلالة. فقط بدلاً من اسم طريقة get
، يتم استخدام اسم طريقة extend
.
كما تعطل الاختبار ، ونحن ننفذ
const Users = require('./Users'); const TableMock = require('./TableMock'); class TableMockMap extends Map{ constructor(){ super(); this.set('users', Users); } extend(options){ const container = this.get(options.tableName); return new TableMock(container); } } module.exports = TableMockMap;
قم بإجراء الاختبارات وشاهد ست نقاط خضراء في وحدة التحكم. الحياة جميلة
يبدو لي الآن أنه يمكنك بالفعل التخلص من
التهيئة
في مُنشئ فئة DbMock
، باستخدام TableMockMap
الرائع. لن نؤجله ، خاصة أنه سيكون من الجيد تناول الشاي بالفعل. التنفيذ الجديد أنيق مبهج:
const TableMockMap = require('./TableMockMap'); class DbMock { constructor(){ this.Model = new TableMockMap(); } } module.exports = DbMock;
قم بإجراء الاختبارات ... و عفوًا! يقع اختبارنا الأكثر أهمية. ولكن هذا أمر جيد ، لأنه كان كعب اختبار والآن علينا فقط إصلاحه:
it('execute should return promise', () => { request.configure(options); request.execute().then((result) => { expect(result.Id).toEqual(1); expect(result.Name).toEqual('Jack'); }); });
تم الانتهاء من الاختبارات بنجاح. والآن يمكنك أخذ قسط من الراحة ، ثم العودة إلى وضع اللمسات الأخيرة على رمز الطلب الناتج ، لأنه لا يزال بعيدًا جدًا عن الكمال ، ولكن حتى من مجرد واجهة سهلة الاستخدام ، على الرغم من أن البيانات المستخدمة منه قواعد يمكن بالفعل وردت.