Saturday, May 28, 2016

Page Objects are not enough

Administrative note: Due to code mixing badly with Hebrew - English only. Sorry.

So, as we all know, if one is writing automation in Selenium, one of the first things to hear about is page objects. Page objects are cool, very useful way to separate "what" from "how", or, if we want to stick with  the object oriented terminology - to encapsulate functionality. There's a lot written on page objects and why you should use them, so I will just put here a link or two (or three, just to point to an official looking link).
In most examples that I've seen, The situation is fairly simple: one action test, maybe two. Something such as "login" or "search". At this level, we get a neat piece of code that looks very nice indeed. You will see a neat piece of code that looks as follows:

 @Test
 public void testLogin(){
  LoginPage loginPage=new LoginPage(driver);
  MainPage mainPage = loginPage.login("Username","Password");
  Assert.assertEquals(mainPage.getLoginFromGreeting(), "Username");
 }
How much better could it get?
But, selenium tests are quite expensive, and are used often also to test end-to-end flows and complete use cases from start to finish, and such actions might require going through multiple pages. For instance, When completing a purchase in Amazon, there are up to 8 steps to be done after adding everything you want to your cart (see this link). The steps are:
  1. Click "check out"
  2. Choose "I am a new customer"
  3. Fill registration form
  4. Fill shipping address
  5. Choose shipping type
  6. Enter credit card details
  7. Choose Billing address
  8. Confirm order
So, how would this look in Code? 
@Test
 @DataProvider(name="UserProvider")
 /**
  * Tests the checkout process of a new customer. Assuming the cart is already filled.
  */
 public void testCheckoutNewCustomer(User user){
  new MainPage(driver).clickCheckOut();
  IsRegisteredPage isRegisteredPage = new IsRegisteredPage(driver);
  Assert.assertTrue(isRegisteredPage.isInPage(),"Is in IsRegisteredPage");
  RegistrationPage registrationPage = isRegisteredPage.chooseNewCustomer("testEmail@mytest.com");
  registrationPage.fillUserDetails(user);
  ShippingAddressPage shippingAddressPage = new ShippingAddressPage(driver);
  Assert.assertTrue(shippingAddressPage.isInPage(),"Is in ShippingAddressPage");
  ShippingOptionPage shippingOptionsPage = shippingAddressPage.fillShippingAddress(user);
  PaymentDetailsPage paymentdetailsPage = shippingOptionsPage.standardShipping();
  BillingAddressPage billingAddressPage = paymentdetailsPage.PayWithCreditCard(user);
  ConfirmationPage confirmationPage=billingAddressPage.enterNewBillingAddress(user);
  confirmationPage.confirm();
  Assert.assertTrue(isUserRegistered(user), "User should be registered after purchase");
  Assert.assertEquals(getPurchaseListForUser(user).size(),1, "User should have exactly one purchase");
 }
OK, not as nice as before, but workable - right?  I'm using page objects to hide the actual implementation, and where possible, the page object itself returns the next page after asserting we are at the expected page (I got lazy, and it's a fair design choice to do so), so the test should be pretty solid, right?
As you can guess I have some problems with this implementation.
  1. The test is not as readable as I would like it to be - I don't think it's very bad, but it's still a bit cumbersome. It would be a bit worse if I added some error handling and reporting to it, but for the sake of the example, this is complicated enough.
  2. What if I would like to write another test that uses this purchase as a step? Sure, I assume that the folks at Amazon can simulate any state using some sort of an API, and this use case is less relevant for them - but what if I work on a legacy system that doesn't allow for such testing hooks? Do I want to write all of that again? to just call my test as  I would any other function?
  3. Even if I don't use this exact sequence ever in my tests, I will still have some tests  for purchases by a registered user, or by a signed in user (no identification), or I will want to check the default currency that is suggested to the user during the purchase - so there will be some tests that will have some amount of similarity in their steps. What if tomorrow the purchase process changes so that the credit card information and the billing address page now appear in the same page? Should I just go over all of the tests where I perform a purchase and fix them?
For this, we came up at work with two solutions, both are trying to leverage DRY principle in order to deal with the above problems.  One is simple to execute, and readers with just about any programming experience already have that in mind and wonder what am I blubbering about, while the other is a bit more complex.
In this post, as it is quite long already, I will mention the simple solution only, as it is an easy way to jump-start your automation efforts, despite having some flaws.

So, after all of that fuss - the solution to this problem is to use the same approach we used in creating page objects and encapsulate the desired behavior. If we would have written this as a manual test script, it would probably look a bit like this:
  1. As a non-registered user, complete a purchase. 
  2. Verify that the user has been registered
  3. Verify that the user purchase history contains only the purchase during which the user registered . 
