أصبح التكامل والاختبارات الشاملة أمرًا سهلاً باستخدام Node.js و MongoDB

نشرت: 2022-03-11

تعتبر الاختبارات جزءًا أساسيًا من بناء تطبيق Node.js قوي. يمكن للاختبارات المناسبة التغلب بسهولة على الكثير من أوجه القصور التي قد يشير إليها المطورون حول حلول تطوير Node.js.

بينما يركز العديد من المطورين على التغطية بنسبة 100٪ باختبارات الوحدة ، فمن المهم ألا يتم اختبار الكود الذي تكتبه بمعزل عن الآخرين. تمنحك اختبارات التكامل والاختبارات الشاملة تلك الثقة الإضافية عن طريق اختبار أجزاء من التطبيق معًا. قد تعمل هذه الأجزاء بشكل جيد من تلقاء نفسها ، ولكن في نظام كبير ، نادرًا ما تعمل وحدات التعليمات البرمجية بشكل منفصل.

تشكل Node.js و MongoDB معًا واحدة من أكثر الثنائيات شهرة في الآونة الأخيرة. إذا كنت أحد الأشخاص العديدين الذين يستخدمونها ، فأنت محظوظ.

في هذه المقالة ، ستتعلم كيفية كتابة اختبارات التكامل والاختبارات الشاملة بسهولة لتطبيق Node.js وتطبيق MongoDB الذي يعمل على مثيلات حقيقية لقاعدة البيانات ، كل ذلك دون الحاجة إلى إعداد بيئة معقدة أو كود إعداد / تفكيك معقد .

سترى كيف تساعد حزمة mongo-unit في التكامل والاختبار الشامل في Node.js. للحصول على نظرة عامة أكثر شمولاً حول اختبارات تكامل Node.js ، راجع هذه المقالة.

التعامل مع قاعدة بيانات حقيقية

عادةً ، من أجل التكامل أو الاختبارات الشاملة ، ستحتاج البرامج النصية الخاصة بك إلى الاتصال بقاعدة بيانات حقيقية مخصصة لأغراض الاختبار. يتضمن ذلك كتابة التعليمات البرمجية التي يتم تشغيلها في بداية ونهاية كل حالة / مجموعة اختبار للتأكد من أن قاعدة البيانات في حالة نظيفة يمكن التنبؤ بها.

قد يعمل هذا بشكل جيد مع بعض المشاريع ، لكن له بعض القيود:

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

من ناحية أخرى ، فإن استخدام قاعدة بيانات حقيقية يجعل بيئة الاختبار أقرب ما يمكن من الإنتاج. يمكن النظر إلى هذا على أنه ميزة خاصة لهذا النهج.

استخدام قاعدة بيانات حقيقية في الذاكرة

يبدو أن استخدام قاعدة بيانات حقيقية للاختبار ينطوي على بعض التحديات. لكن ميزة استخدام قاعدة بيانات حقيقية جيدة جدًا بحيث لا يمكن نقلها. كيف يمكننا التغلب على التحديات والحفاظ على الميزة؟

يمكن أن تكون إعادة استخدام حل جيد من نظام أساسي آخر وتطبيقه على عالم Node.js هو السبيل للذهاب هنا.

تستخدم مشاريع Java على نطاق واسع DBUnit مع قاعدة بيانات في الذاكرة (على سبيل المثال ، H2) لهذا الغرض.

تم دمج DBUnit مع JUnit (مشغل اختبار Java) ويتيح لك تحديد حالة قاعدة البيانات لكل مجموعة اختبار / اختبار ، وما إلى ذلك ، ويزيل القيود التي تمت مناقشتها أعلاه:

  • DBUnit و H2 هما مكتبات Java ، لذلك لا تحتاج إلى إعداد بيئة إضافية. كل ذلك يعمل في JVM.
  • تجعل قاعدة البيانات في الذاكرة إدارة الحالة هذه سريعة جدًا.
  • يجعل DBUnit تكوين قاعدة البيانات بسيطًا جدًا ويسمح لك بالحفاظ على حالة قاعدة بيانات واضحة لكل حالة.
  • H2 هي قاعدة بيانات SQL وهي متوافقة جزئيًا مع MySQL لذلك ، في الحالات الكبرى ، يمكن للتطبيق العمل معها كما هو الحال مع قاعدة بيانات الإنتاج.

انطلاقا من هذه المفاهيم ، قررت أن أصنع شيئًا مشابهًا لـ Node.js و MongoDB: Mongo-unit.

