תבניות עיצוב ואוטומציה | Page Object Pattern




בתור מפתחי אוטומציה (ותוכנה בכלל) אנחנו נתקלים בלא מעט בעיות..

אנחנו לא זוכרים syntax מסוים, נזרק לנו exception שאנחנו רואים פעם ראשונה או אפילו שגיאת קומפילציה שאנחנו לא מכירים.

מה אנחנו עושים כדבר כזה קורה לנו? לרוב נפנה לדר' גוגל... הרי כל דבר שקרה לנו כבר קרה למישהו אחר בעבר, ובכל בעיה בה נתקלנו, אנחנו כנראה לא הראשונים שנתקלנו בה.
ב-95% מהמקרים גוגל אכן פותר לנו את הבעיה.

מה קורה כאשר אנחנו נתקלים בבעיית Design בקוד שלנו?
אנחנו לא בטוחים כיצד לחבר את ה class-ים או שאנחנו יודעים שמה שעשינו "לא כל כך יפה" אבל אנחנו לא בטוחים כיצד לעשות זאת נכון.

לשם כך נוצרו Design Patterns!
Desing Pattens הן הפתרונות לבעיות שלנו בעיצוב התוכנה - אלו מעין תבניות פתורות וכל מה שנשאר למשתמש זה להתאים אותן למקרה שלו.

במילים אחרות, Design Patterns זה פשוט פתרון לבעיה בעיצוב הקוד.

לאחר שהתקדמנו ולמדנו לא מעט בכל הקשור לעיצוב תוכנה מונחית עצמים, וכתיבת תרחישי אוטומציה ב Selenium ו - Appium, הגיע הזמן להמשיך ולהתקדם לנושאים הבאים.

בפוסט של היום -  Page Objects

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

מה זה Page Objects?

Page Object Pattern הינה תבנית העיצוב הנפוצה ביותר בעולם האוטומציה אשר מטרתה להחצין ממשק של האובייקטים במסך בצורה כמה שיותר "High Level".

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

למה להשתמש בזה בכלל?

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

אז כן, טכנית זה אפשרי. אבל זה דבר שממש לא יהיה נכון לעשות.

3 בעיות עיקריות בכתיבת הגישה לאלמנטים hard coded בתוך התרחיש:

  1. בעיה בתחזוקת הקוד - לצורך העניין, במידה ומצאנו את האלמנט בתרחיש הבדיקה שלנו באופן הבא:
    IWebElement element = driver.FindElementById("bt_02");
    אם נרצה לשנות אותו ביום מן הימים נצטרך לגשת לכל מקום ולכל תרחיש בדיקה בו השורה נמצאה ולשנות אותה, דבר שיכול להקשות מאוד על אופן הפיתוח בהתחשב בעובדה שעמודי אינטרנט הם לרוב דינאמיים ויכולים להיות
    בהם שינויים
  2. בעיה של שכפול קוד - כאשר נכתוב את מציאת האלמנטים בדרך שהוצגה לעיל, לא נוכל לבצע שימוש חוזר בקוד, נצטרך למצוא אותו בשאר התרחישים גם כן - דבר שיוביל לשכפול של קוד
  3. בעיה של קריאות הקוד - אמנם מציאה של אובייקט באמצעות המזהה "bt_02", יחסית קריאה וניתן להבנה, אך כאשר יוצגו בקוד שלנו אופנים אחרים למציאת אלמנטים (כמו xpath מורכב וכדומה) יהיה קשה להבין כיצד האלמנט נמצא או אפילו באיזה אלמנט מדובר.

אז מה עושים?

במקרה כזה נרצה להשתמש ב Page Object.

אז איך עושים זאת בפועל?
נסתכל על האפליקציה אותה אנו רוצים לבדוק, וננסה להפריד את האפליקציה למסכים/אזורים בעלי אותו נושא.
בעבור כל מסך או אזור ניצור class. המחלקה תיקרא על שם האזור ותכיל את פונקציונליות הרכיבים בו.

במילים אחרות, מחלקת הPage Object שלנו, תכיל פעולות שינגישו לנו את ה-WebElements עבור כל הרכיבים במסך.
על האלמנטים הללו נוכל לבצע פעולות כמו לבצע לחיצה, לקרוא טקסט מן האלמנט וכו'.

מחלקת ה - PageObject תכיל גם public methods שישתמשו באלמנטים שאיתרנו.

*הערה: PageObject לא חייב להיות שייך לעמוד שלם הוא יכול להיות גם עבור מודולים ספציפיים בתוך העמוד.