So, our coded test should look quite similar. All we should do is to introduce another layer between the page objects and the tests. You might call it "service","utility", "helper" or whatever suites you, personally I'm accustomed to using "flows". 
The flows layer is the translation of business flows to page-object language. It usually does not have any Selenium code, which belongs inside the page objects. The flows layer is providing the tests with simple to use actions that represents the various user "actions". Most of the time, my preference is that a flow will not fail a test, but rather throw an exception like any other library. However, since the flow is a very useful place to put in some general checks that should always apply (e.g.: "login name should be visible after every login"), sometimes a Flow will fail a soft assert. 
So, now for some code. First - the test:
 @Test
 @DataProvider(name = "UserProvider")
 /**
  * Tests the checkout process of a new customer. Assuming the cart is already filled.
  */
 public void testCheckoutNewCustomer(User user) {
  PurchaseFlows.CheckoutNonRegistered(driver, user, PAYMENT_METHOD.CARD, SHIPPING_OPTIONS.STANDARD);
  Assert.assertTrue(isUserRegistered(user), "User should be registered after purchase");
  Assert.assertEquals(getPurchaseListForUser(user).size(), 1, "User should have exactly one purchase");
 }

Neat and clean, right? That's because we shoved the mess under the carpet, which, in my case, looks like this:
public class PurchaseFlows {
 public enum PAYMENT_METHOD {
  CARD, PAYPAL, AMAZON_STORECARD, CHECKING_ACCOUNT;
 }

 public enum SHIPPING_OPTIONS {
  STANDARD, TWO_DAY, ONE_DAY;
 }

 public static void CheckoutNonRegistered(WebDriver driver, User user,
   PAYMENT_METHOD paymentMethod, SHIPPING_OPTIONS shippingOption) {
  new MainPage(driver).clickCheckOut();
  IsRegisteredPage isRegisteredPage = new IsRegisteredPage(driver);
  Assert.assertTrue(isRegisteredPage.isInPage(), "Is in IsRegisteredPage");
  RegistrationPage registrationPage = isRegisteredPage
    .chooseNewCustomer("testEmail@mytest.com");
  registrationPage.fillUserDetails(user);
  shippingToConfirmation(driver, user, paymentMethod, shippingOption);
 }

 private static void shippingToConfirmation(WebDriver driver, User user,
   PAYMENT_METHOD paymentMethod, SHIPPING_OPTIONS shippingOption) {
  ShippingAddressPage shippingAddressPage = new ShippingAddressPage(driver);
  Assert.assertTrue(shippingAddressPage.isInPage(), "Is in ShippingAddressPage");
  ShippingOptionPage shippingOptionsPage = shippingAddressPage
    .fillShippingAddress(user);
  PaymentDetailsPage paymentdetailsPage = shippingOptionsPage
    .ChooseShippingOption(shippingOption);
  BillingAddressPage billingAddressPage = paymentdetailsPage.fill(paymentMethod,
    user);
  ConfirmationPage confirmationPage = billingAddressPage
    .enterNewBillingAddress(user);
  confirmationPage.confirm();
 }
}


You can also note that since flows classes are utility classes, they enables me to somewhat customize the behavior of the flow - I can now choose the payment method and the shipping options, which I didn't really care about in the test, but will become handy since the private method will probably be shared between registered user purchase and non-regisetered user purchase - this way, if something goes amiss with that part, I will have just one place to fix instead of two, three or who knows how many flows.
Of course, The flow itself can look quite different from the example I show here - having static methods with a lot of parameters is not really convenient to use, so you might consider using something else,  but as long as this separates the business flows from the tests, we're good to go.

Whatever design you choose for your flows, a good flow is something a user would consider as a single action. "Order a Large pizza with 4 types of toppings" is one action, despite the need to open the pizza website, choose a large pizza, select 4 toppings, fill in home address and credit details (which, as we remember, was up to 8 distinct pages by itself  in Amazon website). "Change your password and home address", on the other hand, will probably be considered as two actions. Flows that do two things will probably be broken sooner or later to two flows, as we don't always test the two actions together.

Just one last thing before we're done here - It is fairly common knowledge that having direct WebDriver calls from the tests is a code smell (just search google for the number of references you can find to a saying by Simon Stewart "If you have WebDriver APIs in your test methods, You're Doing It Wrong" - I couldn't find the source, so I assume he either tweeted this once or said it in a talk he gave). And the same applies in this case - unless you are writing very short selenium tests, with single page actions, you do not want to have any direct reference to the page objects in your test methods. Page objects should be encapsulated themselves in order to allow the tests to speak direct business language.

There are several problems that could arise by using flows, I will try to address them in a future post about a different solution to this problem. 

Wednesday, May 25, 2016

לעניות דעתי

In my humble opinion



