חברים, יש לכם כבר פרוייקט אוטומציה כתוב ? יפה מאוד, כמה אתם מרוצים מאיך שהוא כתוב ? איך בכלל אתם יודעים שהוא כתוב נכון ?
לפרוייקט אוטומציה, כמו לכל פרוייקט תוכנה, אנו בדרך כלל עובדים לפי עקרונות והנחות, שיטות עבודה, תבניות עיצוב (Design Patterns), ו-Best Practice אותם למדנו מנסיון העבר או מייעוץ חיצוני.
בפוסט הנוכחי אני הולך לדבר על קבוצת עקרונות של כתיבת קוד הנקראת SOLID ואיך אנו יכולים להשתמש בהן בפרוייקט האוטומציה שלנו.
קודם כל, בואו נבין מה זה בכלל SOLID ?
לפני שנגיע לראשי תיבות שהמושג הזה מייצג, נבין כי שיטת העבודה הזו תאפשר לנו לבנות פרוייקט תוכנה מסוג loosely-coupled.
אז רגע, בואו נבין קודם מה זה בכלל loosely-coupled לפני שנבין מהו ה-SOLID.
על פי ההגדרה הרישמית של loosely-coupled מתוך וויקיפדיה, מדובר של שיטה בה אנו מפרידים ומבודדים קומפוננטות כך שלכל קומפוננטה יהיה תפקיד אחד מוגדר, תוך שמירה על הפרדה.
הצד השני של השיטה נקרא tight-coupling , וכאן מדובר על כתיבה של מחלקות אשר בהכרח תלויות אחת בשנייה.
עדיין לא מספיק מוחשי ? נראה לי שהצורה הטובה ביותר להסביר מה זה loosely-coupled ו-tight-coupling תבוא על ידי דוגמא שלא מעולם התוכנה.
בואו נביט על מכשירי סמארטפון, יש לנו את המוצרים של Apple, אם לפני שנתיים קנינו אייפון 7 למשל, וכיום הסוללה שלו מאוד חלשה, לא נוכל לבוא ולהחליף את הסוללה שלו בלבד, אלא נצטרך כבר להחליף את הטלפון כולו. הסוללה של אייפון מגיעה כחלק Built-in בתוך המכשיר ואין אפשרות להוציאה, זה מה שנקרא – tight-coupling (תלות בין הקומפוננטות של המכשיר)
ניקח למשל טלפון אחר – שיאומי, לטלפון זה ניתן להסיר ללא כל בעיה את הסוללה, כך שאם אחת שובקת חיים, נוכל לרכוש אחרת ולהחליף רק אותה, זוהי דוגמא ל-loosely-coupled (הפרדה בין קומפוננטות של המכשיר)
אוקיי אז כפי שאמרנו כבר, עקרונות ה-SOLID דוגלות בשיטת ה-loosely-coupled (כמו הטלפון של שיאומי בו ניתן להפריד את הסוללה).
ה-SOLID הן ראשי תיבות של:
Single Responsibility Principle (SRP)
Open Closed Principle (OCP)
Liskov Substitution Principle (LSP)
Interface Segregation Principle (ISP)
Dependency Inversion Principle (DIP)
מפ"מ = מחלקות, פונקציות, מודולים.
עיקרון ה-Single Responsibility Principle מדבר על תחום האחריות של המפ"מ , לכל מפ"מ יש אחריות אחת בלבד
עיקרון ה-Open Closed Principle מדבר על כך שהמפ"מ יהיה פתוח להרחבות (Extensions) אך סגור לשינויים (Modifications)
עיקרון ה-Liskov Substitution Principle (על שם ברברה ליסקוב שזכתה בפרס טיורינג הנחשב ב-2008) מדבר על כך שאנו יכולים להחליף אובייקטים של מחלקות שונות אשר יש ביניהם קשר של ירושה, ניתן להחליף אובייקט של מחלקת אבא באובייקט של מחלקת בן וזה מבלי לשנות את ההתנהגות של האב.
עיקרון ה-Interface Segregation Principle מדבר על אופן עבודה בו אנו נרצה להפריד בין ממשקים שונים בכדיי למנוע מימוש של תהליכם לא נחוצים, במילים אחרות – לא נתקמצן ביצירת ממשקים (כשזה נחוץ כמובן)
עיקרון ה-Dependency Inversion Principle מדבר על ההפרדה בין הקומפוננטות למען תחזוקה טובה יותר של הקוד וזה מתבצע על ידי אבסטרקציה תוך שימוש ב-Intefraces במקום מחלקות
לקריאה נוספת על עקרונות ה-SOLID (מעבר ללינקים של וויקיפידה שכבר מופיעים), תוכלו להינכס לבלוג של תומר כהן בו הוא מסביר על העקרונות עם דוגמאות, וכמו כן אני מאוד ממליץ על הבלוג של טים קורי עם סרטוני דוגמא לכל אחד מעקרונות ה-SOLID
אז איך ניתן להכניס את עקרונות ה-SOLID בפרוייקט האוטומציה שלנו ? בואו נראה דוגמא.
יש לנו פרוייקט שתומך באפליקציית Web (עם סלניום) ואפליקציית Mobile (עם אפיום), מה הדבר הפשוט שנעשה ? נבדוק אם אנחנו מריצים טסטים על Web אז נאתחל את הדרייבר שלנו לסלניום ואם אנחנו עובדים על Mobile נאתחל את הדרייבר שלנו לאפיום. וכשאנחנו מאתחלים לסלניום, אנחנו למעשה צריכים שוב לבוא ולבדוק על איזה דפדפן אנחנו רוצים לעבוד (זאת אומרת שגם כאן יהיו לנו שירשורים של if/else if או switch / case)
כך הקוד שלנו אמור להיראות:
if (getData("AutomationType").equalsIgnoreCase("Web")) initBrowser(getData("BrowserType")); else if (getData("AutomationType").equalsIgnoreCase("mobile")) initMobile();
פונקציית ה-getData לוקחת את המידע (ההגדרה על מה אנחנו רוצים לעבוד) מתוך קובץ חיצוני שיכול להיות בפורמט: properties / xml / json / txt / ini וכו'…
ובמידה וה-AutomationType = web אנחנו נפנה לפונקציית ה-initBrowser עם פרמטר של BrowserType שיכיל בתוכו את שם הדפדפן עימו נרצה לעבוד, זה יראה כך:
switch (browserType.toLowerCase()) { case "firfox": driver = initFFDriver(); break; case "ie": driver = initIEDriver(); break; case "chrome": driver = initChromeDriver(); break; }
כאשר מתודת ה-initFFDriver תאתחל את הדרייבר לפיירפוקס , ה-initIEDriver ל-אינטרנט אקספלורר וכו'…
טוב, אז זה בדוגמת הקוד הזו הכל סבבה וזה יעבוד, אך כאן אין לנו שימוש בעקרונות ה-SOLID. בשביל להפוך את הקוד הזה למה שאנו למדנו עד כה בפוסט נצטרך לבצע כמה שינויים, כאשר בראש ובראשונה להתחיל ליצור ממשקים ומחלקות מממשות.
נגדיר 2 ממשקים: client ו-browser המחלקות שיממשו את ה-client יהיו mobile ו-web , כאשר המשמעות היא שבעתיד נוכל ליצור מחלקות נוספות כמו api , desktop וכו'.
מחלקת ה-web תהיה מחלקה אבסטרקטית שתממש את ה-client ותגדיר פונקציה אבסטרקטית בה נצהיר על איתחול הדפדפן (initBrowser)
זהו ממשק ה-browser
package Interfaces; import org.openqa.selenium.WebDriver; public interface Browser { public WebDriver initBrowser(); }
וזהו ממשק ה-client
package Interfaces; public interface Client { public void initClient(); }
נגדיר את מחלקת ה-mobile שלנו שכאמור מממשת את ה-client:
package Classes; import Interfaces.client; public class Mobile implements Client { public void initClient() { // TODO Implement this } }
ואת מחלקת ה-web :
package Classes; import org.openqa.selenium.WebDriver; import Interfaces.*; public abstract class Web implements Client, Browser { public void initClient() { initBrowser(); } abstract public WebDriver initBrowser(); }
זה אומר שבעתיד כשנרצה לתמוך בקליינטים נוספים, לא ניגע בפונקציונליות הקיימת, אלא פשוט נוסיף מחלקה חדשה שהאחריות שלה היא לאתחל את אותו קליינט דרך הדרייבר שלו, בדיוק כמו ש-SOLID מתכוון, לדוגמא, אם נרצה מחר לתמוך גם באוטומציה לאפליקציות דסקטופ, ניצור מחלקה חדשה כזו:
package Classes; import Interfaces.client; public class Desktop implements Client { public void initDesktop() { // TODO Implement this } }
נמשיך הלאה, מחלקת chrome תירש מ-web:
package Classes; import org.openqa.selenium.WebDriver; import org.openqa.selenium.chrome.ChromeDriver; import Infra.getData; public class Chrome extends Web { public WebDriver initBrowser() { System.setProperty("webdriver.chrome.driver", getData.go("ChromeDriverPath")); return new ChromeDriver(); } }
ומחלקת firefox תירש גם היא מ-web:
package Classes; import org.openqa.selenium.WebDriver; import org.openqa.selenium.firefox.FirefoxDriver; import Infra.getData; public class Firefox extends Web { public WebDriver initBrowser() { System.setProperty("webdriver.chrome.driver", getData.go("FFDriverPath")); return new FirefoxDriver(); } }
זה אומר שבעתיד כשנרצה לתמוך בדפדפנים נוספים, לא ניגע בפונקציונליות הקיימת, אלא פשוט נוסיף מחלקה חדשה שהאחריות שלה היא לאתחל את אותו דפדפן דרך הדרייבר שלו, בדיוק כמו ש-SOLID מתכוון, לדוגמא, הוספת תמיכה בדפדפן ה-IE:
package Classes; import org.openqa.selenium.WebDriver; import org.openqa.selenium.internetexplorer.InternetExplorerDriver; import Infra.getData; public class Ie extends Web { public WebDriver initBrowser() { System.setProperty("webdriver.ie.driver", getData.go("IEDriverPath")); return new InternetExplorerDriver(); } }
אז ראינו כאן דוגמא "אמיתית" לפרוייקט אוטומציה שסביר כי רובכם כבר כתבתם (מי שכתב) שבא להחליף את ה switch / case ולעבוד עם שיטת עבודה מאוד פופולרית בתחום כתיבת התוכנה.
כמובן שניתן עוד להרחיב ולשפר את הקוד הקיים, לא התייחסתי בדוגמא ל-page objects , למחלקות הטסטים שלי ועוד…