‏הצגת רשומות עם תוויות בסיסי. הצג את כל הרשומות
‏הצגת רשומות עם תוויות בסיסי. הצג את כל הרשומות

יום חמישי, 17 במאי 2007

נקודות יציאה מרובות


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

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

למזלנו, האנשים הנחמדים ב-Mercury (והיום HP) הבינו את הצורך ביציאה מיידית מ-Action, והכינו עבורנו את פקודות ExitAction ו-ExitActionIteration. לרוב השימוש בהן נראה כך:


'….Action Code
If CritialCondition = False Then ExitAction
'….Continue Action



אבל זאת דרך מאוד לא ברורה לצאת באמצע ביצוע משימה. רצוי שנוסיף כתיבה ללוג:


If CritialCondition = False Then
Reporter.ReportEvent MicFail, "Something bad has happened", "Aborting"
ExitAction
End If



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


If CritialCondition = False Then
   Reporter.ReportEvent MicFail, "Something bad has happened", "Aborting"
   oFile.Close
   Set oFile = Nothing
   ExitAction
End If



ורגע, יש מידע שצריך לשתול ב-DataTable:


If CritialCondition = False Then
  
Reporter.ReportEvent MicFail, "Something bad has happened",
"Aborting"
  
oFile.Close
   Set oFile = Nothing
   DataTable("out_EntityID", dtlocalsheet) = sEntityID
  
'More here
  
ExitAction
End If




אני מניח שתפסתם את הרעיון. במקום פקודה פשוטה, יש לנו קטע קוד מורכב למדי, שמופיע שוב ושוב בכל נקודות היציאה. המשמעות היא שיש לנו קוד כפול שנצטרך לתחזק באופן שוטף. קוד כפול הוא התגלמות זרע השטן בעולם הדיגיטאלי, ואתם ממש לא רוצים משהו כזה בקוד שלכם. באופן רציני יותר, כפילויות קוד כאלו הן מתכון בטוח לבאגים קשים לאיתור, עבודה מיותרת ותסכול, וצריך להמנע מהן בכל מחיר. אחת הדרכים להמנע מכפילויות הקוד היא לארוז אותו במרוכז בפונקציית יציאה. זוהי הפונקציה היחידה ש"מותר לה" (לפחות כפי שאני כותב קוד) להיות בתוך Action, ולא בקובץ חיצוני. להלן דוגמה לשימוש בפונקציה, כמודגם ב-Action Template שלי:


Dim sResult 'I store result data and values to be reported up the action-call chain
Dim sActionReport
'Instead of flooding the log with inner-action messages, I store them, and report all of them at the exit gate.
'Errors are still reported on-the-fly

'Action code goes here

'All the exit gates execute only one command : ActionEnd.
'It receives two parameters: Boolean for Pass/Fail, string for the exit reason
If CriticalCondition = False Then Call ActionEnd(False, "Reason for exiting")

'Rest of action

'Even the normal successful action exit is managed through ActionEnd, so the last line in every action is:

Call ActionEnd(True, "Action successful")

Sub ActionEnd(bStatus, sReason)
  
'Report details
  
Reporter.ReportEvent MicGeneral, "Inner Action Logs", sActionReport

   If bStatus = True Then
      Reporter.ReportEvent MicFail, "An error has occurred", sReason
   Else
      Reporter.ReportEvent MicPass, "Action successful",
"See inner logs for details"
  
End if

   'Plant datatable info for action-call chain
   DataTable("out_Status", dtlocalsheet) = bStatus
   DataTable("out_Result", dtlocalsheet) = sResult
  
'More if needed

   'Close objects and set to nothing here

   'Other needed exit code

   ExitActionIteration
End Sub


כפי שראינו, שימוש בפונקציית יציאה מרכזית מאפשר לשמור על קוד נקי ופשוט, ולמנוע הצפות לוג. טיפלנו בנקודות יציאה מ-Action, אבל מה לגבי יציאה מפונקציה? כמובן שאי אפשר לעשות פונקציית יציאה לפונקציה, אבל ישנו פתרון אלטרנטיבי לבעיה. הפתרון פחות יפה וקשה יותר לתחזוקה מהפתרון ל-Action, ולכן אני ממליץ להשתמש בו רק בפונקציות מסובכות במיוחד, המכילות הרבה נקודות יציאה, וקוד יציאה "עבה". הפתרון מתבסס על פקודת Execute, ולכן אם היא לא מוכרת לכם, אני ממליץ לקרוא עליה בקבצי העזרה של ה-QTP. בשורה התחתונה, פקודת Execute מקבלת מחרוזת, ומבצעת את התוכן שלה כאילו שמדובר בפקודות VBScript. כך שהפקודה Execute "msgbox(2)" תקפיץ msgbox עם המספר 2. להלן דוגמה לשימוש בפתרון בפונקציה ComplexFunc:


