Thursday, May 25, 2017

שמות עתיקים ומעלי עובש

names that get old and stale

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

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

----------------------------------------------------------------------------------
One of the first things that are being taught at every introductory programming course is "choose meaningful names for your variables". Those names make your code more readable, and save hours of work. 
A small point that don't get mentioned enough, is that names deteriorate. Sometimes, the domain language we speak is changing and old terms become meaningless, sometimes, it a matter of duplication - After all, what's more sensible than naming the permission to deactivate the main flow of our product as "PERM_DEACTIVATE" ? and, if we have another feature enabling full deactivation, why not name it PERM_FULL_DEACTIVATION? Three years later, you'll find yourselves asking each time "when you say 'deactivate', which of the two do you mean?". 
This week I got to deal with another, nastier form of term deterioration. 
In our systems there are users. As can be expected, where there are users, there are also admins that can manage those users. One of them is called "admin-all". Originally (we believe) it was meant to create all kinds of users in our system. As sometimes happens in developing systems - more roles were added at a later phase, and no one updated the admin-all permissions (it might even be that it was intentional and not a matter of negligence). Eight years later (at least), a crisis happens! Customers are furious! managers are gathering over phone to discuss! Chaos is reigning!
I won't bother you with the exact details of the problem, I'll only mention that we needed to provide someone with an admin that can create all other users. "Well, easy", I said. "Just give them an 'admin-all'". All the other people in the conversation, all of whom have more years of experience with the product than my mere 5 years, voiced their agreement. Yep, that's the user we need. Each and every one of us have encountered at least once in the past the gap between the claim "admin all" and the actual privileges, so when a short check we did showed that some privileges are missing, all of us had this "oh, right, I forgot" moment. 

I don't really have deep insights from this story, except, perhaps - make sure to maintain the names you choose, and update them if you have reason to. It saves confusion and mistakes later. 

Sunday, May 7, 2017

My problem with the screenPlay Pattern

source: https://farm7.staticflickr.com/6132/6009155327_3fc5e7c0e3_o.jpg

Like anyone who is even remotely interested in Selenium, I too heard about the new emerging "Screenplay Pattern" (you can read about it here, or watch this talk) and took some time to consider it. My first reaction was "I think it's a bit silly, writing all of this for no gain at all", then "It looks like more overhead and duplication than I currently have with page-objects". And then I forgot about it.
In Selenium Conf UK (2016) Antony Marcano gave a talk about it which I watched just since it was well presented, and I wanted to see if I could figure out why is there so much fuss around this pattern. It turns out that the screenplay pattern is simply branded incorrectly, and presented as a (complex) solution to a problem that should be solved in a simpler manner. Apart from that, though, it is a really useful tool to have in my utility belt, after fixing the fundamental flaw it has, of course.
To put things simply - The screenplay pattern is not a good substitution for decent page-objects and, in fact, should co-exist with them.
If you read my previous posts about "page-objects are not enough" (pt.1, pt.2) then you might have noticed that I advocate for a layered infrastructure for your automation. Personally, I see three layers that are really needed (Alex Schladebeck is splitting it to 5 distinct layers, which might be a good idea, depending on your context). The three layers I need are: Tests, Business actions (or "flows") and atomic actions.
The atomic actions are stuff like "click on the 'next' button" or "select all users from the database" (In Alex's model, there's a lower level layer which will do "click on something" or "run this SQL query" - a layer very important if you have multiple technologies to drive your actions).
However, atomic actions are not really helpful for your day to day test, so the second layer is created - the business actions. Here you'll find stuff such as "add a new user", "change password" and so on. Business actions are the place to put the context knowledge in - today logging in requires a username and password. Tomorrow it might require an one-time-password sent by SMS. the business action is the place to invoke the specific atomic actions that compose the actual action (Again, in Alex's model, this is split into two layers - internal and external. I think it's nice that she made this distinction, I don't think it's crucial in my context).
Finally, there's the tests themselves - They should be calling the flows layer, and even though some atomic actions might be used for assertions, as a rule of thumb - test methods should not be calling atomic actions themselves. When it get's to UI actions, this rule is more strict - your test should not contain any selenium code or any page-objects (the same is true for any sort of technology you might be using to drive your UI).

As I hope it's easy to notice - the screenplay pattern fits very nicely to that middle layer. Clear business actions that hide the actual steps taken to perform them. I can think of no reason not to include page objects within the screenplay actions, thus separating "how do I interact with an element" from "which elements should I be interacting with?"
This brings me to another point that was touched upon in Anthony's talk - Bloated page objects should not exist.
By separating the responsibility (the "which" from the "how") , most of this is solved - a page object should contain only the elements in it and a way to interact with each element. Perhaps we might go as far as aggregating filling up an entire form, but anything more complex than "login" should be dealt with in the flows layer. By doing this and keeping the page-objects representing small pieces of the UI , no large classes are created, and the flows layer (where the screenplay pattern is really helpful) remains clean and without unnecessary duplication when two actions go through the same pages.

--------------------------------------------------------------------------------------------

