اختبار وحدة لدوال السحابة

توضح هذه الصفحة أفضل الممارسات والأدوات لكتابة اختبارات الوحدة مثل الاختبارات التي قد تكون جزءًا من عملية تكامل متواصل (CI) . لتسهيل الاختبار، يوفّر Firebase Firebase Test SDK لـ Cloud Functions. أُنشأها جون هنتر، الذي كان متخصصًا يتم توزيعها في npm باسم firebase-functions-test، وهي عبارة عن حزمة SDK تجريبية مصاحبة إلى firebase-functions. Firebase Test SDK لـ Cloud Functions:

  • تعتني بالإعداد المناسب وإنهاء الاختبارات، مثل لضبط متغيّرات البيئة وإلغاء ضبطها التي يحتاجها firebase-functions.
  • تنشئ نموذجًا للبيانات وسياق الحدث، لكي يكون عليك فقط تحديد الحقول ذات الصلة باختبارك.

اختبار الإعداد

ثبِّت كلاً من firebase-functions-test وMocha لاختبار إطار العمل، عن طريق تشغيل الأوامر التالية في مجلد الدوال:

npm install --save-dev firebase-functions-test
npm install --save-dev mocha

بعد ذلك، يمكنك إنشاء مجلد test داخل مجلد الدوال وإنشاء ملف جديد. داخله للحصول على رمز الاختبار، وامنحه اسمًا مثل index.test.js.

أخيرًا، عدِّل functions/package.json لإضافة ما يلي:

"scripts": {
  "test": "mocha --reporter spec"
}

بعد كتابة الاختبارات، يمكنك إجراؤها من خلال تنفيذ "npm test" داخلها دليل الدوال.

جارٍ إعداد Firebase Test SDK لـ Cloud Functions

تتوفّر طريقتان لاستخدام "firebase-functions-test":

  1. وضع الاتصال بالإنترنت (يُنصح به): كتابة اختبارات تتفاعل مع مشروع Firebase المخصصة للاختبار حتى تكتب قاعدة البيانات ويقوم المستخدم وما إلى ذلك بالفعل ويمكن أن يفحص رمز الاختبار النتائج. وهذا يعني أيضًا أن المواقع الإلكترونية الأخرى ستعمل أيضًا حِزم تطوير البرامج (SDK) من Google المُستخدَمة في الوظائف.
  2. وضع عدم الاتصال بالإنترنت: يمكنك كتابة اختبارات الوحدات المعزولة وبلا اتصال بالإنترنت بدون أي آثار جانبية. وهذا يعني أنّ أيّ طريقة تستدعي تفاعلاً مع أحد منتجات Firebase (مثل الكتابة إلى قاعدة البيانات أو إنشاء مستخدم) إلى دبلجة. يتم الاستخدام بلا إنترنت لا يُنصح بالوضع العمودي على جهازك بشكل عام إذا كان لديك Cloud Firestore أو Realtime Database الدوال، لأنها تزيد من تعقيد رمز الاختبار بشكل كبير.

إعداد حزمة تطوير البرامج (SDK) في وضع الاتصال (يُنصح بهذا الخيار)

إذا كنت ترغب في كتابة اختبارات تتفاعل مع مشروع اختباري، فأنت بحاجة إلى توفير قيم تهيئة المشروع اللازمة لتهيئة التطبيق من خلال firebase-admin، والمسار إلى ملف مفتاح حساب الخدمة.

للحصول على قيم الإعدادات لمشروع Firebase:

  1. افتح إعدادات مشروعك في وحدة تحكّم Firebase.
  2. في تطبيقاتك، اختَر التطبيق المطلوب.
  3. في الجزء الأيسر، حدِّد الخيار لتنزيل ملف إعداد. لتطبيقات Apple وAndroid.

    بالنسبة إلى تطبيقات الويب، اختَر الإعداد لعرضها. قيم التهيئة.

لإنشاء ملف مفتاح:

  1. افتح جزء حسابات الخدمة. من وحدة التحكم Google Cloud.
  2. اختَر حساب الخدمة التلقائي لـ App Engine واستخدِم قائمة الخيارات على على يسار الشاشة لتحديد إنشاء مفتاح.
  3. عندما يُطلب منك، اختَر JSON لنوع المفتاح، ثم انقر على إنشاء.

بعد حفظ ملف المفتاح، ابدأ إعداد حزمة تطوير البرامج (SDK):

// At the top of test/index.test.js
const test = require('firebase-functions-test')({
  databaseURL: 'https://my-project.firebaseio.com',
  storageBucket: 'my-project.appspot.com',
  projectId: 'my-project',
}, 'path/to/serviceAccountKey.json');

إعداد حزمة تطوير البرامج (SDK) في وضع عدم الاتصال بالإنترنت