Function ComplexFunc
   Dim sExitCode
   Dim sResult
   Dim oFile
'will be FSO textstream

  
'separate code lines by vbcrlf or ":"
  
sExitCode = "oFile.Close" &          vbcrlf & _
         "Set oFile = Nothing" & vbcrlf & _
         "ComplexFunc = sResult"
     
'More exit code

   'Function code goes here

   'Exit Gate
  
If CriticalCondition =
False Then
     
sResult = "False, No Connection"
      Execute sExitCode
      Exit Function
   End if

  
'More function code

   'Successful exit
  
Execute sExitCode
End Function

יום שני, 7 במאי 2007

יישור קו - תכנות תיאורי (DP)

To the English version of this post

לפני שנוכל לסקור נושאים מתקדמים, נבצע "יישור קו" לגבי מספר טכניקות בסיסיות ב-QTP (אם כי הן נחשבות למתקדמות בפני עצמן במקומות מסויימים).
אחת הטכניקות המהותיות לשימוש מתקדם ב-QTP נקראת Descriptive Programming, ואותה נסקור היום. כל ניסיון לתרגם את המונח Descriptive Programming לעברית נראה מטופש, ולכן נתייחס אליה פשוט כ-DP.

רקע והסבר כללי



כאשר אנו משתמשים ב-DP, אנו למעשה עוקפים את מגנון מאגר האובייקטים של ה-QTP (OR – Object Repository). למנגנון זה יתרונות רבים – כשמשתמשים בו קל מאוד לכתוב ולקרוא קוד, הוא מסודר, מאפשר עבודה שיתופית (באמצעות מאגרים משותפים), ועוד. אולם, על אף היתרונות, מדובר במנגנון מאוד לא גמיש, קשה לתחזוקה, חסר יכולת התמודדות עם מצבים מורכבים, ומצמצם מאוד את היכולת להשתמש בפונקציות חיצוניות. בהמשך נראה, כי לעיתים כדאי לוותר על היתרונות הגלומים במאגר האובייקטים, על מנת להנות מהגמישות והיכולות המתקדמות הגלומות ב-DP.

על מנת להבין איך DP עובד, כדאי קודם להבין אין מאגר האובייקטים הרגיל של QTP עובד, ובמיוחד חשוב להבין כי מאגר האובייקטים אינו מנגנון מורכב ועמוק, אלא רק דרך נוחה לשמור קבוצות של תכונות וערכים ביחד. למה הכוונה? כאשר אנו מקליטים אובייקט כלשהו למאגר האובייקטים, QTP אוסף תכונות מהאובייקט הפיסי, ומתעד אותן כזוגות של הגדרה וערך. לדוגמה – גובה האובייקט הוא 400, הכותרת היא "ישות חדשה", ושם התוכנה שלו הוא "NewEntityID". תכונות אלו מתודעות במעין מילון, בו תחת הערך של האובייקט הרלוונטי מופיעות התכונות : Height = 400, Tile="ישות חדשה", vbname = " NewEntityID". כאשר QTP מריץ תסריט שמתייחס לאובייקט מסויים, הוא לוקח את אוסף התכונות של האובייקט, משווה אותן לתכונות של האובייקטים "הפיסיים" שפתוחים במחשב, ומוצא (או שלא) את האובייקט שאוסף התכונות מתאר אותו – ורק אותו. And that's all there is to it.

אופן השימוש

ב-DP, אנו מפרטים "ידנית" את אוסף התכונות על פיו נרצה לזהות אובייקט מסויים, ובכך גורמים ל-QTP להשתמש בתיאור הידני במקום בתיאורים השמורים במאגר האובייקטים שלו. הדרך המהירה לעשות זאת היא תוך כדי הפקודה המשתמשת באובייקט :
אופן השימוש הוא Class(PROPERTIESSTRINGS).Command, כאשר PROPERTIESSTRINGS מפרטות את התכונות והערכים לזיהוי. לדוגמה :


VBWindow("property1:=value1", "property2:=value2").Click



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


VBWindow("property1:=value1").VBCheckBox("property2:=value2").Click



כמובן ש-VBWindow הוא רק דוגמה למימוש בסביבת VB. באופן דומה ניתן לעבוד עם SWFWindow, Browser, וכו'. לדוגמה – Browser("name:=Test").Close.

