תגיות: ,
2 תגובות

בפוסט זה נדגים שימוש ראשוני ב Unit Test ככלי לכתיבת קוד נקי יותר, עם צמידות נמוכה ובעל פתיחות  גבוהה לשינויים – כמובן שחשוב להזכיר את העובדה שהרבה יותר זול לתקן באג כאשר הוא מתגלה אצל המתכנת מאשר כאשר הוא מתגלה אצל הלקוח… אך לפני הכל נציין ש Unit Test זה רכיב מתוך תורה שלמה הנקראת TDD, Test Driven Development.

אני אומר שזו תורה שלמה משתי סיבות:

א. מדובר על נושא רחב ומורכב.

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

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

בדרך כלל, כאשר מזכירים ליד מתכנתים את המילה 'טסטים', הם מיד חושבים שזה נושא של QA. ובכן – לא נכון… כל מפתח בודק את הקוד שהוא כתב לפני שהוא מעביר אותו לQA. הרעיון של Unit Test הוא פשוט.

מלבד הקוד שעליו אני עובד, אכתוב גם איזה Console Application (ה GUI החביב עלי) שמריץ את הקוד שכתבתי, וזאת על מנת להימנע מלהריץ את כל המערכת ועל מנת לבדוק רק מתודה אחת ויחידה שנוספה או ששונתה.

הרעיון של Unit Test הוא שבמקום לכתוב Console כזה, אשתמש ב Framework קיים.

יש כמה Frameworks כאלה בשוק – החל מזה הבא Build-In בתוך ה Visual Studio, דרך ReSharper, וכלה בכמה וכמה שאפשר למצוא בNuGet כגון Nunit ודומיו.

יש כמה הבדלים בין ה Frameworks האלה, אבל העקרון זהה ויודגם מיד.

ניצור פרוייקט חדש מסוג Unit Test Project

clip_image002

מה שנפתח לנו הוא למעשה Class Library רגיל, שיש לו Reference אל Microsoft.VisualStudio.QualityTools.UnitTestFramework, וכן נפתח Class לדוגמא שאפשר להשתמש בו או למחוק וליצור חדש על פי הצרכים שלנו.

ב Class שנוצר אפשר להבחין בשני Attributes. אחד מעל שם ה Class – [TestClass] ואחד מעל שם המתודה – [TestMethod].

כעת נריץ את הטסט על ידי לחיצה ימנית על העכבר ובחירה ב Run All Tests – ובגלל שהטסט ריק, ובגלל שהוא לא בודק כלום, הטסט כמובן יעבור ונקבל אור ירוק.

clip_image004

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

או אם ננסה לעשות קצת סדר, התהליך הוא כזה:

שלב א: כתיבת טסט שיכשל (אדום)

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

שלב ג: Refactoring – ארגון הקוד מחדש.

הדגמה:

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

אם כן, יהיה לנו טסט כזה:

   1: [TestMethod]

   2: public void TestMethod1()

   3: {

   4:     int result = Add(3,5);

   5:     Assert.AreEqual(8, result);

   6: }

 

השורה הראשונה מניחה שיש לנו מתודת ADD המקבלת 3 ו 5 ומחזירה מספר הנשמר במשתנה result.

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

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

בדוגמה שלנו אנו משתמשים באפשרות של AreEqual ואנו מניחים ומקווים שהמצופה (8) יהיה שווה למתקבל על ידי המתודה (result).

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

ניצור מתודת Add

   1: private int Add(int i, int i1)

   2: {

   3:     throw new NotImplementedException();

   4: }

ונריץ שנית.

 

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

clip_image006

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

נעשה זאת…

   1: private int Add(int i, int i1)

   2: {

   3:     return 8;

   4: }

 

כעת הטסט עובר…

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

 

השלב הראשון והפשוט – אם כי החשוב הוא נתינת שמות משמעותיים. במקום i i1 ו TestMethod1 נבחר בשמות שהקורא אותם יבין על מה מדובר.

   1: [TestClass]

   2: public class ClaculatorShould

   3: {

   4:     [TestMethod]

   5:     public void AddTowNumbers()

   6:     {

   7:         int result = Add(3,5);

   8:         Assert.AreEqual(8, result);

   9:     }

  10:

  11:     private int Add(int num1, int num2)

  12:     {

  13:         return 8;

  14:     }

  15: }

השלב הבא הוא כתיבת דבר הגיוני בתוך המתודה – ללא שינוי הטסט!

   1: private int Add(int num1, int num2)

   2: {

   3:     return num1 + num2;

   4: }

 

כעת נריץ שוב את הקוד, ונוודא שלמרות כל השינויים שעשינו הוא ממשיך לעבור.

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

כעת הקוד יראה כך:

clip_image008

וכמובן שנריץ את הטסט שוב על מנת לוודא שלא שברנו שוב דבר.

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

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

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

כמה פיצ'רים

אתחול

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

בשביל לפתור את הבעיה הזו ניצור קלאס שמדמה את ה Constractor ונשים מעליו Attribute של [SetUp] או [Test Initialize] (תלוי באיזה Framework עובדים).

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

קיבוץ לפי קטגוריות

בחלק מה Frameworks, אם נרצה לקבץ כמה טסטים הקשורים לאותו הנושא ולהריץ אותם בנפרד, נוכל להוסיף Attribute שנראה כך:

   1: [TestCategory("CategoryName")]

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

 

 

ExpectedException

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

הקוד נראה כך:

   1: [TestMethod]

   2: [ExpectedException(typeof (DivideByZeroException))]

   3: public void DivideByZero()

   4: {

   5:     //some code             

   6: }

StringAssert ו CollectionAssert

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

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

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

סיכום

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

בהצלחה!

הוסף תגובה " class="ir icon-in">linkedin twitter email