בפוסט הזה אני הולך לדבר איתכם על אחד מהנושאים הבעייתיים שיש לנו כאשר אנו מריצים בדיקות אוטומטיות בצורה מקבילית (כמה הרצות שמתבצעות באותו הזמן).
במידה ואנחנו לא נדאג לשמור על האובייקטים שלנו (למשל ה-driver ב-Selenium) או לחילופין כאשר שתי ריצות שונות ינסו לכתוב לאותו קובץ Log בו זמנית אנחנו הולכים לקבל הפתעה לא נעימה. יש לכך כמובן פתרונות ברמה הסביבתית (בואו נריץ על כמה מכונות שונות) או התכנותית (בואו נעבוד עם Design Patterns כמו ה-Singleton או שימוש ב-Thread Safe וכו'…)
במילים אחרות, במקרה של הרצת בדיקות אוטומטיות בסביבה סטרילית על Thread אחד יהפכו את החיים שלנו לדבש, הבדיקות ירוצו כפי שהתכוונו, יעברו או ייכשלו בעקבות באג… הכל סבבה. אבל בעולם האמיתי כשאנחנו מתחילים להריץ על סביבות מרובות, ווירטואליות, הריצות מתנהגות לא כפי שציפינו.
כאמור, אחד מהפתרונות הקיימים (ברמה תכנותית) היא להגן על אובייקטי הריצה או בכלל על כל הריצה , מה שנקרא: Thread Safe. ובשביל לעשות זאת, בואו קודם כל נכיר כמה מושגים בעולם התוכנה:
Stateless Objects:
אובייקטים שהם חסרי מצב (נשמע תירגום גרוע…) הם אובייקטים ללא מאפיינים \ תכונות (properties), שזה אומר שהמחלקה ממנה ייצרנו את המופע איננה מכילה instance fields.
במילים אחרות, אובייקטים אלו ללא מאפיינים אינם צריכים לזכור שום מידע במהלך הפעלתם ומכיוון שכך בבואנו להריץ את האובייקט הזה (נגיד ב-Thread מספר 1) הוא בוודאות לא ישפיע על אותו האובייקט בהרצה אחרת (נגיד ע"י Thread מספר 2), מהסיבה הזו, ה-Stateless Objects או ה-Stateless Classes הם גם Thread Safe (מוגנים)
להלן דוגמא:
public class Wrappers { public void click(WebElement elem) { elem.click(); } public void update(WebElement elem, String value) { elem.sendKeys(value); } }
אנו רואים כאן את מחלקת ה-Wrappers שבסופו של דבר עוטפת כל מיני פעולות בסיסיות בסלניום (למה שנעשה את זה ? זה כבר לפוסט אחר…) ולמחלקה זו ישנן 2 מתודות, פעולת ה-click שמקבלת אלמנט ולוחצת עליו ופעולת ה-update שמקבלת אלמנט ומזינה לתוכו טקסט, מתודות אלו אינן משתמשות במאפייני המחלקה מכיוון שאין כאלו במחלקה, את המידע על מי ללחוץ או איזה טקסט להזין לאלמנט היא מקבלת ישירות מבחוץ כפרמטרים. כעת נרצה ליצור אובייקטים מהמחלקה הזו ולהשתמש בהם בבדיקות שלנו למשל כך:
class Example { Wrappers wrap1 = new Wrappers(); @Test public void example1() { WebElement test = driver.findElement(By.id("kuku")); wrap1.click(test); wrap1.update(test, "Hello"); } }
האובייקט כאן: wrap1 הוא Stateless – חסר מצב, גם אם נריץ במקביל את המחלקה הזו האובייקט לא יכיל מידע שיכול להרוס לנו את הריצה, במילים אחרות זהו אובייקט מוגן.
אגב, זוהי גם דוגמא למחלקה שהיא Stateless:
public class Wrappers { final private static String value = "Hello"; public void click(WebElement elem) { elem.click(); } public void update(WebElement elem) { elem.sendKeys(value); } }
במקרה הזה עדיין לא יתבצע כל שינוי ב-fields של המחלקה מכיוון שהמאפיין היחיד שקיים שם הוא "סגור" (final) ואיננו ניתן לעידכון וכמו כן הוא מסוג static שכן זהו מאפיין של המחלקה ולא של אובייקט (לא ניתן "לשכפל" אותו לאובייקטים אחרים)
ולמען ההבנה המלאה, בואו נראה דוגמא ליצירת אובייקט שהוא לא במצב Stateless:
public class Wrappers { public static String value = "Hello"; public void update(WebElement elem) { elem.sendKeys(value); } } class Example { Wrappers wrap1 = new Wrappers(); @Test public void example1() { WebElement test = driver.findElement(By.id("kuku")); wrap1.update(test); Wrappers.value = "kuku"; wrap1.update(test); } }
אז מה יש לנו כאן ?
את אותה מחלקה Wrappers עם מאפיין String שהוא public – זאת אומר נגיש לכל וניתן לשינוי, מאפיין זה מסיר את ההגנה על האובייקט (במצבים מסויימים זה בדיוק מה שנרצה לעשות, במצבים אחרים – לא). אנו רואים כי במחלקת ה-Example אנו באמת משנים אח"כ את הערך של ה-String מ-Hello ל-kuku ושולחים למתודת ה-update
Immutable Objects:
המילה Immutable בתרגום לעברית היא: בלתי ניתן לשינוי, או בתרגום שלי: לקריאה בלבד (Read Only), כך שהמושג Immutable Objects מתייחס לאובייקטים שאינם ניתנים לשינוי. נשמע מאוד דומה להתנהגות ה-Stateless Objects נכון ? אז התשובה היא – כן דומים במטרה, ולא – שונים במימוש.
בזמן שה-Stateless Objects אינם מכילים מאפיינים, או שהם מכילים מאפיינים מסוג static final , ה-Immutable Objects יכולים להכיל מאפיינים המגדירים את מצב האובייקט, אך הם מובנים באיתחול שלהם בתוך הבנאי (Constructor) וברגע שמייצרים את האובייקט מהמחלקה המצב של המאפיינים נקבע, הם מאותחלים ולא ניתן לשנות אותם אח"כ.
להלן דוגמא ליצירת אובייקט מסוג Immutable:
public class Wrappers { private final WebElement elem; private final String value; public Wrappers(WebElement elem, String value) { this.elem = elem; this.value = value; } public void click() { elem.click(); } public void update() { elem.sendKeys(value); } }
כך אנו נקרא לה ממחלקת הטסטים שלנו למשל:
class Example { @Test public void example1() { WebElement test = driver.findElement(By.id("kuku")); Wrappers wrap1 = new Wrappers(test, "Hello"); wrap1.click(); wrap1.update(); } }
אז מה יש לנו כאן ?
מחלקת ה-Wrappers מכילה מאפיינים מסוג private ו-final וכן בנאי המאתחל אותם, שימו לב כי זהו המקום היחיד בו אנו יכולים לאתחל את מאפייני המחלקה, לאחר מכן לא נוכל לעדכן אותם שוב (אין פה הגדרה של Setters), במחלקת ה-Example אנו ניצור אובייקט ממחלקת ה-"קריאה בלבד" ונעבוד מולו.
- הערה, בוודאי שמתם לב כי הדרך שהחלטתי להציג כאן את הנתונים היא די מוזרה, למה ליצור אובייקט של Wrappers (שאמור להיות גנרי) ולנעול אותו לערכים מסויימים של מאפינים. ובכן – אתם צודקים, בדוגמא הזו אין הרבה היגיון לעשות זאת, וזוהי כמובן רק דוגמא אחרת מעולם האוטומציה (נמאס כבר ראות דוגמאות עם Dog / Cat / Student / וכו'… :-))
את החלק השני של הנושא "להגן על האובייקט" אכתוב כבר בפוסט נפרד ובו ארחיב על הרצה במקביל תוך התייחסות להגנה על אובייקטים עם Thread Safe בפריימוורקים של בדיקות. איזה כיף … 🙂