מה כדאי לסטודנט לדעת- טסטים

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

אז הנושא הראשון שבחרתי הוא-

טסטים- בדיקות (Tests)

ואחרי כל ההקדמה- על מה מדובר?

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

מקרה לדוגמא:

מבוסס על סיפור אמיתי- הטסטים הראשונים שלי.

ניקח תרגיל ממבנה נתונים:

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

אנחנו נממש את התכנית בעזרת רשימה מקושרת ממוינת. תהיה לנו מחלקת Person שתחזיק את הנתונים על האיש, ואת הPerson הבא. בנוסף, תהיה לנו מחלקה PersonList שתחזיק את האיש הראשון, ותבצע את הפעולות של החיפוש, הוספה וכו'.

אני אציג רק את הקוד הרלוונטי כדי לא להעמיס (המימוש הוא בשפת #C. אני מחפש תוסף שיאפשר להציג בכמה שפות):

[code language="csharp" classname="code"]<br>
class PersonList<br>
{<br>
    private Person head;<br>
    public void InsertNewPerson(Person person)<br>
    {<br>
        var temp = head;<br>
        while (temp.Next()?.age > person.age)<br>
        {<br>
            temp = temp.Next();<br>
        }<br>
        var next = temp.Next();<br>
        temp.SetNext(person);<br>
        person.SetNext(next);<br>
    }<br>
    public int GetMinAge()<br>
    {<br>
        var temp = head;<br>
        while (temp.HasNext()) temp = temp.Next();<br>
        return temp.age;<br>
    }<br>
    public double GetAverage()<br>
    {<br>
        var temp = head;<br>
        var sum = 0, i = 0;<br>
        for (; temp.HasNext(); i++, temp = temp.Next()) sum += temp.age;<br>
        return sum / i;<br>
    }<br>
}<br>
[/code]

מעולה. עכשיו אנחנו רוצים כמובן לראות שהכל בסדר. מה אנחנו עושים? מפעילים את התוכנה, בוחרים אופציה בתפריט, מזינים איזה 10 אנשים ובודקים את הממוצע, מה שלוקח פחות או יותר 20 דקות, כי בהכנסה התשיעית אנחנו מתבלבלים ומזינים שם של אדם במקום אופציה בתפריט, והתוכנה נופלת ומתחילים שוב.

לא!

סוגי הבדיקות

הבדיקות נחלקות ל4 קבוצות, כשכל קבוצה מרחיבה את הבדיקות של הקבוצה הקודמת.

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

בדיקות יחידה (Unit Tests):

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

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

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

[code language="csharp"]
[TestClass]
class PersonListTests
{
    [TestMethod]
    public void InsertNewPerson()
    {
        var personToAdd = new Person("Baruch", 25);
        var personList = new PersonList();
        personList.InsertNewPerson(personToAdd);
    Assert.AreEqual(personToAdd, personList.GetHead());
}

[TestMethod]
public void GetMinAge()
{
    var personList = new PersonList();
    personList.InsertNewPerson(new Person("A", 34));
    personList.InsertNewPerson(new Person("B", 100));
    personList.InsertNewPerson(new Person("C", 0));
    personList.InsertNewPerson(new Person("D", 34));
    personList.InsertNewPerson(new Person("E", 22));
    personList.InsertNewPerson(new Person("F", 57));

    Assert.AreEqual(0, personList.GetMinAge());
}

[TestMethod]
public void GetAverage()
{
    var personList = new PersonList();
    personList.InsertNewPerson(new Person("A", 34));
    personList.InsertNewPerson(new Person("B", 100));
    personList.InsertNewPerson(new Person("C", 0));
    personList.InsertNewPerson(new Person("D", 34));
    personList.InsertNewPerson(new Person("E", 22));
    personList.InsertNewPerson(new Person("F", 57));

    Assert.AreEqual(41, (int)personList.GetMinAge());
}

} [/code]

אם נריץ את הקוד בעזרת הפקודה `dotnet test` למשל, נקבל דו"ח עבור שלושת הטסטים והאם הם עברו.

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

בדיקות אינטגרציה (Integration Tests):

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

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

בדיקות מערכת (System Tests):

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

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

בכך שאנחנו עושים את הבדיקות בשלבים, אנחנו יודעים שאם נתקלנו בבעיה בשלב המערכת, כנראה שהבעיה היא בסביבה ולא בלוגיקה של הקוד (אם כי תמיד צריך גם לחשוב- אולי יש בעיה בלוגיקה של הקוד שלא כיסינו בשלבים הקודמים?)

בדיקות קבלה (Acceptance Tests):

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

להרחבה:

  1. נושא אחד שלא נכנסתי אליו בפוסט הוא השיטות והטכניקות לזיוף אובייקטים בבדיקות שאינם נדרשים כרגע. כדי לכתוב בדיקות טובות, אנחנו צריכים להתמקד במשהו מסוים לבדיקה, ולוודא ששאר הדברים בתוכנה לא משפיעים על תוצאות הבדיקה (כי אחרת איך נדע איפה הבעיה?)
  2. נושא שני הוא תהליכי CI, ששם הבדיקות תופסות חלק משמעותי, ואני מקווה לכתוב על זה פוסט אחר בהזדמנות.

מושגים

  • agile- עדיין אין פירוט למושג הזה.
  • unit-test- עדיין אין פירוט למושג הזה.
  • integration-test- עדיין אין פירוט למושג הזה.