לא מאוד מזמן, נתקלתי דרך Humble Bundle במשחק בשם "מכונת משאבי אנוש". זה משחק ששמתי עליו עין כבר לפני זמן מה, אבל החלטתי שמחיר של 10$ למה שיהיה לכל היותר שעשוע קצרצר זה מוגזם. לעומת זאת, כאשר פורסם באנדל שכלל את המשחק, החלטתי שאני מוכן לשלם דולר כדי לקנות את המשחק הזה (ועוד שניים או שלושה משחקים שכנראה שלא אשחק בהם לעולם), ועל הדרך, חלק מהכסף מגיע לצדקה - אז למה לא?
מהלך המשחק, אם לא טרחתם לראות את הוידיאו בקישור, הוא פשוט - שטח המשחק מחולק לארבעה חלקים - תור קלט, תור פלט, משטח עבודה ורשימת הוראות. בכל שלב, המטרה זהה: צריך להפעיל כללים מסויימים לכל קלט ולשים את הדברים הנכונים בתוך תור הפלט, בסדר הנכון. למשל - יכול להיות שהדרישה תהיה להעביר רק את המספרים הזוגיים, או לכתוב לתור הפלט את כל מספרי פיבונאצ'י שקטנים ממספר הקלט. כדי לבצע את הדברים הללו, עומדות לרשותנו עד אחת עשרה פקודות (כתלות בשלב במשחק) - קריאה מהקלט, כתיבה אל הפלט, הוספה וחיסור, קריאה וכתיבה למשטח העבודה, הוספת וחיסור אחד מבלוק במשטח העבודה, קפיצה לפקודה, קפיצה אם אפס, קפיצה אם שלילי. 
אלו מכם שיצא להם להתעסק עם אסמבלי בעבר בטח כבר שמו לב לדמיון הניכר. 
המשחק עצמו נחמד, ואופטימיזציה של השלבים הגבוהים היא אתגר גם למי שהשלים תואר במדעי המחשב ויצא לו לכתוב קצת באסמבלי במסגרת הלימודים (אני מניח שאנשים שעובדים עם השפה באופן יומיומי לא יתקשו בכלל), אבל הוא גרם לי לחשוב דווקא על מגוון הכלים בשוק שנועדו כדי לאפשר אוטומציה למי שאינם מתכנתים. 
מה הקשר בין משחק שמדמה תכנות באסמבלי לכלי אוטומציה אתם שואלים? ובכן, העיקרון הבסיסי דומה - בשני המקרים המטרה הראשית היא לגרום למישהו לתכנת בלי שהוא שם לב שזה מה שהוא עושה. לדעתי, אפשר ללמוד לא מעט ממשחקים כאלו לגבי הפוטנציאל של כלים מהסוג הזה. חשוב להבהיר - למרות שכלי הקלטה הם האסוציאציה הראשונה (לפחות שלי) כאשר מתייחסים לכלים שנועדו לאפשר תכנות ללא תכנות, אני מתייחס גם לכלים שנראים מתוחכמים יותר ומספקים ממשק גרפי יפה שמאפשר גרירה של כל מיני צעדים לכדי תסריט בדיקות (דוגמה לכלי כזה היא soapUI).

אז למה שמתי לב בזמן ששיחקתי במשחק הזה?

  1. היה לי הרבה יותר קל לשחק במשחק הזה מאשר לאחרים, כי ידעתי שאני מתכנת, ויש לי ניסיון מסוים בתכנות. 
  2. תוך כדי משחק, הרגשתי בחסרונן של כמה פקודות בסיסיות מאוד - כן, זה הרבה יותר פשוט לתכנת את המשחק אם שומרים את סט הפקודות בסיסי, ואפשר בהחלט לעשות כך הכל - אבל זה כל כך מתיש! אם אני כותב באסמבלי, למה אני לא יכול לקפוץ לשורה בתוך הקוד כדי ליצור לעצמי פונקציות? כתוצאה מכך, הקוד שכתבתי היה משמעותית פחות יפה מאשר רציתי שיהיה (כי בשביל קוד יפה הייתי זקוק לפונקציות, או לפקודת "קפוץ אם גדול מאפס", למשל). 
  3. ומילא פקודות חסרות - הייתה פונקציה אחת שהרגיזה אותי במיוחד - המשחק מאפשר לכתוב תוויות - גם על שמות של משתנים (בלוקים על הרצפה) וגם לפני חלקים בקוד (ממש כמו תוויות באסמבלי). לכאורה - אחלה! בפועל, כדי לכתוב תווית נאלצתי לצייר אותה בעזרת העכבר. לכתוב בעזרת המקלדת? לא ולא. אז מה אם זה לוקח לי פי עשרה יותר זמן?
  4. כשהבעיות הסתבכו, הממשק הגרפי הידידותי להפליא בעיקר הפריע - כשהקוד הנדרש היה ארוך, או אם רציתי לנסות ולשפץ את הפיתרון כדי להצליח להגיע לאתגרים הספציפיים - פשוט פתחתי notepad וכתבתי את התוכנה שם. כל ההתעסקות הזו עם חיצים שזזים ממקום למקום בעיקר סיבכה את העין והסיחה את דעתי. 
  5. ככה שמתי לב שלוקח לי הרבה פחות זמן לכתוב את התוכנה שלי מחוץ למשחק. גם בלי בזבוז הזמן על התוויות, לגרור משהו למקום הנכון (ובמקרה של פקודות "קפוץ ל...", לגרור שני דברים) פשוט לוקח הרבה יותר זמן מאשר לכתוב את הפקודה הרצוייה.
