אחד מהאתגרים הגדולים ביותר בכתיבת unit testing לאפליקציה שלנו היא העובדה שלעתים אנו תלויים בגורמים חיצוניים שמקשים עלינו לבדוק כל יחידה בצורה עצמאית. למשל, קטע קוד הכותב למדפסת או למערכת קבצים.
שימוש ב- Fakes ב- Visual Studio 2012
VIsual Studio 2012 מציגה פיצ’ר חדש ושימושי שנקרא Fakes. הפיצ’ר נותן לכם 2 יכולות חדשות: להוסיף stubs ו- shimes באמצעות כתיבה מינימלית של קוד וללא שימוש ב- mocking framework חיצוני, מה שמאפשר כתיבת בדיקות קלה ומהירה יותר במקרים של קוד שבד”כ נתקשה לבדוק אותו בשל התלות שלו בגורמים “חיצוניים”.
אחד מכללי הברזל של Unit Testing אומר, כי עלינו לבדוק כמה שיותר את הקוד שביחידה שלנו, וכמה שפחות את הקוד ש"בסביבה". כך, למשל, אם יש לנו פונקציה שמחשבת ערך מסויים (נניח מכפלה של שני מספרים), שומרת אותו לקובץ ומדפיסה אותו במדפסת, נשאף לכתוב לה בדיקות הבודקות את נכונות החישוב, וכן בודקות שאכן למדפסת ולקובץ נשלח הערך הנכון.
לא נרצה לכתוב בדיקה שתהיה תלויה בהמצאות מדפסת בזמן הרצת הבדיקה, או בזכויות גישה לתקיה מסוימת בדיסק – מה שייתכן שלא יתקיים אם אנחנו מריצים את הבדיקה על ה-Build Server או בכלל בענן.
כדי להשיג את המטרה הזו, נצטרך אובייקט "מפוברק" (Fake) שיחליף את הסביבה האמיתית.
אם כתבנו את הקוד לפי כללים נכונים והשתמשנו כיאות ב-Dependency Injection , הקוד שלנו ייראה כך בערך:
public class Multiplier
{
private IPrintHandler m_printHandle;
private IFileSystemHandler m_fileHandle;
public Multiplier(IPrintHandler printHandle, IFileSystemHandler fileHandle)
{
m_printHandle = printHandle;
m_fileHandle = fileHandle;
}
public int MultipySaveAndPrint(int m, int n)
{
var result = m * n;
var strResult = result.ToString();
m_printHandle.Print(strResult);
m_fileHandle.Save(strResult);
return result;
}
}
כאשר ה-Interfaces בהם אנו משתמשים יוגדרו כך:
public interface IFileSystemHandler
{
void Save(string strToSave);
}
public interface IPrintHandler
{
void Print(string strToPrint);
}
במקום אחר בקוד יופיע יישום של IFileSystemHandler ו-IPrintHandler שאכן כותב לקובץ ומדפיס למדפסת.
(עוד על Dependency Injection).
Stubs
כדי לבדוק את ה-Multiplier ללא תלות במדפסות או במערכת קבצים, כל מה שנצטרך לעשות הוא לכתוב יישום משלנו ל-IPrintHandler ו-IFileSystemHandler. את זה נעשה בצורה הבאה, תוך שימוש בתכונת ה-Fake של Visual Studio 2012:
נוסיף ל-Solution שלנו Test Project וניצור ממנו Reference לפרוייקט שבו כתבנו את ה-Multiplier (בדוגמא שיצרתי הוא נקרא TrialLib).
כעת נצביע על ה-Reference ונלחץ על מקש העכבר הימני. יופיע התפריט הבא:
נבחר Add Fakes Assembly וכעת יתווספו לפרוייקט שני אלמנטים חדשים – ספריה בשם TrialLib.Fakes וקובץ הגדרות באותו שם:
בשלב זה, Visual Studio 2012 כבר ייצר לנו יישום ל-Interfaces, שבו נוכל להחליף כל מתודה שנרצה. כך, למשל, נוכל לכתוב Unit Test בצורה הבאה:
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using TrialLib;
using TrialLib.Fakes;
namespace TryConsoleUnitTestProject
{
[TestClass]
public class UnitTestMultiplier
{
[TestMethod]
public void TestMultiply()
{
string ActualSavedString = "";
string ActualPrintedString = "";
var stubSave = new StubIFileSystemHandler
{
SaveString = (string textToSave) => { ActualSavedString = textToSave; }
};
var stubPrint = new StubIPrintHandler
{
PrintString = (string textToSave) => { ActualPrintedString = textToSave; }
};
var multiplier = new Multiplier(stubPrint, stubSave);
var Actual = multiplier.MultipySaveAndPrint(5, 7);
Assert.AreEqual<int>(35, Actual);
Assert.AreEqual<string>("35", ActualSavedString);
Assert.AreEqual<string>("35", ActualPrintedString);
}
}
}
שימו לב לצורת הכתיבה – Fakes כבר יצר עבורי Class המיישם את ה- Interface (בשם Stub ולאחריו שם ה-Interface), ומה שנשאר לי הוא להחליף את המתודות שמעניינות אותי במתודות בדיקה.
שם המתודה יורכב מקומבינציה של השם המקורי והפרמטרים – כך מתודת ה- Save שמקבלת פרמטר מסוג String תקרא SaveString, ובדוגמא הזו החלפתי אותה במתודה משלי שפשוט שומרת את הפרמטר במשתנה ActualSavedString כדי לבדוק אחר כך אם אמנם נשלח למתודה הפרמטר הנכון.
בצורה דומה טיפלנו במתודת ה-Print. כך בדקתי שהמתודה שולחת את הטקסט הנכון לשמירה ולהדפסה, ללא צורך לעבוד באמת מול מדפסת או דיסק.
בדוגמא זו השתמשתי בשיטה הנקראת Stub (כפי שמעיד גם שם ה-Classes ש-Fake ייצר בשבילנו, כלומר StubIFileSystemHandler ו- StubIPrintHandler . זוהי השיטה הנכונה, לדעתי, לבודד את המתודה הנבדקת מהסביבה האמיתית. אם תרצו, זו כנראה השיטה שילמדו בהוגוורטס אם אי פעם ילמדו שם הנדסת תוכנה..
Shimes – כאשר אין אפשרות לבצע Refactor לקוד
קיים עדיין בעולם (לא אצלכם חלילה) קוד שפונה ישירות לרכיבים חיצוניים – דרך ספריות של מיקרוסופט או של צד שלישי – ולא עושה לשם כך שימוש בממשק ברור וניתן להחלפה. כמובן שהטיפול הנכון בקוד כזה הוא כתיבתו מחדש בצורה נכונה, תוך שימוש ב-Dependency Injection כמו שהראינו למעלה. בכל זאת, קיימים עדיין מנהלים בעולם (לא המנהלים שלכם חלילה) שהאפשרות של שכתוב מחדש של כל המערכת גורמת להם גירוד במקומות לא נעימים, ובכל זאת הם דורשים שתוסיפו Unit Tests למתודות הישנות.
בשביל האפשרות הזו נוצרו כוחות האופל – ה-Shims. הם מאפשרים ל- Fakes להחליף כל קריאה למתודה בקריאה "מפוברקת" גם בלי שהקוד הקורא מודע לכך, ובזה מאפשרים, בעצם, לכתוב Unit Tests לכל קוד שהוא. כמו שכל מי שהתעסק עם כוחות אופל יודע, השימוש בהם בדרך כלל אינו בחינם, אבל על כך – בפוסט הבא.
מומלץ לקרוא את הבלוג המצויין של Peter Provost להבנה מלאה של Stubs ו-Shims.
יש לכם שאלות נוספות בנושא VS 2012, ALM או Testing?
הכנסו עכשיו לפורום העברי שלנו בנושא והתייעצו עם מיטב מומחי הקהילה.
הפוסט נכתב על ידי יואל ארנון, מהנדס תוכנה במיקרוסופט המסייע ללקוחות פרמייר - Premier Field Engineer. בעבר יואל היה יועץ עצמאי וחבר בצוות הפיתוח של MSMQ במיקרוסופט חיפה.