إذا أردت كتابة اختبارات بلا اتصال بالإنترنت بالكامل، يمكنك إعداد حزمة تطوير البرامج (SDK). بدون أي معلمات:

// At the top of test/index.test.js
const test = require('firebase-functions-test')();

قيم التكوين الافتراضية

إذا كنت تستخدم functions.config() في رمز الدوال، يمكنك محاكاة الإعدادات. القيم. على سبيل المثال، إذا كان functions/index.js يحتوي على الرمز التالي:

const functions = require('firebase-functions/v1');
const key = functions.config().stripe.key;

بعد ذلك، يمكنك محاكاة القيمة داخل ملف الاختبار على النحو التالي:

// Mock functions config values
test.mockConfig({ stripe: { key: '23wr42ewr34' }});

استيراد الدوال

لاستيراد الدوال، استخدم require لاستيراد ملف الدوال الرئيسية كملف واحدة. احرص على عدم تنفيذ هذا الإجراء إلا بعد إعداد firebase-functions-test. ومحاكاة قيم الإعدادات

// after firebase-functions-test has been initialized
const myFunctions = require('../index.js'); // relative path to functions code

في حال إعداد firebase-functions-test في وضع عدم الاتصال، ولديك admin.initializeApp() في رمز الدوال، وعليك استخدامه قبل استيراد الدوال:

// If index.js calls admin.initializeApp at the top of the file,
// we need to stub it out before requiring index.js. This is because the
// functions will be executed as a part of the require process.
// Here we stub admin.initializeApp to be a dummy function that doesn't do anything.
adminInitStub = sinon.stub(admin, 'initializeApp');
// Now we can require index.js and save the exports inside a namespace called myFunctions.
myFunctions = require('../index');

اختبار دوال الخلفية (التي لا تستخدم HTTP)

تتضمّن عملية اختبار الدوال التي لا تتوافق مع HTTP الخطوات التالية:

  1. التفاف الدالة التي تريد اختبارها باستخدام الطريقة test.wrap
  2. إنشاء بيانات الاختبار
  3. استدعِ الدالة المضمّنة مع بيانات الاختبار التي أنشأتها وأي حدث. والسياق التي تريد تحديدها.
  4. تقديم تأكيدات حول السلوك

أولاً، قم بلف الدالة التي تريد اختبارها. لنفترض أن لديك دالة في functions/index.js باسم makeUppercase، والذي تريد اختباره. كتابة تتم المتابعة في functions/test/index.test.js

// "Wrap" the makeUpperCase function from index.js
const myFunctions = require('../index.js');
const wrapped = test.wrap(myFunctions.makeUppercase);

wrapped هي دالة تستدعي makeUppercase عند استدعائها. wrapped يأخذ معاملين:

  1. data (مطلوبة): البيانات المطلوب إرسالها إلى makeUppercase. هذا مباشرةً مع المعلمة الأولى التي يتم إرسالها إلى معالج الدالة الذي كتَب. توفِّر firebase-functions-test طرقًا لإنشاء واجهة برمجة تطبيقات مخصَّصة. البيانات أو أمثلة البيانات.
  2. eventContextOptions (اختيارية): حقول سياق الحدث التي يرغب في التحديد. سياق الحدث هو المَعلمة الثانية التي يتم إرسالها إلى الذي كتبته. في حال عدم تضمين سمة eventContextOptions معلَمة عند استدعاء wrapped، لا يزال يتم إنشاء سياق حدث من خلال حقول معقولة. يمكنك إلغاء بعض الحقول التي تم إنشاؤها عن طريق وتحديدها هنا. لاحظ أنه يجب عليك فقط تضمين الحقول التي الذي تريد إلغاءه. يتم إنشاء أي حقول لم يتم تجاوزها.
const data =  // See next section for constructing test data

// Invoke the wrapped function without specifying the event context.
wrapped(data);

// Invoke the function, and specify params
wrapped(data, {
  params: {
    pushId: '234234'
  }
});

// Invoke the function, and specify auth and auth Type (for real time database functions only)
wrapped(data, {
  auth: {
    uid: 'jckS2Q0'
  },
  authType: 'USER'
});

// Invoke the function, and specify all the fields that can be specified
wrapped(data, {
  eventId: 'abc',
  timestamp: '2018-03-23T17:27:17.099Z',
  params: {
    pushId: '234234'
  },
  auth: {
    uid: 'jckS2Q0' // only for real time database functions
  },
  authType: 'USER' // only for real time database functions
});

إنشاء بيانات الاختبار

المعلمة الأولى لدالة ملفوفة هي بيانات الاختبار لاستدعاء الدالة المضمنة الوظيفة. هناك عدد من الطرق لإنشاء بيانات الاختبار.

استخدام البيانات المخصّصة