וכמובן, כל הדברים האלה רלוונטיים גם לתוכנות אוטומציה להמונים. צריך לנסח אותן טיפה אחרת, אבל העיקרון דומה. 
  1. למתכנתים יהיה קל יותר להפעיל תוכנות כאלה. בסופו של יום, עיקר ההבדל בין מתכנת לבין מי שאינו מתכנת הוא בהבנה של איך כותבים אלגוריתם - וגם אם גוררים ציורים יפים פה ושם, הם צריכים להתנהג כמו תוכנה. אפשר לרכוש מיומנות גם בלי רקע בתכנות, אבל זה רק אומר שלומדים לתכנת תוך כדי עבודה. 
  2. "קוד" שנכתב בעזרת כלי כזה לעולם יהיה פחות אלגנטי ופחות יעיל מקוד שנכתב בשפת תכנות מלאה - כדי לשמור על דברים פשוטים יחסית, התוכנה תאפשר סט פעולות מצומצם יותר. אפילו אם אפשר יהיה לשמור קטעי קוד בתוך התוכנה (SoapUI מאפשרת להכניס קוד groovy, אם אני זוכר נכון) - עדיין יהיה מדובר בקטעי קוד מבודדים, עם ממשק מוגבל להעברת נתונים בין קטעי קוד. 
  3. לכל כלי גרפי יהיו המוזרויות שלו - דברים שהמפתחים החליטו שצריכים להתנהג בצורה מסויימת, ולמשתמש אין מה לעשות חוץ מאשר להתרגל. כן, זה כך גם בשפות תכנות, אבל בממשק גרפי זה מורגש יותר ומגביל יותר. 
  4. ממשקים גרפיים מוספים רעש ומפריעים למוח להתמקד בבעיה. אם בשפת תכנות אני יכול להוציא את קטע הקוד בו אני מתמקד לפונקציה נפרדת או למחלקה חדשה בתוך חמש שניות - טרם נתקלתי בממשק גרפי שמאפשר לי גמישות שכזו, ובחלק גדול מהם -גם אם הצלחתי לבודד את הקטע בו אני רוצה להתמקד, השאר יהיו שם כדי לעשות לי רעש בעיניים או להחביא מידע חשוב מאחורי רשימות מתקפלות. 
  5. תכנות דרך ממשק גרפי הוא יקר באופן משמעותי בהשוואה לתכנות בטקסט. 
