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

בכן, לא עוד! בפוסט הזה אנו נלמד כיצד אנו יכולים להגדיר Custom Annotations משל עצמנו, עם הלוגיקה והפונקציונליות שאנחנו נקבע לקוד שלנו, מוכנים ? אנחנו מתחילים.

 

אך קודם כל לפני שנראה את ה"איך", בואו קודם נדבר על ה-"מה" , מה זה בכלל אנוטציה (Annotation) ולמה היא עוזרת לנו.

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

מה זה בכלל אנוטציה ? זהו מנגנון בשפת ג'אווה להוספת מטה-דאטה (לא, לא המטה של פייסבוק), שזה אומר – מידע שנלווה לקוד שלנו ומגדיר אותו בצורה סדורה, במילים אחרות המטה-דאטה הינו "מידע אודות המידע" (תיאור התכונות של הנתונים), באמצעות המטא דאטה ניתן לאתר זהות מופעים של נתונים המאוחסנים במקומות שונים. אוקיי, אז אם נרצה להגדיר בצורה מופשטת התנהגות מסויימת, נוכל להגדיר איזושהי יישות תכנותית (למשל מתודה) על פי האנוטציה שלה.

האנוטציות הן הדרך החדשה יותר (בעבר השתמשו בעיקר ב-xml) להגדרת התנהגויות בקוד שלנו.

כל מי שכותב אוטומציה בג'אווה משתמש באנוטציות. בשפות אחרות המטה-דאטה הללו נקראים בצורה קצת שונה, כמו Attributes או Fixtures אבל הרעיון הינו אותו רעיון.

מי לא מכיר למשל את ה-Test@ ? הרי זוהי אנוטציה אשר באה להגדיר לנו כי המתודה שממוקמת מתחת לאנוטציה הינה מתודה ברת הרצה ע"י מנוע ההרצה של אותו פריימוורק של הבדיקות (למשל TestNG) , כך גם ה-BeforeClass@ נותנת משמעות ומגדירה התנהגות של אותה מתודה שתרוץ פעם אחת בלבד לפני כל ה-Tests באותה מחלקה, יש לנו אנוטציות נוספות בעולם הזה כמו ה-FindBy של Page Objects וה-Data Provider לעבודה עם Data Driven Testing , יש את ה-Step של ה-Allure ועוד ועוד.

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

הדוגמא שאני הולך להראות לכם כעת היא בג'אווה, אבל כאמור גם בשפות אחרות ניתן לממש אותם (רק ששם הן לא ייקראו אנוטציות, אבל למה לנו להיות קטנוניים :-))

בדוגמא הזו אנחנו הולכים להגדיר אנוטציה שתיקרא: ExecutionData והיא תגדיר לנו כמה שדות כמו: שם המפתח שכתב את הבדיקה, סוג הבדיקה (למשל Sanity או Regression) ומאיזו חברה הוא מגיע (אפשר גם לשנות את זה לשם מחלקה – department בה הוא עובד אם תרצו).

נגדיר אנוטציה זו שתפעל על מתודות ה-Test של TestNG

להלן דוגמא להרצת אנוטציה כזו:

@ExecutionData(name = Testers.SAED, category = Category.REGRESSION, company = "From ATID College")
@Test
public void test01() {
    System.out.println("Executing Test 01");
}

שימו לב כי בקוד הנ"ל הגדרתי את האנוטציה שלנו (ExecutionData) מעל מקרה הבדיקה ומי שמריץ את הבדיקה הוא SAED , הבדיקה תהיה מסוג Regression והיא שייכת ל ATID College , כדי להגיע לכך, עלינו לבצע כמה פעולות מקדימות בפרוייקט שלנו:

 

השלב הראשון:

בשלב הראשון אנחנו הולכים להצהיר על האנוטציה החדשה שלנו על ידי שימוש ב-Interface או יותר נכון: Interface@

public @interface ExecutionData {
    
}

אל ה-Interface הזה אנחנו נשתמש (שוב) באנוטציות מובנות כדי להגדיר אותו (כן, מצחיק, מגדירים אנוטציה בעזרת אנוטציה אחרת…) והן:

ה-RetentionPolicy – שדה זה יכול להכיל 3 ערכים: CLASS, RUNTIME, SOURCE, במקרה שלנו אנו נבחר ב-RUNTIME שכן אנו מגדירים שהאנוטציות יופעלו בקובץ המחלקה ע"י הקומפיילר ויהיו שמורים ב-JVM (ה-Class לא יהיה שמור ב-JVM ואילו ה-Source לא יעובד ע"י הקומפיילר כלל) , שזה אמור במילים אחרות, בבואנו לבחור את האופציה של RUNTIME אנחנו למעשה מגדירים שיישויות קוד אחרות יוכלו להכיר את האנוטציה שלנו בזמן ריצה

ה-Target – שדה זה יכיל את ה scope על מי אנחנו מעוניינים להכיל את ההתנהגות של האנוטציה: FIELD, METHOD, PACKAGE, PARAMETER, TYPE, CONSTRUCTOR ועוד. בדוגמא שלנו אנו נבחר את ה-METHOD מכיוון שאנו רוצים להפעיל את האנוטציה על מתודת ה-Test

 