Mongo-unit هي حزمة Node.js يمكن تثبيتها باستخدام NPM أو Yarn. يقوم بتشغيل MongoDB في الذاكرة. يجعل اختبارات التكامل سهلة من خلال التكامل الجيد مع Mocha وتوفير واجهة برمجة تطبيقات بسيطة لإدارة حالة قاعدة البيانات.

تستخدم المكتبة حزمة mongodb-prebuilt NPM ، والتي تحتوي على ثنائيات MongoDB سابقة الإنشاء لأنظمة التشغيل الشائعة. يمكن تشغيل مثيلات MongoDB هذه في وضع الذاكرة.

تركيب وحدة Mongo

لإضافة mongo-unit إلى مشروعك ، يمكنك تشغيل:

 npm install -D mongo-unit

أو

 yarn add mongo-unit

وهذا هو عليه. لا تحتاج حتى إلى تثبيت برنامج MongoDB على جهاز الكمبيوتر الخاص بك لاستخدام هذه الحزمة.

استخدام الوحدة المنغولية لاختبارات التكامل

لنتخيل أن لديك تطبيق Node.js بسيطًا لإدارة المهام:

 // service.js const mongoose = require('mongoose') const mongoUrl = process.env.MONGO_URL || 'mongodb://localhost:27017/example' mongoose.connect(mongoUrl) const TaskSchema = new mongoose.Schema({ name: String, started: Date, completed: Boolean, }) const Task = mongoose.model('tasks', TaskSchema) module.exports = { getTasks: () => Task.find(), addTask: data => new Task(data).save(), deleteTask: taskId => Task.findByIdAndRemove(taskId) }

عنوان URL لاتصال MongoDB ليس مشفرًا هنا. كما هو الحال مع معظم الواجهات الخلفية لتطبيق الويب ، فإننا نأخذها من متغير البيئة. سيسمح لنا هذا باستبداله بأي عنوان URL أثناء الاختبارات.

 const express = require('express') const bodyParser = require('body-parser') const service = require('./service') const app = express() app.use(bodyParser.json()) app.use(express.static(`${__dirname}/static`)) app.get('/example', (req, res) => { service.getTasks().then(tasks => res.json(tasks)) }) app.post('/example', (req, res) => { service.addTask(req.body).then(data => res.json(data)) }) app.delete('/example/:taskId', (req, res) => { service.deleteTask(req.params.taskId).then(data => res.json(data)) }) app.listen(3000, () => console.log('started on port 3000'))

هذا مقتطف لتطبيق يحتوي على واجهة مستخدم. تم حذف رمز واجهة المستخدم للإيجاز. يمكنك التحقق من المثال الكامل على GitHub.

التكامل مع المخاوي

لجعل Mocha تشغل اختبارات التكامل ضد mongo-unit ، نحتاج إلى تشغيل مثيل قاعدة بيانات mongo-unit قبل تحميل كود التطبيق في سياق Node.js. للقيام بذلك ، يمكننا استخدام المعلمة mocha --require Mocha-Preparing ، والتي تتيح لك إجراء عمليات غير متزامنة في البرامج النصية المطلوبة.

 // it-helper.js const prepare = require('mocha-prepare') const mongoUnit = require('mongo-unit') prepare(done => mongoUnit.start() .then(testMongoUrl => { process.env.MONGO_URL = testMongoUrl done() }))

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

الخطوة الأولى هي إضافة اختبار إلى قاعدة بيانات الاختبار ( testData.json ):

 { "tasks": [ { "name": "test", "started": "2017-08-28T16:07:38.268Z", "completed": false } ] }

الخطوة التالية هي إضافة الاختبارات نفسها:

 const expect = require('chai').expect const mongoose = require('mongoose') const mongoUnit = require('../index') const service = require('./app/service') const testMongoUrl = process.env.MONGO_URL describe('service', () => { const testData = require('./fixtures/testData.json') beforeEach(() => mongoUnit.initDb(testMongoUrl, testData)) afterEach(() => mongoUnit.drop()) it('should find all tasks', () => { return service.getTasks() .then(tasks => { expect(tasks.length).to.equal(1) expect(tasks[0].name).to.equal('test') }) }) it('should create new task', () => { return service.addTask({ name: 'next', completed: false }) .then(task => { expect(task.name).to.equal('next') expect(task.completed).to.equal(false) }) .then(() => service.getTasks()) .then(tasks => { expect(tasks.length).to.equal(2) expect(tasks[1].name).to.equal('next') }) }) it('should remove task', () => { return service.getTasks() .then(tasks => tasks[0]._id) .then(taskId => service.deleteTask(taskId)) .then(() => service.getTasks()) .then(tasks => { expect(tasks.length).to.equal(0) }) }) })