אז האם אני אומר שאין טעם להשתמש בכלים לאוטומציה ללא קוד? לא, לא בדיוק. לפעמים זול יותר לתת למישהו ללמוד כלי ספציפי ולהסתפק בתוצאות סבירות עם זמן כתיבה קצת יקר מאשר לשלם למתכנת ולהקים פרוייקט קוד של ממש שיעשה את העבודה באופן קצת יותר יעיל. אבל אני חושב שאם ניגשים לפרוייקט כזה, כדאי לדעת על מה מוותרים ולשם מה. כי למרות שהכלי בו משתמשים עשוי להיראות ממש מוצלח, ולאפשר כל פעולה שאנחנו מצליחים לחשוב עליה כרגע - אנחנו משלמים איפשהו את מחיר הפשטות ועדיף להיות מודעים  לכך.
חוץ מזה, המשחק הזה די חמוד.
----------------------------------------------------------------
Not very long ago I came, via humble bundle, across a game called "Human Resource Machine". It's a game I've noticed quite a while ago, but decided that while 10$ isn't much at all, it is still more than I'm willing to pay for what would probably be a fairly short amusement with almost no challenge. So, since humble bundle offers a "pay what you wish", I bought this game (alongside two or three other games I won't even install on my PC, not to mention play) at tenth of the usual price of the game. 
In case you skipped the video in the link above, the way the game goes is as follows -  Each level looks the same: there's an input queue, an output queue, a work floor and a place to write instructions. Only two things change between levels: the possible inputs in the queue, and the required output. For example, one level might be "move only the even numbers to the output queue and discard the rest" and another might be "for each number in the input, output all Fibonacci numbers that are not bigger than it. (so, for the input "10", output 1,1,2,3,5,8). In order to achieve this goal, you have a set of up to 11 possible commands: take from inbox, put in outbox, copy to a tile in the work-floor, copy from a tile, increase a tile by 1, decrease a tile by 1, subtract, add, jump to command, jump if zero, jump if negative. 
Those of you who have seen an assembly code once or twice in their life can surely see the similarity. The rest - trust me, it's a fully capable assembly programming language. 
The game itself is quite nice, and optimizing the solutions for the higher levels is a bit of a challenge even to someone with formal CS background and a bit of university-level experience in assembly such as myself (I'm assuming it wouldn't be any problem for those with some practical experience with assembly), but it made me think about the plethora of tools meant to provide non-programming testers with automation capabilities.
How did I get from playing a game to thinking about commercial, sometimes very powerful tools? Well, the basic goal is the same - to have people programming without noticing this is what they do. I think we can learn something about those tools by looking at the game and projecting this on the click-programming tools.

  1. I had it much easier than others. I knew I was programming and I was familiar with assembly. 
  2. While playing, I strongly felt the need of certain basic commands. True, it is simpler to program the game while keeping the instruction-set to a minimal, and everything can be done this way - but this is so very much more frustrating this way. Why couldn't I jump to a line in my code? or even better, have a "call function" functionality? As a result, the code I had in the game was not as clean as I would like it to be - there's a limitation to what I could do without functions, or even a "jump if greater" command. 
  3. On top of the missing instructions, I found one existing, and quite needed feature to be extremely annoying. It was the labeling mechanism.The game allows for creation of labels by drawing pictures on a limited space. Both pieces of code and tiles on the work-floor can be labeled. However, the process of creating a label required me to doodle a word using the mouse. Writing with the keyboard? nope. So i spent about 30 seconds drawing "first" instead of typing it in two. In the cases I skipped using labels, I ended up confusing myself. 
  4. When I got to the more complex problems, especially to those requiring long code, or those where I didn't manage to optimize the solution easily, I wrote my program in notepad++ before copying it to the GUI. Messing around with dragging and dropping commands, placing them properly (with jump commands requiring two such actions!), was taking me too long to properly think. Moreover - I couldn't think properly when looking at the spaghetti code I had. One can't really write assembly code without jumping, since every "if" statement is jumping somewhere, but with the GUI I could see the lines intersecting, and most of the times had to scroll up or down in order to see where I was jumping to or from. Processing the information from the GUI took more thinking resources from me than sorting out the algorithm in a clean textual way. 
  5. by writing stuff in notepad I noticed that even when I knew what I should do and did not have to stop thinking, writing code was tenfold faster than dragging stuff in the GUI. Even without the labels mess - working with the mouse is that much slower than typing. 
All of those points are, to some extent, relevant to all "you don't need to be a programmer" tools. and here's how I try to generalize them: 
  1.   Even with those programs, programmers will have an easier time generating more valuable product. The concepts of code reuse, being able to design an algorithm to solve a problem and the rest of the programming shticks really apply to this type of programming as well - not everything is feasible in every tool, but programming with a  GUI is still programming. 
  2. "Code" written with such tools will, most of the time, be less pretty than proper code written in a proper programming language. If the tool is really good, most of the mess will be hidden under the carpet and the developer working with this tool won't notice the mess, or it would be quite expensive to design and implement a framework that will be cleaner. Even when one have the ability to write code snippets, moving data between code snippets will be limited to the way chosen by the tool. Making the tool itself communicate with your code snippet is even more limited - could I return an error code from a snippet that will direct the choice of the next step performed by the tool? The tool can parse only the interface chosen for it. 
  3.  Every graphic tool will have its own quirks - missing functionality, hidden stuff, common actions that take 5 clicks to perform or even simply doing things in a certain way, and the users can only grind their teeth and get used to it. It is also true for any programming language (really, Python? white spaces are syntax??), but in a GUI those nuisances are more present and a whole lot more annoying. 
  4. Graphic user interfaces add a whole lot of noise that interferes with engaging the programmatic problem. In any decent IDE I can take a problematic piece of code I want to focus on and extract it to a method or to a new class in less than five seconds, where I can then look only at the code I want to think about. I have yet to see a graphic tool that enables this.
    And even if there is something equivalent - all of those graphic hints ("this is a data source", "this is a piece of code", "this is an assertion") still require some brain power to parse. Yes, it gets better as one gets used to the graphic language, but my guess is that it still requires more than simply reading text. 
  5. Programming through a GUI is way more expensive than using text. 
So, what am i saying, that such tools are useless? That everyone should just learn to program and write their own code? While tempting - that's not what I say. Those tools (at least the good ones) do serve a purpose, and for that purpose they do very well - a non-programmer can definitely benefit from them, and even for a programmer, they tend to save quite a while of setting up your own infrastructure.
What I am saying, though, is that those tools, shiny as they may be, contain a rotten core - some fundamental limitation that will punish you some day. It might be an explosion of hard to manage "scenarios" (or "suites", or whatever), it might be that you'll start building large objects that will make the tool UI slow, or it might only be the fact that you get to the point where you have exhausted the potential of the tool, and now you can't augment it (or it is really hard to augment and you waste a lot of time finding a workaround). But, as this bite might not be that bad, it does make sense, in some cases, to use such a tool. However, when you do consider choosing such a tool, take its promises with a pinch of salt, and be aware of what you are losing, not only what you  might gain. 
Besides, this game is quite cute. 


Friday, May 13, 2016

It's hibernation time

This one is in English only, as code and Hebrew text don't mix well. 
The information below isn't new, and can be easily found in the internet, but after being burnt by it three times, perhaps it's time I put a warning here for everyone else to see, and for me to remember. 
Some of us are working with Java. Or are testing code developed in Java. In this case, I think it's safe to assume that most are working with a database, and some of them will be using Hibernate, or any other ORM library. If you are in this situation, check your wristwatch often. 
More specifically, check that every time you store in the database any indication of "when" it contains both the right date and the correct time.Sometimes, one of them will be set unintentionally to the default value (either Jan 1st 1970, or midnight - depending on the part that got messed up).  
I encountered this in a hibernate implementation, but some of the answers here claim that this issue is at the JDBC layer, so it might exist in any Java database operation (When we were using plain JDBC, or JDBC template, we accidentally avoided this by writing it as a string, but it might happen also there). 
Now, let's have a look at the problem itself: 
Those of you working with Java probably already know that it has a really crappy approach to dealing with time. You have at least two objects named "Date" (java.util.Date and java.sql.Date, which inherits from the first), then you have java.sql.Time and java.sql.Timestamp. I'm leaving outside the proper approach to dealing with time through Calendar objects, since when working with databases you will eventually have to use java.util.Date, or any of its sub-classes (all of the mentioned above are directly extending it). 
Why so many classes you ask? that's a good question. The reason is probably that SQL has a conceptual separation between date & time. "Date" is a representation of the year-month-day, where "time" is the hours-minutes-seconds-milliseconds part. So, java.sql.Date and java.sql.Time are mimicking this logic, with java.sql.timestamp filling in for SQL concept of Timestamp (which is simply date+time). 
Now, let's have a look on a pretty standard hibernate mapping of a table.
The table is defined:

Which looks in Java like this:

If your DB column is defined as date (which, at least in Oracle's DB is accepting also hours and minutes by default) most Hibernate code generators (you didn't think someone was actually going to manually map those DB columns to Java, did you?) will use by default TemporalType.DATE which, in turn, will cause your application to save dates that are only exactly at midnight. 
Or, even if you fixed your annotations properly, the setter is still getting a java.util.Date, you can have one of your developers (including yourself) put a line such as: 
And now you'll have a perfectly good Jan 1st 1970, but with the correct hour.


So, If you are working in these conditions (Java, some ORM, and probably with Oracle too) - take special care to check that the time & date are written properly to each an every column, and recheck it whenever you upgrade your JDBC driver or your ORM version as well.

Wednesday, May 11, 2016

אל תקרא לי מהנדס

Not an Engineer

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

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

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

וכאן אנחנו מגיעים לנקודה שבאמת הציקה לי לאורך הוובינר - אחת לכמה זמן, נזרקה לחלל האוויר האמירה "כי פיתוח תוכנה זה עדיין לא מקצוע הנדסי". 
האמירה הזו מכילה חלק נכון מאוד וחלק שגוי מאוד. החלק הנכון בעיני הוא שפיתוח תוכנה בצורתו היום אכן אינו מקצוע הנדסי. שלא כמו מהנדסים במקצועות אחרים שיודעים לייצר תהליכים ולפקח עליהם כך שהתוצר יהיה באיכות יציבה ובעלות צפויה, מהנדסי תוכנה מאלתרים את דרכם קדימה במרץ.
החלק השגוי הוא במילה "עדיין", ממנה נובע שפיתוח תוכנה צריך או יכול להפוך לתחום הנדסי. יש כמה סיבות בגללן אני סבור שפיתוח תוכנה בכלל, ובדיקות תוכנה בפרט, צריך להימצא על הציר שבין אוּמנות (craftsmanship) לבין משהו בתחום מדעי החברה. 
  1. מקצוע הנדסי פועל בסביבה יציבה - מהנדסי בניין, מהנדסי מזון, מהנדסי תעופה או מהנדסי רכב - כל אלה פועלים בסביבה קבועה שאינה משתנה, או שמשתנה לאט מאוד. הכביש בו נסעה המכונית אתמול הוא אותו הכביש בו היא תיסע מחר,ובעוד חודש ובעוד עשרים שנה. הקיבה האנושית תעכל את אותו המזון היום, מחר ובשלושת הדורות הקרובים לפחות. גם האוויר בו טסים המטוסים לא יהפוך חומצי מספיק כדי לאכל מתכת ביום אחד. עולם התוכנה, מצד שני, משתנה בכל יום - טלאי אבטחה של מערכת ההפעלה עלול לחסום שימוש בספרייה חיצונית במוצר אותו אני מפתח, עדכון דפדפן יכול לגרום לאתר שלי להיראות מכוער, ואם מתמטיקאי כזה או אחר ימצא חולשה במגנון ההצפנה שלי, הוא הופך להיות לא בטוח בתוך רגעים מפרסום המאמר.  הסביבה הדינמית בה תוכנה קיימת מונעת את היציבות הנדרשת ממקצוע הנדסי. 
  2. תוכנה היא מערכת חוקים שנועדה לפעול בסביבה אנושית. חלק מהבאגים הם עניין של העדפה - גוגל חשבו ששליחת תמונה של המיניונים זה רעיון מגניב, וחלק ממשתמשי הדוא"ל הסכימו איתם. חלק אחר, לא. יש כאן עניין של איזון בין רצונות שונים, וזה כבר עניין של טעם, או של מחקרים סוציולוגיים. מדע מדוייק - זה לא, וזה גם לא יכול להיות. לפחות לא כל עוד אנשים מעורבים בעניין.  
  3. מהנדסים עושים את אותו דבר שוב ושוב ושוב. הבעיות שצריך לפתור בבניית גשר הן קבועות - הוא צריך להיות יפה, זול, עמיד בפני העומס הדרוש, או בפני רעידות אדמה ורוחות כפי שנפוץ שיהיה, וזהו. תוכנה היא עניין חד פעמי - מעט מאוד תוכנות עושות את אותו הדבר בדיוק, ופיתוחים חדשים בתוך אותו מוצר יתמודדו עם בעיות שונות. אני לא מנסה לומר שהנדסה היא קלה יותר, אלא רק שהיא מובנה ומתוחמת יותר. 
עם הסיבות האלה (ועם עוד כמה שלא הזכרתי ובטח מתחבאות מעבר לפינה), אני לא חושב שהשאיפה להפוך את התכנות למקצוע הנדסי, בו ניתן לחשב עלויות מראש, לסמוך על מספר פגמים נמוך או כל מיני רעיונות נאצלים שכאלה. 

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

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


נ.ב.
כשעברתי שוב על הוובינר, נתקלתי בסיבה נוספת שבלאק מציין - כי העלות של תיקוני באגים מושתת על הלקוחות (כתשלום על תמיכה).
אני לא מכיר את השוק מספיק טוב כדי לחוות דעה מוסמכת בנידון, אבל תחושת הבטן שלי היא שיש כאן מתיחה מסויימת של המציאות: חברות שפועלות במודל SaaS לא גובות תשלום על "תמיכה" אלא דמי-שימוש, ובמרבית המקרים בהם אני נתקלתי, המשמעות של "תמיכה" הייתה קבלה של עדכונים ופיצ'רים חדשים, או סיוע בביצוע משימות לא טריוויאליות. אז נכון, חלק מהעדכונים הם תיקוני באגים, אבל טרם שמעתי על חברת תוכנה שיש לה יותר זמן מאשר משימות, וכל באג שמתוקן הוא פיצ'ר שלא יוצא ללקוחות ולקוחות שאינם מצטרפים כי הפיצ'ר הזה חסר.
-------------------------------------------------------------
The other day, I attended a webinar by Rex Black (is "attended" the correct word for a webinar?). As I think I have already stated - I find his questions very interesting, and most of the time I don't like his answers even a bit. The subject was "Why does software quality (still) suck?". The answer, in short (if we skip the part of "not enough time and budget" which is a general truth) is divided to three parts : 
  1. Because software engineering has not yet matured to true engineering.
  2. Because we don't know how to measure quality, so we cannot improve in it. 
  3. Because software testers are not properly certified. 
I want to start with a short point of agreement - Rex Black has defined this webinar as a rant. That was very correct. 
All of the rest, however, is fundamentally wrong. 
First of all - I do not necessarily agree that the quality of software is low. During most of the webinar, Black uses "defect density" as an indication - until the point where he claims that this isn't a very strong measurement (Since two, very different quality products, can have the same defect density) and says that the main reason of using it is that it is easy to measure and that we don't really have other accurate measurements. Well, I'm a regular user of software. And when I look at the software I use, I can't really say that they are low-quality. Yes, there are bugs, sometimes really annoying ones (at work, my notepad++ is crashing randomly on my work computer. I claim it is still of higher quality than the regular notepad that is stable, to the best of my knowledge), but still, my PC loads reliably, does what I expect it to do and have a pretty decent user experience. Even my five years old phone, that suffers from both wear and weak hardware, is doing its job, more or less. So why insult them with claims of low quality? 

Second - if we claim that we don't know how to measure quality, on what basis is Black claiming that the quality of software sucks? What lies beneath this claim is the fact that even without measurements we can provide some rough, generally acceptable, estimates of software quality. We might not be able to put an exact number to it, but we can T-shirt size it, and if we set up to improve our quality, we can conduct a series of experiments and see if the quality improves. We might miss small improvements, but we will notice bigger improvements, so not being able to measure quality isn't really a reason for poor quality (from this point onward I will accept the claim that software quality sucks for the sake of the argument). 

And now we got to the point that bothered me the most - software development is not yet a true form of engineering. 
This has two part - the correct part, and the wrong part. 
the correct part is that software engineering is very different than other types of engineering that we are familiar with. Other engineering professions are about creating processes and applying (or supervising) them in order to get consistent results in a predictable cost, software engineers are improvising their way forward. 
The wrong part lies in the "yet" part. There are several reasons that lead me to believe that software development (and testing in particular) isn't, and shouldn't be, an engineering field but rather somewhere between a craft and a social science. 
  1. An engineering profession is operating in a rather stable environment. This environment might be extremely hostile, but is is largely constant - the type of problems do not change drastically or rapidly. Car engineers, avionics engineers or architects are dealing with the same problem again and again - the physics that enable an airplane to fly are constant, the road conditions for cars are pretty much the same as they were in the last 50 years. The solutions might vary, but something that worked in the past, will still work today, and is still a valid measure to compare against.
    The software world, however, is changing rapidly. If the first computers were standalone devices, the proliferation of the internet has changed everything, then mobile came and flipped it all upside down, and now we have IoT to change it all again. Moreover - even if we remain in the same world - an OS update might change some of the assumptions that were made when the software was developed (such as - running with admin privileges does not require special approval), or a browser update can make my site look horrible. Even if nothing changed on my computer - it is sufficient that a cryptographer finds a new flaw in an encryption algorithm to suddenly render my software insecure. In such instability, I don't think it's feasible to create an engineering practice. 
  2. A software is a set of rules that operates in a human environment. Some bugs are simply a matter of preference. Google thought that sending a cute Minions animated .gif is a great idea for April 1st, and some of the users agreed. Some others, however, didn't. It is a problem of balancing different wills and needs of different people, which is really a matter of taste, or of some psychological \ sociological researches. It is not, and cannot be, an exact science, at least as long as people are involved. 
  3. Engineers solve the same problems over and over. A bridge should carry such and such weight, withstand earthquakes and winds in a given range, be as pretty and cheap possible. Once a solution that satisfies those conditions is found, it can be reused and adjusted over and over. In a software project - each time the software solves a different problem. Even when working on the same project, each new feature is addressing a different problem - the common parts are quickly extracted to libraries. The equivalent of "build a bridge" is something like "open an HTTPS connection" - but software is the whole ecosystem - it's about the parts that cannot be reused but have to be invented. The engineering equivalent of just about every software isn't the final model, but rather it is the first prototype. 
for theses reasons (and probably some others I just forgot to mention), I don't think software development should ever be similar to engineering professions

Now, the third reason mentioned by Black is that software testers aren't properly certified. This claim manages to be at the same time true and twice false. 
It is true in the part that there isn't any common way to join the testing profession or some formal training that is widely accepted. I always feel a bit sad when I hear all those "how I fell into testing" stories (am I the only one who set out of the university and actively looked for a testing position?), but as long as there isn't any clear path a tester should tread, it will probably still be the main way testers find their profession. I think that having some strong certification path can be beneficial both to the testing profession and to the software industry. 
it is wrong once in that that not having a certificate isn't an excuse. Ok, so testers start with a disadvantage. I still expect from an experienced professionals to be good at what they do. A tester with 3 years of experience should be able to provide for the testing needs of a company in a sufficient manner, and a tester with ten years of experience should be able to do testing well and have that added value that might have been easier to achieve with a strong certification.
It is wrong a second time since when Rex Black speaks about certifications, one cannot overlook the certification program that he is involved in - the ISTQB certifications. I'll spare you the usual rant about ISTQB as a certification program (I promise to have a post about it sometime soon), but for now, let's face the bottom line of that rant - The ISTQB certificate (and I refer mostly to the CTFL) isn't providing value, and companies that have a decent percentage of  their testers with CTFL or more advanced certificates are not known for having a better quality products. 


All in all, despite (or maybe because?) I didn't agree with any of the points, it was a very interesting webinar. I strongly recommend listening to Rex Blacks webinars - check up one or two, and see if it makes you think. There's a webinar usually once a month, and the next one is next week.