רק רציתי לעדכן פוסט מהעבר מתודה חדשה להשוואת אובייקטים בעת הבדיקות, משהו יותר נחמד עם ג'נריקס
להזכיר, רציתי להשוות שני אובייקטים – אחד מה שהקוד החזיר ואחד זה מה שמצופה, אז כתבתי מתודה פשוטה שתשווה בין כל 2 אובייקטים.
הוספתי בסוף אופציה להוריד בדיקה של חלק מהמאפיינים – כמו למשל זמן יצירה שיכול להיות שונה בדטה בייס:
public static void Compare<T>(T expected, T actual, params string[] exclude)
{
Assert.IsNotNull(expected);
Assert.IsNotNull(actual);
List<string> p = new List<string>();
if (exclude != null)
{
p.AddRange(exclude);
}
foreach (var pi in typeof(T).GetProperties())
{
if (p.SingleOrDefault(a => a.Equals(pi.Name, StringComparison.InvariantCultureIgnoreCase)) != null)
{
continue;
}
Assert.AreEqual(pi.GetValue(expected, null), pi.GetValue(actual, null), pi.Name);
}
}
והשימוש פשוט כמובן:
[TestMethod()]
public static void Test()
{
var a = new MailMessage()
{
Subject = "MYSubject",
Body = Guid.NewGuid().ToString()
};
var b = new MailMessage()
{
Subject = "MYSubject",
Body = Guid.NewGuid().ToString()
};
Compare<MailMessage>(a, b, null);
Compare<MailMessage>(a, b, "Body");
}
Currently rated 5.0 by 1 people
- Currently 5/5 Stars.
- 1
- 2
- 3
- 4
- 5
רוב מה שאני כותב בקשר לרפלקשן אני משתמש לצורך ה unit testing שלי, בדרך כלל אני מנסה להימנע משימוש בזה בייצור.
עם כל ההנאה שיש בקוד דינאמי, לא תמיד אני יכול להרשות את זה לעצמי, בעיקר בגלל נושא הביצועים.
אז לפני כמה ימים, כשאני כותב כמה בדיקות לקוד, עלתה בדעתי מחשבה.
הרבה מהקוד שלי מבצע עידכון וטעינה של מידע מהדטה בייס לאובייקטים עסקיים.
הרבה משתמשים ב ORM לצורך ביצוע המשימות האלה, אך שוב, באפליקציה הראשית של החברה שבה אני עובד אין אפשרות להשתמש בתוכנות אלו, גם בגלל ביצועים, וגם בגלל מורכבות השאילתות והקשרים בין הטבלאות.
אז יוצא שהרבה בדיקות שאני כותב הם לפונקציות שלוקחות אובייקט עסקי ומעדכנות את הטבלאות המתאימות, ומצד שני פונקציות שמושכות מידע ויוצרות את האובייקטים העסקיים.
המטרה שלי בבדיקה היא לבדוק שאכן האובייקט שנטען מהדטה בייס מכיל את אותם הנתונים שהכיל האובייקט ששלחתי לשמירה. כך יצא שמאות פעמים אני צריך לכתוב משהו בסגנון
FieldInfo a;
FieldInfo b;
Assert.AreEqual(a.FieldType, b.FieldType, "field type");
Assert.AreEqual(a.IsAssembly, b.IsAssembly, "is assembly");
וכך עבור כל פרופרטי שיש לאובייקט. ואז אמרתי לעצמי, למה להיות חמור? בסך הכל אתה משווה ערכים, תכתוב כבר פונקציה שתעשה את זה בשבילך. וזה מה שיצא לי:
public static void CompareObjects(object a, object b)
{
if (a == null) Assert.Fail("a is null");
if (b == null) Assert.Fail("b is null");
Type t1 = a.GetType();
Type t2 = b.GetType();
if (t1 != t2)
{
Assert.Fail("Type a (" + t1.Name + ") is not as type as type b(" t2.Name + ")");
}
PropertyInfo[] pi = t1.GetProperties();
for (int i = 0; i < pi.Length; i++)
{
object val1 = pi[i].GetGetMethod().Invoke(a, null);
object val2 = pi[i].GetGetMethod().Invoke(b, null);
Assert.AreEqual(val1, val2, pi[i].Name);
}
}
הסבר קטן למי צריך: קודם אני בודק שאכן המצביעים לאובייקטים לא ריקים. אחר כך אני בודק שהם בכלל מאותו סוג של אובייקט. רק עכשיו אני מושך את כל הפרופרטיס שלהם ומתחיל להשוות אחד אחד... נחמד, בכמה שורות חסכתי מאות שורות, או שבעצם לא נחמד, שעד עכשיו לא עלה על דעתי לעשות את זה... ישנה עוד תוספת קטנה לפונקציה. הרבה מהאובייקטים מכילים תאריך עדכון אחרון שניתן על ידי בסיס הנתונים באופן אוטומטי, וגם במקרה של הכנסה חדשה לבסיס הנתונים יש את הערך של השדה המזהה בדרך כלל, ערכים אלו יהיו שונים בין האובייקט לפני ההכנסה לדטה בייס לאובייקט שנטען לאחר ההכנסה ואז הפונקציה תמיד תזרוק הודעת כישלון.... אז הנה תיקון קטן לקוד:
public static void CompareObjects(object a, object b, string[] exclude)
{
if (a == null) Assert.Fail("a is null");
if (b == null) Assert.Fail("b is null");
Type t1 = a.GetType();
Type t2 = b.GetType();
if (t1 != t2)
{
Assert.Fail("Type a (" + t1.Name + ") is not as type b (" + t2.Name + ")");
}
PropertyInfo[] pi = t1.GetProperties();
for (int i = 0; i < pi.Length; i++)
{
if (exclude != null && Array.IndexOf<string>(exclude, pi[i].Name) > -1)
{
continue;
}
object val1 = pi[i].GetGetMethod().Invoke(a, null);
object val2 = pi[i].GetGetMethod().Invoke(b, null);
Assert.AreEqual(val1, val2, pi[i].Name);
}
}
כעת הפונקציה מקבלת רשימה של פרופרטיס שאותם לא צריך לבדוק לדוגמה:
Customer a;
Customer b;
CompareObjects(a, b, new string[] { "Id", "LastUpdate" });
Currently rated 4.0 by 1 people
- Currently 4/5 Stars.
- 1
- 2
- 3
- 4
- 5
"עיצוב מונחה בדיקות" , זה המושג המדויק... מה הכוונה? ובכן מתוך מספר שיטות תיכנון ועיצוב תוכנה, אחת השיטה נקראת extremeprogramming ואחת מנקודות השיטה הזאת היא שאנו בונים את האפליקציה על בסיס בדיקות. אבל לא כרגיל שאנו בונים את האפליקציה ואז עושים בדיקות, אלא קודם מציבים את הבדיקות ואז בונים את האפליקציה כך שתוכל לעבור את הבדיקות! אני לא ארחיב כעת על התיאוריה וכל מה שקשור, אני רק אראה צורה מאוד נוחה, שיכולה לשמש בכל אפליקציה, לבצע בדיקות באפליקציה. זה אומנם נראה קצת מסורבל בהתחלה, אבל אחרי שמתרגלים אי אפשר לעזוב את זה :)
קודם כל צריך להוריד את תוכנית ההתקנה ל NUnit
פשוט תורידו את ההתקנה לחלונות ותריצו.
כעת ניצור פרוייקט חדש ונוסיף הפניה ל NUnit. שימו לב שבחרתי פרויקט מסוג class library, זה לא חובה אבל עדיף
כעת נבנה לנו מחלקה חדשה שתייצג ריבוע, הריבוע יודע להחזיר את השטח שלו וניתן לשנות לו את הגודל
public class Cube {
private int width;
private int height;
public Cube(int w, int h) {
if (w <= 0)
{
throw new ArgumentOutOfRangeException("width must be a positive integer");
}
if (h <= 0)
{
throw new ArgumentOutOfRangeException("height must be a positive integer");
}
this.width = w;
this.height = h;
}
public void Resize(int w, int h) {
if (w <= 0)
{
throw new ArgumentOutOfRangeException("width must be a positive integer");
}
if (h <= 0)
{
throw new ArgumentOutOfRangeException("height must be a positive integer");
}
this.width = w;
this.height = h;
} public int Size {
get { return width * height; }
}
}
כדי להשתמש ב NUnit עלינו להגדיר לה מה כאן בדיקות , לשם כך נוסיף את הסימון [Test] מעל כל פונקציה שאנו רוצים לבדוק, במקרה שלנו זה Resize ו Size
[Test]
public void Resize(int w, int h)
{
if (w <= 0)
{
throw new ArgumentOutOfRangeException("width must be a positive integer");
}
if (h <= 0)
{
throw new ArgumentOutOfRangeException("height must be a positive integer");
}
this.width = w;
this.height = h;
}
אני חייב להגיד שגם בצורת הבדיקות יש כל מיני גישות... וכמובן שאני אציג את זאת שאני חושב שהיא הכי טובה :)
כדי לבדוק את הריבוע שלי, או כל דבר אחר באפקליציה, אני אצור מחלקה שכל מטרתה הוא לבדיקות. עדיף אפילו ליצור פרוייקט נפרד באותו שם עם תוספת Test בסופו שכל מטרתו הוא לבדוק את הפרוייקט המקורי שלי. ניצור מחלקה נוספת בשם CuteTest.
במחלקה אני אצור מספר פונקציות כשכל אחת אמורה לבדוק משהו אחר בריבוע שלנו:
public class CuteTest {
public void GoodTest()
{
Cube c = new Cube(3, 4);
int s = c.Size;
Console.WriteLine("size : " + s); }
public void BadTest()
{
Cube c = new Cube(-2, 3);
int s = c.Size;
Console.WriteLine("size : " + s);
}
public void AnotherBadTest()
{
Cube c = new Cube(3, 6);
c.Resize(4, -2);
int s = c.Size;
Console.WriteLine("size : " + s);
}
public void ZeroTest()
{
Cube c = new Cube(0, 0);
int s = c.Size;
Console.WriteLine("size : " + s);
}
}
* שימו לב שחלק מהבדיקות אמורות להיכשל כי הכנסתי ערך שלילי...
*הגדרת המחלקה והפונקציות חייבת להיות public
כעת נגיד ל NUnit שאלו הבדיקות שלנו על ידי הוספת סימון [Test] לפני שם כל פונקציה
[Test]
public void ZeroTest()
{
Cube c = new Cube(0, 0);
int s = c.Size;
Console.WriteLine("size : " + s);
}
ונריץ את UNnit (בעת ההתקנה נוסף לנו פריט לתוכניות בתפריט ההתחלה)
ונטען את הפרוייקט שלנו
אנו רואים שהבדיקות שלנו מופיעות כבר. כעת רק נשאר ללחוץ על הרצה
ואנו רואים שרק בדיקה אחת עברה בהצלחה...
אבל אנחנו כן מרוצים מכך שהשאר לא עברו בהצלחה, ובשבילנו הבדיקות האלה כן מוצלחות כי ציפינו לשגיאה ואכן קיבלנו שגיאה... אז נגיד ל NUnit שהפונקציה אמורה לקבל שגיאה, ואז אם לא נקבל שגיאה אזי הבדיקה נכשלה, ואם כן נקבל שגיאה כצפוי אז הבדיקה נחשבת כמוצלחת
[Test]
[ExpectedException(typeof(ArgumentOutOfRangeException))]
public void BadTest()
{
Cube c = new Cube(-2, 3);
int s = c.Size;
Console.WriteLine("size : " + s);
}
ונריץ שוב
NUnit מגיע גם שורת פקודה ולא רק עם GUI כך שניתן להריץ אותו באופן אוטומטי לאחר כל בניה של פרוייקט על ידי שינוי ההגדרות של בניית הפרוייקט בויז'ואל סטודיו.
יש ב NUnit עוד הרבה אפשרויות כמו למשל Assert שדומה למחלקה Debug שיש בדוט נט. אצלנו הפונקציות היו פשוטות ורק בדקתי שאכן הריבוע שלי מוכן לקבל ערכים חוקיים בלבד, אך איך לבדוק שהגודל שהוא מחזיר לי הוא אכן תקין?
לשם כך נשתמש ב Assert
[Test]
public void GoodTest()
{
Cube c = new Cube(3, 4);
int s = c.Size;
Assert.AreEqual(s, 3 * 4);
Console.WriteLine("size : " + s);
}
כעת אם התוצאה לא תהיה נכונה אזי הבדיקה תיכשל
אפשר להמשיך עוד הרבה אבל אני אשאיר לכם לגלות לבד את עולם הבדיקות האוטומטיות, העיקר שהתקנתם והתחלתם...
להורדת הפרוייקט
Technorati Tags:
NUnit,
UnitTesting
Be the first to rate this post
- Currently 0/5 Stars.
- 1
- 2
- 3
- 4
- 5
הפעם אני לא אכתוב קוד, אני רק אראה קוד שמישהו אחר כתב, אבל בלתי אפשרי להסתדר בלי התוכנה הקטנה הזאת...
מדובר על התוכנה Test Driven שבשיא הפשטות ניתן להגיד שהיא בסך הכל מריצה עבורכם את הקוד ישירות מסביבת הפיתוח. מה? כן, כמו שזה נשמע, לחצן ימני על הפונקציה שעכשיו כתבתם, לחיצה על Run Test והפרוייקט מתקמפל, הפונקציה מופעלת והכל נסגר.
ועדיין אולי לא ראיתם את היתרון, אבל עכשיו תראו... כמה פעמים כתבנו class libraies? Dllים שאמורים לשמש תוכניות אחרות, ואז כדי לבדוק אותם בזריזות כתבנו איזה WinForm שיריץ אותם? אז זה כל העניין, כעת ברוב הפעמים אני לא אצטרך את זה! כי התוכנה מריצה גם Dllים שהם ספריות.
טוב בואו נתחיל, קודם נוריד את התוכנה מהאתר שלהם
נסגור את ויז'ואל סטודיו, נריץ את ההתקנה ונפתח את ויז'ואל סטודיו שוב. לחצן ימני יראה לנו את האפשרויות:
כפי שניתן לראות, וגם זה צוין בעת ההתקנה, התוכנה תומכת גם בפריימורקס אחרים לביצוע בדיקות ומבחני קוד כגון NUnit שעליה כבר דיברנו
ועכשיו סתם דוגמה קטנה שיהיה נחמד, נניח שבמחלקה שלי יש פונקציה עם איזה אלגוריתם מסובך, במקום כל פעם לבנות מחדש את כל הפרויקט , להריץ ולבדוק בדיבוג את האלגוריתם אני יכול פשוט להריץ אותו ישירות. ומה אם הפונקציה מקבלת פרמטרים? אז נוסיף לקוד פונקציה נוספת שנקרא לה בשם Tester לדוגמה, למהדרין ביננו ניתן להקיף אותה בהוראות לקומפיילר שרק בדיבוג יכלול את הפונקציה הזאת (#if DEBUG.... #endif)
בפונקציה הזאת אני אריץ את הפונקציה המסובכת ואשלח לה פרמטרים. את התוצאה ניתן או להדפיס דרך Debug.Write("aa") ואז נראה את התוצאות בחלון הלבן הקטנצ'יק שמראה לנו את כל השגיאות בקימפול תמיד, או להגדיר שפונקציית הטקסט שלנו מחזירה ערך, ואז Test Driven ידפיס את הערך הזה בעצמו בחלון הקטנצ'יק הזה...
שימו לב שאם הפונקציה שאנו רוצים להריץ היא לא סטטית, אז אנחנו צריכים לספק גם בנאי ללא פרמטרים למחלקה שבה רצה הפונקציה, כדי ש TestDriven תוכל ליצור אובייקט מהמחלקה הזאת, או לחילופין פונקציה הבדיקה עצמה יכולה להיות סטטית והיא תיצור מופע חדש מהמחלקה שאותה אנו רוצה לבדוק ותקרא לפונקצית המטרה.
public class MyClass
{
#if DEBUG
public static void Tester1()//this is for the static method test
{
int result=MyClass.DoMath(3,5);
System.Diagnostics.Debug.WriteLine(result);
}
#endif
public static int DoMath(int a, int b)
{
return a * b;//this is my complicated algorithm
}
}
Be the first to rate this post
- Currently 0/5 Stars.
- 1
- 2
- 3
- 4
- 5