وفويلا!

لاحظ كيف يوجد فقط سطرين من التعليمات البرمجية التي تتعامل مع الإعداد والتفكيك.

كما ترى ، من السهل جدًا كتابة اختبارات التكامل باستخدام مكتبة mongo-unit. نحن لا نسخر من MongoDB نفسها ، ويمكننا استخدام نفس نماذج Mongoose. لدينا سيطرة كاملة على بيانات قاعدة البيانات ولا نفقد الكثير في أداء الاختبار لأن MongoDB المزيف يعمل في الذاكرة.

يتيح لنا هذا أيضًا تطبيق أفضل ممارسات اختبار الوحدة لاختبارات التكامل:

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

على سبيل المكافأة ، يمكننا حتى تشغيل التطبيق نفسه ضد mongo-unit. يسمح لنا بإجراء اختبارات شاملة لتطبيقنا مقابل قاعدة بيانات تم الاستهزاء بها.

الاختبارات الشاملة بالسيلينيوم

للاختبار الشامل ، سنستخدم Selenium WebDriver و Hermione E2E عداء الاختبار.

أولاً ، سنقوم بتمهيد السائق وعداء الاختبار:

 const mongoUnit = require('mongo-unit') const selenium = require('selenium-standalone') const Hermione = require('hermione') const hermione = new Hermione('./e2e/hermione.conf.js') //hermione config seleniumInstall() //make sure selenium is installed .then(seleniumStart) //start selenium web driver .then(mongoUnit.start) // start mongo unit .then(testMongoUrl => { process.env.MONGO_URL = testMongoUrl //store mongo url }) .then(() => { require('./index.js') //start application }) .then(delay(1000)) // wait a second till application is started .then(() => hermione.run('', hermioneOpts)) // run hermiona e2e tests .then(() => process.exit(0)) .catch(() => process.exit(1))

سنحتاج أيضًا إلى بعض الوظائف المساعدة (تمت إزالة معالجة الأخطاء للإيجاز):

 function seleniumInstall() { return new Promise(resolve => selenium.install({}, resolve)) } function seleniumStart() { return new Promise(resolve => selenium.start(resolve)) } function delay(timeout) { return new Promise(resolve => setTimeout(resolve, timeout)) }

بعد ملء قاعدة البيانات ببعض البيانات وتنظيفها بمجرد الانتهاء من الاختبارات ، يمكننا إجراء اختباراتنا الأولى:

 const expect = require('chai').expect const co = require('co') const mongoUnit = require('../index') const testMongoUrl = process.env.MONGO_URL const DATA = require('./fixtures/testData.json') const ui = { task: '.task', remove: '.task .remove', name: '#name', date: '#date', addTask: '#addTask' } describe('Tasks', () => { beforeEach(function () { return mongoUnit.initDb(testMongoUrl, DATA) .then(() => this.browser.url('http://localhost:3000')) }) afterEach(() => mongoUnit.dropDb(testMongoUrl)) it('should display list of tasks', function () { const browser = this.browser return co(function* () { const tasks = yield browser.elements(ui.task) expect(tasks.length, 1) }) }) it('should create task', function () { const browser = this.browser return co(function* () { yield browser.element(ui.name).setValue('test') yield browser.element(ui.addTask).click() const tasks = yield browser.elements(ui.task) expect(tasks.length, 2) }) }) it('should remove task', function () { const browser = this.browser return co(function* () { yield browser.element(ui.remove).click() const tasks = yield browser.elements(ui.task) expect(tasks.length, 0) }) }) })

كما ترى ، تبدو الاختبارات الشاملة مشابهة جدًا لاختبارات التكامل.

يتم إحتوائه

التكامل والاختبار الشامل مهمان لأي تطبيق واسع النطاق. يمكن لتطبيقات Node.js ، على وجه الخصوص ، الاستفادة بشكل كبير من الاختبار الآلي. باستخدام mongo-unit ، يمكنك كتابة التكامل والاختبار الشامل دون القلق بشأن جميع التحديات التي تأتي مع مثل هذه الاختبارات.

يمكنك العثور على أمثلة كاملة لكيفية استخدام mongo-unit على GitHub.

مزيد من القراءة على مدونة Toptal Engineering:

  • بناء Node.js / TypeScript REST API ، الجزء 3: MongoDB والمصادقة والاختبارات الآلية