כלי המקצוע

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

כפי שהוזכר בקטע הקודם, הרכיבים הבסיסיים האלה כמעט תמיד כוללים להגדיר בדיקות עצמאיות (שנקראות מקרי בדיקה) ולספק טענות נכוֹנוּת (assertions). טענות נכוֹנוּת הן דרך לשלב בין בדיקת תוצאה לבין אם משהו שגוי, והוא יכול להיחשב כרכיב הבסיסי פרימיטיבים לבדיקה.

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

לדוגמה:

import { fibonacci, catalan } from '../src/math.js';
import { assert, test, suite } from 'a-made-up-testing-library';

suite('math tests', () => {
  test('fibonacci function', () => {
    // check expected fibonacci numbers against our known actual values
    // with an explanation if the values don't match
    assert.equal(fibonacci(0), 0, 'Invalid 0th fibonacci result');
    assert.equal(fibonacci(13), 233, 'Invalid 13th fibonacci result');
  });
  test('relationship between sequences', () => {
    // catalan numbers are greater than fibonacci numbers (but not equal)
    assert.isAbove(catalan(4), fibonacci(4));
  });
  test('bugfix: check bug #4141', () => {
    assert.isFinite(fibonacci(0)); // fibonacci(0) was returning NaN
  })
});

בדוגמה הזו נוצרת קבוצה של בדיקות (שלפעמים נקראת חבילה) שנקראת 'מתמטיקה' בדיקות", ומגדיר שלושה מקרי בדיקה עצמאיים, שכל אחד מהם מריץ טענות נכונות (assertions). בדרך כלל ניתן לטפל במקרי הבדיקה האלה בנפרד או להריץ אותם, לדוגמה על ידי במסנן הבדיקה.

עוזרים לטענות נכונות (assertions) בתור פרימיטיביות

רוב מסגרות הבדיקה (frameworks), כולל Vitest, כוללות אוסף של טענות נכונות (assertions) עזרים באובייקט assert, שמאפשרים לבדוק במהירות ערכים מוחזרים לבין ציפיות מסוימות. הציפייה הזו נקראת בדרך כלל 'טוב ידוע' ערכים. בדוגמה הקודמת, אנחנו יודעים שמספר פיבונאצ'י ה-13 צריך להיות 233, כדי שנוכל לאשר את זה ישירות באמצעות assert.equal.

עשויות גם להיות לכם ציפיות לגבי הפורמט של ערך מסוים, או גדול מערך אחר או שיש לו נכס אחר. בקורס הזה כוללים את כל מגוון הגורמים האפשריים לטענות נכונות (assertions), אבל מסגרות להתנסות לספק תמיד את הבדיקות הבסיסיות הבאות:

  • 'truthy' לבדוק, ולרוב הוא מתואר כ'תקין' לבדוק אם תנאי מסוים מתקיים, תואם לאופן שבו אפשר לכתוב if שבודק אם משהו מצליח, או נכון. בדרך כלל הוא מוגדר כך: assert(...) או assert.ok(...), וגם מקבל ערך יחיד והערה אופציונלית.

  • בדיקת שוויון, כמו בדוגמה של מבחן המתמטיקה, שבה אתה מצפה מוחזר ערך או מצב של אובייקט כדי להיות שווה לערך טוב ידוע. אלה הן עבור שוויון פרימיטיבי (למשל למספרים ולמחרוזות) או שוויון ייחוס (האם אלה אותו אובייקט). מאחורי הקלעים, אלה הן רק 'אמת' לסמן עם השוואה של == או ===.

    • JavaScript מבחין בין שוויון חלש (==) לבין שוויון מחמיר (===). רוב ספריות הבדיקה מספקות את השיטות assert.equal ו assert.strictEqual, בהתאמה.
  • בדיקות שוויון מעמיקות, המרחיבות את בדיקות השוויון כולל בדיקה של של אובייקטים, מערכים וסוגי נתונים מורכבים יותר, וגם לוגיקה פנימית לחצות אובייקטים כדי להשוות ביניהם. הנושאים האלה חשובים כי ב-JavaScript אין דרך מובנית להשוות את התכנים של שני אובייקטים או מערכים. לדוגמה, הערך [1,2,3] == [1,2,3] תמיד מוגדר כ-False. ניסוי frameworks כוללות בדרך כלל כלים נוספים מסוג deepEqual או deepStrictEqual.