تتضمن الدالة firebase-functions-test عددًا من الدوال لإنشاء البيانات المطلوبة. لاختبار الدوال. على سبيل المثال، استخدِم test.firestore.makeDocumentSnapshot. لإنشاء Firestore DocumentSnapshot. الوسيطة الأولى هي البيانات، الوسيطة الثانية هي المسار المرجعي الكامل، وتوجد وسيطة ثالثة اختيارية للخصائص الأخرى للّقطة التي يمكنك تحديدها.

// Make snapshot
const snap = test.firestore.makeDocumentSnapshot({foo: 'bar'}, 'document/path');
// Call wrapped function with the snapshot
const wrapped = test.wrap(myFunctions.myFirestoreDeleteFunction);
wrapped(snap);

إذا كنت تختبر دالة onUpdate أو onWrite، ستحتاج إلى إنشاء لقطتان: إحداهما للحالة السابقة والأخرى للحالة اللاحقة. بعد ذلك، استخدام طريقة makeChange لإنشاء كائن Change باستخدام هذه اللقطات.

// Make snapshot for state of database beforehand
const beforeSnap = test.firestore.makeDocumentSnapshot({foo: 'bar'}, 'document/path');
// Make snapshot for state of database after the change
const afterSnap = test.firestore.makeDocumentSnapshot({foo: 'faz'}, 'document/path');
const change = test.makeChange(beforeSnap, afterSnap);
// Call wrapped function with the Change object
const wrapped = test.wrap(myFunctions.myFirestoreUpdateFunction);
wrapped(change);

يمكنك الاطّلاع على مرجع واجهة برمجة التطبيقات للحصول على دوال مشابهة. لجميع أنواع البيانات الأخرى.

استخدام نموذج البيانات

إذا لم تكن بحاجة إلى تخصيص البيانات المستخدَمة في اختباراتك، فحينئذٍ تقدّم firebase-functions-test طرقًا لإنشاء نماذج من البيانات لكل منهما. ونوع الدالة.

// For Firestore onCreate or onDelete functions
const snap = test.firestore.exampleDocumentSnapshot();
// For Firestore onUpdate or onWrite functions
const change = test.firestore.exampleDocumentSnapshotChange();

يمكنك الاطّلاع على مرجع واجهة برمجة التطبيقات للتعرُّف على طرق الحصول على أمثلة البيانات لكل نوع دالة.

استخدام البيانات التي تم استئصالها (لوضع عدم الاتصال بالإنترنت)

إذا أعددت حزمة تطوير البرامج (SDK) في وضع عدم الاتصال بالإنترنت وكنت تختبر Cloud Firestore أو Realtime Database، يجب استخدام كائن عادي به تداخلات بدلاً من إنشاء DocumentSnapshot أو DataSnapshot فعليين.

لنفترض أنك تكتب اختبار وحدة للدالة التالية:

// Listens for new messages added to /messages/:pushId/original and creates an
// uppercase version of the message to /messages/:pushId/uppercase
exports.makeUppercase = functions.database.ref('/messages/{pushId}/original')
    .onCreate((snapshot, context) => {
      // Grab the current value of what was written to the Realtime Database.
      const original = snapshot.val();
      functions.logger.log('Uppercasing', context.params.pushId, original);
      const uppercase = original.toUpperCase();
      // You must return a Promise when performing asynchronous tasks inside a Functions such as
      // writing to the Firebase Realtime Database.
      // Setting an "uppercase" sibling in the Realtime Database returns a Promise.
      return snapshot.ref.parent.child('uppercase').set(uppercase);
    });

داخل الدالة، يتم استخدام snap مرتين:

  • snap.val()
  • snap.ref.parent.child('uppercase').set(uppercase)

في اختبار الرمز البرمجي، أنشِئ كائنًا عاديًا يعمل فيه كلا مسارَي الرمز، واستخدام Sinon لتغيير الطرق.

// The following lines creates a fake snapshot, 'snap', which returns 'input' when snap.val() is called,
// and returns true when snap.ref.parent.child('uppercase').set('INPUT') is called.
const snap = {
  val: () => 'input',
  ref: {
    parent: {
      child: childStub,
    }
  }
};
childStub.withArgs(childParam).returns({ set: setStub });
setStub.withArgs(setParam).returns(true);

تقديم التأكيدات

بعد تهيئة SDK والتفاف الدوال وإنشاء البيانات، يمكنك استدعاء الدوال المرتبطة بالبيانات التي تم إنشاؤها وإجراء تأكيدات حول السلوك. يمكنك استخدام مكتبة مثل Chai الذين يقدمون هذه التأكيدات.

تقديم التأكيدات أثناء الاتصال بالإنترنت

