350. Using proxies to mock objects

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

יש עוד שימוש נפלא בProxies שטרם ציינתי. השימוש הוא ביצירת Mockים.

למי שלא מכיר, Mockים הם אובייקטים שאנחנו בד”כ משתמשים בהם בUnit Testים על מנת לדמות התנהגות של תלות חיצונית.

למשל, נניח שיש לנו מחלקה שאמורה להתחבר לDatabase ולשלוף טבלאות. את החיבור לDatabase היא מבצעת באמצעות איזשהו ממשק, למשל

1
2
3
4
public interface IDataProvider
{
DataTable RetrieveBySql(string query);
}

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

בUnit Test לא נרצה להתחבר לDatabase, מכמה סיבות:

  • זו פעולה שלוקחת זמן
  • זו פעולה שיכולה להכשל

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

נדחוף לInstance של המחלקה שלנו איזשהו Instance של מחלקה שמממשת IDataProvider שנממש אנו, עם התנהגות משלנו – כלומר במחלקה זו לא יהיה חיבור לDatabase, אלא היא תמומש בצורה אחרת, למשל תחזיר תמיד אותו ערך, או מימוש אחר.

למחלקה שנממש קוראים בד"כ Mock. כאמור Mockים משמשים בעיקר בUnit Testים.


עד כה דיברנו על מה זה Mock באופן כללי.

עכשיו לבעיה: מה קורה אם יש לנו הרבה ממשקים שאנחנו צריכים לעשות להם Mock? מה אם יש להם הרבה פונקציות ואנחנו לא מעוניינים לממש את כולן, אלא רק את חלקן?

יוצא שאנחנו כותבים הרבה מאוד Mockים, ויכול להיות שMock מסוים שהשקענו בו הרבה עבודה משמש אותנו בUnit Test בודד.

כאן Proxyים נכנסים לתמונה:

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

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

למעשה, אין צורך להמציא את הגלגל – יש Frameworkים מצוינים (שאפילו מתבססים על DynamicProxy) שמאפשרים את היכולת הזאת.

אחד הFrameworkים האלה הוא Moq המאפשר לנו לעשות את זה באמצעות Expression Trees של Framework 3.5 (ראו גם טיפים 173-181)

למשל דוגמה ליצירת Mock:

1
2
3
4
5
6
7
Mock<IFoo> mock = new Mock<IFoo>();
string outString = "ack";
// TryParse will return true, and the out argument will return "ack", lazy evaluated
mock.Setup(foo => foo.TryParse("ping", out outString))
.Returns(true);

כעת נוכל לקבל אובייקט שמממש IFoo ע"י:

1
2
3
4
5
6
7
IFoo mockObject = mock.Object;
string outValue;
bool parsed =
mockObject.TryParse("ping", out outValue); // true
// outValue == "ack"

בקיצור, Moq מומלץ בחום לכל מי שנאלץ לכתוב Unit Testים 😃

לזכור ולא לשכוח.

שתף