כפי שניתן לראות, ההבדל היחיד בין השימוש ב-DP לדרך הרגילה בה אנו עובדים עם מאגר האובייקטים, היא בתוכן המוכנס בין הסוגריים של VBWindow(). כאשר עובדים עם מאגר האובייקטים, אנו מכניסים את השם הלוגי של האובייקט; וכאשר עובדים עם DP, מכניסים מחרוזות המתארות את התכונה לזיהוי, ואת הערך הצפוי בה (מופרדים ע"י :=).

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



'----Create Object----'
Dim oDesc
Set oDesc = Description.Create

'----Set ID properties & values---'
oDesc("property1").Value = "value1"
oDesc("property2").Value = "value2"

'----Use and reuse the description object---'
VBWindow(oDesc).Type "Something"
'…
'…

VBWindow(oDesc).Close

'----Release description object---'
Set oDesc = Nothing


וניתן לשלב בין שתי השיטות


VBWindows(oDesc).VBCheckBox("vbname:=DPIsCool").Set "ON"


שימושים נפוצים

(דוגמאות מפורטות ניתן למצוא בקובץ המצורף לפוסט)

עד כאן הכל טוב ויפה (ואפילו מגניב), אבל למה לעזאזל צריך את זה? הרי הפונקציונאליות הזאת כבר קיימת במנגנון מאגר האובייקטים, שמאפשר לנו להגדיר תכונות של אובייקטים באופן מסודר ויפה (ועד ל-QTP 9 זאת הייתה הדרך היחידה לקבל השלמה אוטומטית בזמן כתיבת הקוד). אכן, לפעמים הדבר הכי טוב הוא להשתמש במאגר האובייקטים, בלי התחכמויות ומעקפים, אבל במקרים מסויימים, שימוש ב-DP עדיף בהרבה, או אפילו בלתי נמנע :

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

התמודדות עם אובייקטים כפולים : במצב בו תכונות הזיהוי שהכנסנו מתאימות ליותר מאובייקט אחד, נקבל הודעת שגיאה – object's description matches more than one of the objects currently displayed in your application. כאשר משתמשים במאגר האובייקטים אין דרך קלה להתמודד עם המצב (ניתן באופן עקיף ומסורבל להשתמש ב-Recovery Scenario). כאשר משתמשים ב-DP, ניתן להוסיף לתכונות הזיהוי את : "index:=X", כאשר X הוא מספר רץ (מתחיל מ-0), ובכך להתייחס מיידית לחלון ה-X בעל התכונות שצויינו.

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

אובייקטים בהיררכיות משתנות: לעיתים אובייקט בן יופיע תחת אובייקט אב אחר בכל פעם (לדוגמה – Pop-Up שמופיע תחת החלון שהקפיץ אותו). ישנן אפליקציות בהן הדרך היחידה לעבוד עם אובייקט כזה הינה באמצעות DP, שמתעלם מהיררכיית האב, ופונה ישירות לאובייקט הרלוונטי.

שימוש שמגיע לו סעיף בפני עצמו

עבודה עם אוסף אובייקטים: בסופו של דבר, אחרי כל היתרונות והפרטים, כאן גלום ה"Killer Feature" של טכניקת DP, והוא שהופך אותה לכלי חובה עבור כל אלו העובדים עם QTP. קשה להפריז בחשיבות היכולות והאפשרויות הגלומות בשיטה זו. הרעיון הוא שבמקום לעבוד עם אובייקט בודד, אנו יכולים לציין קריטריון לזיהוי, לקבל את כל האובייקטים שעונים לקריטריון הזה, ולעבוד איתם סדרתית.

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

שיטת אוסף האובייקטים מהווה שדרוג מהותי היות ואנו לא תלויים בשינויים באפליקציה. מספר ה-CheckBoxים, התכונות שלהם ומיקומם עשוי להשתנות בעתיד, אבל התסריט ימשיך לעבוד בלי בעיות. מעבר לכך, ישנן אפליקציות שרק באופן הנ"ל ניתן לעבוד איתן.

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


Dim oDesc
Dim oChildren
Dim i

oDesc = Description.Create
oDesc("micclass").Value = "VbCheckBox"
'----We could've left the oDesc object blank, To get all objects----'

Set oChildren = VBWindow("Main").ChildObjects(oDesc)
'----Now oChildren holds the checkboxs' collection----'

'----Run through the collection----'
For I = 0 to oChildren.Count-1
'----Set the specific checkbox to "ON"----'
oChildren(i).Set "ON"
Next




כפי שאולי שמתם לב, פקודת .ChildObjects צריכה אובייקט אב להתבצע בתוכו. המשמעות היא שלא ניתן לקבל אוסף של חלונות Top-Level, היות שאין אובייקט מעליהם בהיררכיה, עליו נוכל לבצע את הפקודה. הנושא טופל ב-QTP 9, בעזרת אובייקט העזר Desktop. האובייקט מכיל את כל חלונות ה-Top-Level, ומאפשר לקבל בחזרה אוסף שלהם ע"י ביצוע Desktop.ChildObjects(oDesc), ושלום על ישראל.



דוגמאות ושימושים נוספים ניתן לראות בקובץ המצורף.

קריאה נוספת

1. קבצי העזרה של QTP : ערך Using Programmatic Descriptions