• בלוג
  • עמוד 120
  • איך לבדוק פעולות הקשורות לזמן עם Jest ו JavaScript

איך לבדוק פעולות הקשורות לזמן עם Jest ו JavaScript

18/08/2021

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

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

ב Jest יש מנגנון חמוד שעוזר לנו לבדוק פעולות הקשורות בזמן שנקרא Fake Timers. זה עובד ככה - נניח שיש לכם את הקוד הבא למימוש של משחק זיכרון:

import _ from 'lodash';

export default class MemoryGame {
  constructor(size) {
    this.cards = _.shuffle(_.range(size).flatMap(i => [i, i]).map(i => ({ value: i, visible: false })));
    this.foundPairIndexes = new Set();
    this.activeCardIndex = -1;
    this.hideTimer = null;
  }

  play = (index) => {
    const card = this.cards[index];
    if (card.visible) {
      return;
    }

    card.visible = true;
    clearTimeout(this.hideTimer);
    this.hideTimer = null;

    if (this.activeCardIndex >= 0) {
      if (this.cards[this.activeCardIndex].value === card.value) {
        // Found a match
        this.foundPairIndexes.add(this.activeCardIndex);
        this.foundPairIndexes.add(index);
        this.activeCard = null;
      } else {
        this.hideTimer = setTimeout(this.hideAllCards.bind(this), 2000);
      }
    }
    this.activeCardIndex = index;
  }

  isVisible(index) {
    if (this.foundPairIndexes.has(index)) {
      return true;
    }

    return this.cards[index].visible;
  }

  hideAllCards() {
    this.cards.forEach(c => { c.visible = false; });
  }
}

המשחק מגריל N קלפים לפי הגודל שביקשתם ומערבב אותם, והפונקציה play מטפלת בלוגיקה של המשחק. אם אין קלפים גלויים על השולחן היא הופכת קלף ואם יש כבר קלף גלוי אז היא בודקת אם הקלף שהפכנו עכשיו זהה למה שכבר מופיע, אם כן מציגה את שניהם ואם לא אז מדליקה Timer כדי להפוך את שני הקלפים חזרה בעוד שתי שניות.

אגב - במשחק יש כבר באג. נסו לקרוא את הקוד ולראות אם אתם מבינים מהו.

החלק שקשה לבדוק במשחק הוא השעון, ופה jest יכול מאוד לעזור לנו. הפונקציה jest.useFakeTimers() משתלטת על השעונים ומאפשרת לנו לזוז קדימה בזמן מהר יותר; הפונקציה jest.useRealTimers() מחזירה את המצב לקדמותו. אני אוהב להתחיל את בלוק הבדיקות שלי ב:

  afterEach(function() {
    jest.useRealTimers();
  });

עכשיו מי שירצה יוכל להשתמש ב useFakeTimers ומי שלא לא, ובכל מקרה בסוף כל בדיקה המצב יחזור להתנהגות הרגילה.

אחרי שיש לנו Fake Timers אנחנו יכולים להשתמש בפונקציה advanceTimersByTime של jest כדי לרוץ קדימה בזמן. הנה הבדיקה שמוודאת ששתי שניות אחרי שהפכנו שני קלפים לא תואמים הם יתהפכו חזרה:

it('Turns back the cards when pair is not found', () => {
  jest.useFakeTimers();
  const g = new MemoryGame(5);
  const i1 = 0;
  const i2 = g.cards.findIndex(c => c.value !== g.cards[0].value);

  g.play(i1);
  g.play(i2);
  expect(g.isVisible(0)).toBeTruthy();
  expect(g.isVisible(i2)).toBeTruthy();

  jest.advanceTimersByTime(2500);

  expect(g.isVisible(0)).toBeFalsy();
  expect(g.isVisible(i2)).toBeFalsy();
});

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