השלב השני:

הגדרת המאפיינים של האנוטציה. ובכן, החלטנו להגדיר לאנוטציה את המאפיינים הבאים: name, category ו-company , זה אומר שמבחינתינו אנו צריכים להגדיר אותם ב-Interface שיצרנו, אבל רגע, כאן מגיע החלק המעניין, כשאנו עושים זאת אנחנו למעשה מגדירים את המאפיינים (תכונות) כמתודות (מן הסתם אבסטרקטיות כי מדובר ב-Interface אחרי הכל), כך:

public Testers name();
public Category category();
public String company();

למעשה מלבד ה-String company אין לנו עדיין מושג מהם ה-Data Types האחרים כי עדיין לא יצרנו אותם בדוגמא. אז רגע לפני שניצור אותם, בואו נראה את הגדרת האנוטציה במלואה:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ExecutionData {
    public Testers name();
    public Category category();
    public String company();
}

 

השלב השלישי:

בשלב זה נגדיר את הטיפוסים השונים עימם נרצה לעבוד, יש לנו שניים כאלו, ה- Testers ששם נגדיר את שמות הבודקים ואת ה-Category ששם נגדיר את סוגי הבדיקה. אפשר להגדיר אותם כאובייקטים רגילים, אבל נראה לי שבמקרה זה יהיה הכי חכם להשתמש כאן ב-Enums , כך למשל יראה ה-Enum של Testers:

public enum Testers {
    SAED,
    YONI,
    MOSHE,
    DAVID,
    CHAIM
}

 

וכך יראה ה-Enum של Category:

public enum Category {
    SANITY,
    REGRESSION,
    HEALTHCHECK
}

 

השלב הרביעי:

בשלב זה נרצה להגדיר את הלוגיקה, כך שלמשל אחרי שהכנסנו את הנתונים של ה-name, category ו-company נרצה לעשות משהו עם המידע הזה, נגיד שנרצה לתעד את המידע הזה בדוח ההרצה שנוצר לנו לאחר כל סבב ריצות, אז לצורך הדוגמא הזו כאן בפוסט אינני הולך לממש כעת איזשהו report מתוחכם, אלא סתם להשתמש בפעולת ההדפסה לקונסול עם System.out.println (אתם יכולים לקחת את זה מכאן ולהמשיך הלאה לדיווח לכל מערכת שתרצו לממש)

במקום להגדיר את פעולות ההדפסה בטסטים עצמם, החלטתי לכתוב בצורה אינטיליגנטית יותר בתוך ה-Event Listener (השתמשתי ב-Event Listener של ה-TestNG אבל אפשר גם לעבוד עם זה של ה-WebDriver)

import org.testng.ITestListener;
import org.testng.ITestResult;
public class AutomationListeners implements ITestListener {

    public void onTestStart(ITestResult result) {
        System.out.println("Tester Name: " + result.getMethod().getConstructorOrMethod().getMethod().getAnnotation(ExecutionData.class).name());
        System.out.println("Test Category: " + result.getMethod().getConstructorOrMethod().getMethod().getAnnotation(ExecutionData.class).category());
    }

    public void onTestSuccess(ITestResult result) {
        System.out.println("Test Summery: " + result.getMethod().getConstructorOrMethod().getMethod().getAnnotation(ExecutionData.class).company());
    }
}

מה אנחנו רואים כאן ?

שימוש ב-OnTestStart שזה אומר בכל פעם שתתחיל להריץ את ה Test שלך (כל Test) תופעל המתודה הזו, ובפנים השתמשתי באובייקט ה-result שנותן לי מידע (reflected) על הבדיקה המורצת כעת, תוך שימוש ב-getAnnotation אני יכול להגיע לאותו ממשק שכתבנו עוד בשלב הראשון ובתוכו להביא את השם של הבודק שמריץ את הטסט, בדיוק באותו אופן אני מביא גם את הערך category שהגדרנו בטסט

תחת OnTestSuccess החלטתי להדפיס את שם החברה של הבודק – אותו שם שהוא הכניס באנוטציה של הטסט, הכל דרך ה-getAnnotation תוך קריאה להגדרת האנוטציה שלנו ExecutionData

 

השלב החמישי:

כתיבת מקרה הבדיקה וקריאה לאנוטציה המקוסטמת שלנו ExecutionData:

import org.testng.annotations.Listeners;
import org.testng.annotations.Test;

@Listeners(AutomationListeners.class)
public class TestCases {

@ExecutionData(name = Testers.SAED, category = Category.REGRESSION, company = "From ATID College")
@Test
public void test01() {
    System.out.println("Executing Test 01");
}

    @ExecutionData(name = Testers.YONI, category = Category.SANITY, company = "From ATID College")
    @Test
    public void test02() {
        System.out.println("Executing Test 02");
    }
}

נריץ את שני מקרי הבדיקה וכאשר נפתח את ה-Console נראה את התוצאה הבאה:

 

אהבתם את הפוסט ? מוזמנים לשתף בקבוצות הלינקדאין והפייסבוק

בהצלחה,

סאיד

 

השאר הערה\הודעה