כמו כל אחד שמתעניין קצת בעולם של סלניום, גם לי יצא לשמוע על הדבר הזה שנקרא screenplay pattern (אפשר לקרוא על זה כאן או לצפות בהרצאה הזו) ועצרתי לחשוב על זה. תכל'ס? התגובה הראשונה שלי נעה בין משיכה בכתפיים לתהייה בשביל מה צריך את כל התסבוכת הזו - נראה שבמימוש סטנדרטי של התבנית הזו אני אסיים עם שכפול קוד משמעותי יותר מאשר יש לי כרגע עם page-objects. נו, מילא. קראתי, סיווגתי, המשכתי הלאה. 
בשנה שעברה, בכנס סלניום, Antony Marcano העביר הרצאה על התבנית הזו (נו, אותה הרצאה שקישרתי אליה שם למעלה), וצפיתי בה, בעיקר כי הוא העביר אותה היטב, אבל גם כי רציתי לראות - אולי אצליח להבין מה ההתלהבות הגדולה מסביב לזה. 
ואכן הבנתי משהו - כל סיפור התסריטים הזה (בפוסט הזה, לפחות, אשתמש ב"תסריט" במקום screenplay, כי זה נוח יותר להישאר בעברית) פשוט משווק לא נכון ולכן מציג פתרון מסובך לבעיה שעדיף לפתור באופן פשוט יותר. אבל חוץ מזה? נראה שהתסריטים הם משהו שכדאי להכניס לארגז הכלים שלי - אחרי שמתקנים את הפגם היסודי שבהם. 
בפשטות - התסריטים אינם חלופה טובה לpage objects בנויים כהלכה, וקל וחומר שאינם "הדור הבא" שלהם. למעשה, התסריטים צריכים לדור איתם בכפיפה אחת. 
אם יצא לכם לקרוא את שתי הרשומות שלי על page objects are not enough (חלק א', חלק ב'), בטח שמתם לב שאני ממליץ על אוטומציה שבנוייה בשכבות. בפרט, אני נוטה לחשוב על שלוש שכבות נפרדות (Alex Schaldebeck מציעה חלוקה לחמש שכבות שונות, מה שעשוי להתאים לפרוייקטים מסויימים). שלוש השכבות שלי הן שכבת הבדיקות, שכבת הפעולות העסקיות ושכבת הפעולות האטומיות. 
הפעולות האטומיות הן פשוטות - לחיצה על כפתור "הבא", שליפת מספר המשתמשים ממסד הנתונים. דברים כאלה (אצל אלכס יש שכבה בסיסית יותר בה יופיעו "לחיצה על אלמנט" או "הרצת שאילתה מול מסד הנתונים" - שזו שכבה חשובה מאוד אם יש לכם כמה דרכים לבצע את אותן פעולות אטומיות בסביבות שונות). 
עם זאת, פעולות אטומיות לא ייקחו אותנו רחוק מאוד - ולכן נולדה השכבה השנייה - שכבת הפעולות העסקיות. לפונקציות כאן כבר יש משמעות בעיני המשתמש. זה לא "לחץ כאן" "מלא את השדה הזה" אלא "הוסף משתמש חדש" או "החלף סיסמה". הפעולות העסקיות הן החלק בו נשים את הידע המוצרי - איך עושים דברים. הרי היום כניסה למערכת דורשת שם משתמש וסיסמה, אבל אולי מחר הכניסה תתבצע בעזרת סיסמה חד פעמית שתישלח במסרון. הפעולות העסקיות הן המקום ממנו נקראות הפעולות האטומיות, והן המקום בו מוגדר מה הפעולות האטומיות שצריך לבצע כדי להשיג תוצאה ספציפית (שוב, במודל של אלכס זה מפורט יותר ומחולק לשתי שכבות - חיצונית ופנימית. זו אבחנה שכדאי לעשות, אבל אני חושב שהיא פחות קריטית ברמה העקרונית). 
לסיום ישנה שכבת הבדיקות - כאן נכתבות הבדיקות בעזרת הפעולות העסקיות. למרות שניתן מדי פעם לקרוא לפעולות אטומיות לצורכי אימות (assertion), כלל האצבע הוא שהבדיקות קוראות רק לפעולות עסקיות.  כאשר מדברים על אוטומציה של UI, הכלל הזה צריך להיאכף בקפידה  - בבדיקות לא אמור להיות קוד של סלניום, או של page objects (והדבר נכון לכל טכנולוגיה בה אתם משתמשים כדי לגרום לממשק הגרפי לזוז). 

כמו שאני מקווה שקל לשים לב - המקום הטבעי והנכון לתסריטים הוא בשכבת הביניים. הם מאפשרים כתיבת בדיקות בשפה עסקית ומהווים שכבת תיווך בין השפה העסקית לפעולות האטומיות. באופן אישי, אני לא מצליח לחשוב על סיבה לא לכלול page object בתוך תסריט מסויים - כך אנחנו מפרידים בין "איך לתקשר עם אלמנט?" לבין "אילו אלמנטים צריך להפעיל?"
וזה מביא אותי בחזרה להרצאה של אנטוני - page objects באורך הגלות לא צריכים להתקיים. 
על ידי כך שהפרדנו את הלוגיקה ל"מה להפעיל" ו"איך להפעיל" טיפלנו ברוב הבעיה, הדפים צריכים להכיל את האלמנטים הרלוונטיים ואת התקשורת איתם, ואולי, אם אנחנו מתעקשים, גם מילוי של טפסים. אבל כל דבר מורכב יותר מאשר "login" צריך להיות מטופל בשכבת הפעולות העסקיות. בעזרת הגישה הזו, ובעזרת הפרדה של אובייקטי דף לייצג חלקים מהממשק ולא בהכרח דף שלם עם המון אלמנטים אנחנו מקבלים דפים בגודל הגיוני ושכבה עסקית שלא צריכה לשכפל את עצמה בכל פעם בה שתי פעולות עוברות דרך דף משותף.