עוזרים לטענות שמשווים שני ערכים (במקום בדיקת 'נכון' בלבד) בדרך כלל לוקחים שניים או שלושה ארגומנטים:

  • הערך בפועל, כפי שנוצר מהקוד בבדיקה או מתאר את של המצב שצריך לאמת.
  • הערך הצפוי, בדרך כלל בתוך הקוד (לדוגמה, מספר מילולי או ).
  • (אופציונלי) מוסיפים תיאור של מה שצפוי לקרות או מה היה יכול להיכשל. שייכלל אם השורה הזו תיכשל.

הוא גם נפוץ למדי לשלב טענות נכונות כדי ליצור מגוון מאחר שנדיר מאוד שניתן לאמת בצורה נכונה את מצב של המערכת בפני עצמה. לדוגמה:

  test('JWT parse', () => {
    const json = decodeJwt('eyJieSI6InNhbXRob3Ii');

    assert.ok(json.payload.admin, 'user should be admin');
    assert.deepEqual(json.payload.groups, ['role:Admin', 'role:Submitter']);
    assert.equal(json.header.alg, 'RS265')
    assert.isAbove(json.payload.exp, +new Date(), 'expiry must be in future')
  });

Vitest משתמש בספריית הטענות הנכוֹנוּת (assertion) של Cheai באופן פנימי כדי לספק את העוזרים הנאמנים שלו, וייתכן שכדאי לבדוק שלו כדי לראות אילו טענות נכונות (assertions) וגורמים עוזרים מתאימים לקוד שלכם.

טענות נכונות (assertions) ו-BDD

חלק מהמפתחים מעדיפים סגנון של טענות נכונות (assertions) שאפשר לקרוא לו 'מבוסס-התנהגות' פיתוח (BDD), או סגנון יפה טענות נכוֹנוּת (assertions). האינטראקציות האלה נקראות גם 'ציפיות'. עוזרים מאוד, כי נקודת הכניסה בדיקת ציפיות היא שיטה בשם expect().

יש לצפות לעוזרים להתנהג באותו אופן כמו טענות נכונות (assertions) שנכתבו בשיטה פשוטה שיחות כמו assert.ok או assert.strictDeepEquals, אבל יש מפתחים שקל יותר לקרוא אותן. טענת נכוֹנוּת (assertion) של BDD עשויה להיות כך:

// A failure here would generate "Expect result to be an array that does include 42"
const result = await possibleMeaningsOfLife();
expect(result).to.be.an('array').that.does.include(42);

// or a simpler form
expect(result).toBe('array').toContainEqual(42);

// the same in assert might be
assert.typeOf(result, 'array', 'Expected the result to be an array');
assert.include(result, 42, 'Expected the result to include 42');

הסגנון הזה של טענות נכוֹנוּת (assertions) פועל בגלל שיטה שנקראת 'שרשור שיטות', שבו אפשר לשרשר באופן קבוע את השרשרת שחוזרת על ידי expect, קריאות שיטה נוספות. חלקים מסוימים בשיחה, כולל to.be וגם that.does בדוגמה הקודמת, אין להם פונקציה והם נכללים רק כדי לבצע את הקריאה קל יותר לקרוא אותו ואולי גם ליצור תגובה אוטומטית אם נכשל. (שימו לב: בדרך כלל אי אפשר להוסיף תגובה ב-expect, כי השרשרת צריכה לתאר את הכשל בצורה ברורה.)

מסגרות בדיקה רבות תומכות גם ב-Fluent/BDD וגם בטענות נכונות (assertions) רגילות. ביקור, לדוגמה, מייצאות גם הגישה של Chai ויש לו גישה קצת יותר תמציתית לגבי BDD. Jest, מצד שני, כוללת רק ציפיות method כברירת מחדל.

בדיקות קבוצתיות בין קבצים

בזמן כתיבת מבחנים, אנחנו כבר נוטים לספק קבוצות מרומזות – מאשר כל הבדיקות בקובץ אחד, מקובל לכתוב בדיקות . למעשה, מריצים של בדיקות בדרך כלל יודעים שקובץ נועד לבדיקה, של מסנן מוגדר מראש או של ביטוי רגולרי – לדוגמה, 'vitest' כולל את כל קבצים בפרויקט שמסתיימים בסיומת כמו 'test.jsx'. או " .spec.ts" (" .test" ו-" .spec" ועוד כמה סיומות חוקיות).

בדיקות הרכיבים ממוקמות בדרך כלל בקובץ שכנות (peering) של הרכיב הנבדק, כמו במבנה הספריות הבא:

רשימה של קבצים
  כולל UserList.tsx ו-UserList.test.tsx.