في حال إعداد Firebase Test SDK لـ Cloud Functions في وضع الاتصال، يجب: التأكيد على أن الإجراءات المطلوبة (مثل كتابة قاعدة البيانات) قد تم اتخاذها عن طريق باستخدام حزمة تطوير البرامج (SDK) لـ firebase-admin.

يؤكد المثال أدناه أن "INPUT" تمت كتابتها في قاعدة بيانات اختبار المشروع.

// Create a DataSnapshot with the value 'input' and the reference path 'messages/11111/original'.
const snap = test.database.makeDataSnapshot('input', 'messages/11111/original');

// Wrap the makeUppercase function
const wrapped = test.wrap(myFunctions.makeUppercase);
// Call the wrapped function with the snapshot you constructed.
return wrapped(snap).then(() => {
  // Read the value of the data at messages/11111/uppercase. Because `admin.initializeApp()` is
  // called in functions/index.js, there's already a Firebase app initialized. Otherwise, add
  // `admin.initializeApp()` before this line.
  return admin.database().ref('messages/11111/uppercase').once('value').then((createdSnap) => {
    // Assert that the value is the uppercased version of our input.
    assert.equal(createdSnap.val(), 'INPUT');
  });
});

تقديم تأكيدات في وضع عدم الاتصال

يمكنك تقديم تأكيدات حول القيمة الناتجة المتوقعة للدالة:

const childParam = 'uppercase';
const setParam = 'INPUT';
// Stubs are objects that fake and/or record function calls.
// These are excellent for verifying that functions have been called and to validate the
// parameters passed to those functions.
const childStub = sinon.stub();
const setStub = sinon.stub();
// The following lines creates a fake snapshot, 'snap', which returns 'input' when snap.val() is called,
// and returns true when snap.ref.parent.child('uppercase').set('INPUT') is called.
const snap = {
  val: () => 'input',
  ref: {
    parent: {
      child: childStub,
    }
  }
};
childStub.withArgs(childParam).returns({ set: setStub });
setStub.withArgs(setParam).returns(true);
// Wrap the makeUppercase function.
const wrapped = test.wrap(myFunctions.makeUppercase);
// Since we've stubbed snap.ref.parent.child(childParam).set(setParam) to return true if it was
// called with the parameters we expect, we assert that it indeed returned true.
return assert.equal(wrapped(snap), true);

يمكنك أيضًا استخدام Sinon sinon من أجل التأكيد على أنه تم استدعاء طرق معينة، وباستخدام المعاملات التي تتوقعها.

اختبار دوال HTTP

لاختبار دوال HTTP onCall، استخدِم الأسلوب نفسه الذي يتبعه اختبار دوال الخلفية.

إذا كنت تختبر دوال HTTP onRequest، فيجب استخدام firebase-functions-test إذا:

  • أنت تستخدم functions.config().
  • تتفاعل الدالة مع مشروع على Firebase أو واجهات Google APIs الأخرى إذا كنت تريد استخدام مشروع Firebase حقيقي وبيانات اعتماده لإجراء اختباراتك.

تأخذ دالة HTTP onRequest معلمين: كائن الطلب والاستجابة . إليك طريقة اختبار مثال الدالة addMessage():

  • إلغاء وظيفة إعادة التوجيه في كائن الاستجابة، بما أنّ sendMessage() وتسميه.
  • ضمن دالة إعادة التوجيه، استخدِم chai.assert للمساعدة في تقديم تأكيدات حول المعاملات التي ينبغي لدالة إعادة التوجيه عن طريق:
// A fake request object, with req.query.text set to 'input'
const req = { query: {text: 'input'} };
// A fake response object, with a stubbed redirect function which asserts that it is called
// with parameters 303, 'new_ref'.
const res = {
  redirect: (code, url) => {
    assert.equal(code, 303);
    assert.equal(url, 'new_ref');
    done();
  }
};

// Invoke addMessage with our fake request and response objects. This will cause the
// assertions in the response object to be evaluated.
myFunctions.addMessage(req, res);

إزالة البرامج غير المرغوب فيها

في نهاية رمز الاختبار، استدعِ دالة التنظيف. عدم ضبط هذا الإعداد متغيّرات البيئة التي حدّدتها حزمة SDK عند إعدادها ويحذفها هي تطبيقات Firebase التي ربما تمّ إنشاؤها إذا استخدمت حزمة تطوير البرامج (SDK) لإنشاء قاعدة البيانات الزمنية DataSnapshot أو Firestore DocumentSnapshot.

test.cleanup();

مراجعة الأمثلة الكاملة والاطّلاع على مزيد من المعلومات

يمكنك مراجعة الأمثلة الكاملة على مستودع GitHub في Firebase.

لمزيد من المعلومات، يُرجى الاطّلاع على مرجع واجهة برمجة التطبيقات. مقابل firebase-functions-test.