דוגמה ל Page Objects באמצעות Appium ו - C#

בדוגמה הקודמת הוצגה דוגמה בסיסית של ביצוע אוטומציה למחשבון באמצעות Appium, בבדיקה ביצענו את תרחיש הבדיקה הבא: ביצוע פעולת החשבון 2+2 ולאחר מכן השוואה ווידוא שהתוצאה יצאה כמו שרצינו.

תרחיש הבדיקה עבד מעולה אך בתרחיש לא השתמשנו בPage Object אלא כתבנו את אופן מציאת האלמנטים Hard Coded.

הפעם נבצע את אותו התרחיש רק באמצעות Page Objects.

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

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

חשוב לזכור - ה-TestCase לא אמור להכיר את הממשק של סלניום, אנחנו אמורים לתת לו PageObjects עליהם יעבוד

המחלקה CalculatorPage תראה כך:


והשימוש בPage Object בתרחיש הבדיקה יראה כך:


כמובן לאחר שהגדרנו את ה-driver ואת CalculatorPage ברמת המחלקה.

ניתן לראות שאנחנו נדרשים להעביר לפעולות של CalculatorPage את driver בכל פעם מחדש, דבר שהופך את הקוד למעט מסורבל ופחות נקי.

אם ננסה לvבין את הדברים לעומק נוכל לראות חזרתיות מסוימת - כל פעולה מחזירה את אותו האובייקט (IWebElement) וכל הפעולות מקבלות driver, אז למה לא לבצע את יצירת האלמנטים באופן גנרי?

בדיוק עבור הצורך נוצר ה-PageFactory.

מה זה PageFactory ואיך משתמשים בו?

אז העיקרון דומה מאוד, רק שהפעם ב - CalculatorPage במקום ליצור פונקציות שמקבלות driver ומחזירות IWebElement, ניצור Properties עם שם האלמנט אותו נרצה למצוא ומעל כל Property נכתוב תכונה (attribute) עם הדרך בה אנחנו מוצאים את האובייקט. מגניב נכון?

עכשיו המחלקה תראה כך:




בשביל שתרחיש הבדיקה שלנו יכיר את האלמנטים עכשיו, נצטרך לבצע עם אתחול הdriver גם אתחול לPageObject באמצעות השורה -
PageFactory.InitElements(driver, calculatorPage);

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

וכעת אופן השימוש בתרחיש הבדיקה יראה כך:


שים לב: כעת אנחנו כבר לא קוראים לפונקציות אלא ל Properties ולכן לא נצטרך יותר את ה - "()" לאחר שם האלמנט.

Page Objects בתצורה הנכונה


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

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

ועקרון האנקפסולציה אומר, לא לחשוף למשתמש את מה שאינו מעניין אותו וגם לא את מה שיכול לפגוע בתכנית שלנו.

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

ודבר נוסף הוא זה שכשאנחנו נותנים WebElement המשתמש יכול לבצע פעולות שהמערכת שאנחנו בודקים לא יודעת לבצע.

לדוגמה, המשתמש יוכל לשלול SendKeys לתיבת התוצאה של המחשבון על אף שהיא ReadOnly.

בעייתי נכון?

אז מה עושים?

נהפוך את האלמנטים של ה-Page Object שלנו ל private ונחצין פעולות בהן המשתמש ייעזר בשביל לבצע את מה שהוא רוצה

void ClickButton_02();
void ClickButton_Add();
void ClickButton_Equal();
string GetResult();

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

המחלקה תראה כך:



וה-Test יראה כך:


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

כמובן שניתן להוסיף לפעולות עוד לוגיקה כמו כתיבה ללוג אך זהו השימוש הפשוט ב PageObjects

סיכום

היום למדנו מהם PageObjects, כיצד להשתמש בהם, וראינו את האבולוציה מהשימוש בPageObject באמצעות פונקציות ועד השימוש ה PageFactory.

בנוסף הבנו שה-PageObject יהיה חייב ליצור שכבת אבסטרקציה מעל מציאת האלמנטים והקריאה לקוד הסלניום ולהכתב בשפה אנושית ככל שאפשר

PageObjects הוא ה Design Pattern הנפוץ והחשוב ביותר בעולם האוטומציה וחשוב מאוד ליישם אותו בכל מקום בו ניתן.



נתראה בפוסט הבא :)

תגובות

פוסטים פופולריים מהבלוג הזה

תכנות מונחה עצמים | Dependency Inversion Principle

מהם קבצי DLL ואיך להשתמש בהם?

מה ההבדל בין אוטומציה לפיתוח רגיל