בדיקת יחידות של פונקציות ענן

דף זה מתאר שיטות עבודה מומלצות וכלים לכתיבת מבחני יחידות עבור הפונקציות שלך, כגון מבחנים שיהיו חלק ממערכת אינטגרציה מתמשכת (CI). כדי להקל על הבדיקות, Firebase מספקת את Firebase Test SDK עבור פונקציות ענן. הוא מופץ ב-npm כ- firebase-functions-test , והוא SDK נלווה לבדיקה ל- firebase-functions . Firebase Test SDK עבור פונקציות ענן:

  • דואג להגדרה ולפירוק המתאימים עבור הבדיקות שלך, כגון הגדרה וביטול של משתני סביבה הדרושים 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 עבור פונקציות ענן

ישנן שתי דרכים להשתמש firebase-functions-test :

  1. מצב מקוון (מומלץ): כתוב מבחנים המקיימים אינטראקציה עם פרויקט Firebase המוקדש לבדיקה, כך שכותבת מסד נתונים, משתמש יוצר וכו' יתבצעו בפועל, וקוד הבדיקה שלך יוכל לבדוק את התוצאות. זה גם אומר ש-SDKs אחרים של Google המשמשים בפונקציות שלך יעבדו גם כן.
  2. מצב לא מקוון: כתוב בדיקות יחידות מוצקות ולא מקוונות ללא תופעות לוואי. משמעות הדבר היא שכל קריאות למתודה המקיימות אינטראקציה עם מוצר Firebase (למשל כתיבה למסד הנתונים או יצירת משתמש) צריכות להיות מנותקות. השימוש במצב לא מקוון בדרך כלל אינו מומלץ אם יש לך פונקציות Cloud Firestore או Realtime Database, מכיוון שהוא מגדיל מאוד את המורכבות של קוד הבדיקה שלך.

אתחול SDK במצב מקוון (מומלץ)

אם ברצונך לכתוב בדיקות המקיימות אינטראקציה עם פרויקט בדיקה, עליך לספק את ערכי תצורת הפרויקט הדרושים לאתחול האפליקציה דרך firebase-admin , ואת הנתיב לקובץ מפתח של חשבון שירות.

כדי לקבל את ערכי התצורה של פרויקט Firebase שלך:

  1. פתח את הגדרות הפרויקט שלך במסוף Firebase .
  2. באפליקציות שלך, בחר את האפליקציה הרצויה.
  3. בחלונית הימנית, בחר באפשרות להוריד קובץ תצורה עבור אפליקציות אפל ואנדרואיד.

    עבור אפליקציות אינטרנט, בחר Config כדי להציג ערכי תצורה.

כדי ליצור קובץ מפתח:

  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');
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 לוקח 2 פרמטרים:

  1. נתונים (חובה): הנתונים שיש לשלוח ל- 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);

עיין בהפניה ל-API עבור פונקציות דומות עבור כל סוגי הנתונים האחרים.

שימוש בנתונים לדוגמה

אם אינך צריך להתאים אישית את הנתונים המשמשים בבדיקות שלך, 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();

עיין בהפניה ל-API עבור שיטות לקבלת נתונים לדוגמה עבור כל סוג פונקציה.

שימוש בנתונים סתומים (למצב לא מקוון)

אם אתחולת את ה-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, עטיפת הפונקציות ובניית נתונים, אתה יכול להפעיל את הפונקציות הגלומות עם הנתונים שנבנו ולהצהיר על התנהגות. אתה יכול להשתמש בספרייה כגון צ'אי כדי להעלות את הטענות הללו.

הצהרות במצב מקוון

אם אתחולת את Firebase Test SDK for Cloud Functions במצב מקוון , אתה יכול לטעון שהפעולות הרצויות (כגון כתיבת מסד נתונים) בוצעו על ידי שימוש ב- firebase-admin SDK.

הדוגמה להלן קובעת כי '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);

אתה יכול גם להשתמש במרגלי סינון כדי לקבוע ששיטות מסוימות נקראו, ועם פרמטרים שאתה מצפה.

בדיקת פונקציות HTTP

כדי לבדוק פונקציות HTTP onCall, השתמש באותה גישה כמו בדיקת פונקציות רקע .

אם אתה בודק פונקציות HTTP onRequest, עליך להשתמש firebase-functions-test אם:

  • אתה משתמש functions.config()
  • הפונקציה שלך מקיימת אינטראקציה עם פרויקט Firebase או ממשקי API אחרים של Google, ואתה רוצה להשתמש בפרויקט 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();

סקור דוגמאות מלאות ולמד עוד

אתה יכול לעיין בדוגמאות המלאות במאגר Firebase GitHub.

למידע נוסף, עיין בהפניה ל-API עבור firebase-functions-test .