קובץ רכיב וקובץ בדיקה קשור.

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

בדיקות קבוצתיות בתוך הקבצים

כפי ששימש בדוגמאות הקודמות, מקובל לבצע בדיקות בתוך קריאה אל suite() שמקבץ את הבדיקות שהגדרת עם test(). בדרך כלל לא מדובר בסוויטות בדיקות בעצמם, אבל הן עוזרות ליצור מבנה על ידי קיבוץ בדיקות קשורות או יעדים, על ידי קריאה לשיטה שהועברה. עבור test(), השיטה שהועברה מתארת את פעולות הבדיקה עצמה.

כמו בטענות נכונות, יש שוויון די סטנדרטי ב-Fluent/BDD ל- של בדיקות קיבוץ. כדי להשוות בין כמה דוגמאות טיפוסיות, השתמשו בקוד הבא:

// traditional/TDD
suite('math tests', () => {
  test('handle zero values', () => {
    assert.equal(fibonacci(0), 0);
  });
});

// Fluent/BDD
describe('math tests', () => {
  it('should handle zero values', () => {
    expect(fibonacci(0)).toBe(0);
  });
})

ברוב המסגרות, suite ו-describe מתנהגים באופן דומה, כך גם test וגם it, בניגוד להבדלים הגדולים יותר בין שימוש ב-expect וב-assert כדי לכתוב טענות נכונות (assertions).

לכלים אחרים יש גישות שונות במקצת לארגון חבילות ובדיקות. עבור לדוגמה, תוכנת ההרצה המובנית של Node.js תומכת בקריאות ל-test() כדי ליצור היררכיית בדיקה. עם זאת, Vitest מאפשר רק סוג כזה של בקינון באמצעות suite(), ולא יפעל test() שהוגדר בתוך test() אחר.

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

שיטות של מחזור החיים

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

לכל 'test() ' או 'it() ' פעם אחת לסוויטה
לפני הרצת הבדיקות 'beforeBefore()' `beforeAll()`
אחרי הרצת הבדיקות 'afterAny() ' 'afterAll() '

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

suite('user test', () => {
  beforeEach(() => {
    insertFakeUser('bob@example.com', 'hunter2');
  });
  afterEach(() => {
    clearAllUsers();
  });

  test('bob can login', async () => {  });
  test('alice can message bob', async () => {  });
});

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

עצה כללית

הנה כמה טיפים שכדאי לזכור כשחושבים על המאפיינים הבסיסיים האלה.

פרימיטיביים הם קו מנחה

חשוב לזכור שהכלים והעקרונות הבסיסיים שכאן ובדפים הבאים התאמה מדויקת של Vitest, Jest, Mocha, Web Test Runner, או כל סוג אחר . אמנם השתמשנו ב-Vitest כמדריך כללי, אבל חשוב למפות בהתאם למסגרת שתבחרו.

לשלב טענות נכונות (assertions) לפי הצורך

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

אבל אם תוכנת ההרצה הזו מספקת גם assert(), expect() ועוזרים של טענות נכונות (assertions), זכרו שהחלק הזה עוסק בנוחות, ואתם יכולים לדלג עליו אם צריכים. אפשר להריץ כל קוד שעלול להציג שגיאה, כולל פקודות ספריות של טענות נכונות (assertions) או הצהרת if בסגנון ישן.

הגדרת סביבת פיתוח משולבת (IDE) יכולה להציל חיים

מוודאים שלסביבת הפיתוח המשולבת (IDE), כמו VSCode, יש גישה להשלמה האוטומטית על כלי הבדיקה שבחרתם יכולים לשפר את הפרודוקטיביות שלכם. עבור לדוגמה, יש יותר מ-100 שיטות ב-assert בטענת הנכוֹנוּת (assertion) של Cheai (צ'אי) בספרייה, ויש תיעוד של יכול להיות נוח.

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

// some.test.js
test('using test as a global', () => {  });

אנחנו ממליצים לייבא את העוזרים גם אם הם נתמכים באופן אוטומטי, כי כך סביבת הפיתוח המשולבת (IDE) יכולה להיות דרך ברורה לחפש את השיטות האלה. (ייתכן שיש לך נתקלו בבעיה הזאת במהלך הפיתוח של React, כי לחלק מרכיבי הקוד יש פונקציית קסם React באופן גלובלי, אבל יש כאלה שלא. צריך לייבא אותם לכל הקבצים באמצעות React.)

// some.test.js
import { test } from 'vitest';
test('using test as an import', () => {  });