Friday, December 28, 2018

Information Science: Information is Meaningless Without Context - Now With Puppies!

Motivation


Most simple text editors will open any file and try to display it as text.  This phenomenon is where this post came from... a junior coworker asked why an .exe looks like garbled text when viewed with a text editor.

The answer is context.

What does "10110101101000101110101110" mean?  


Well it depends on who you ask.
Let's put those exact bits into a file called:
  • my_file.jpeg and open it with Photoshop - it's going to be interpreted as an image
  • my_file.mp3 and open it with iTunes - it's going to be interpreted as sound
  • my_file.txt and open it with Notepad - it's going to be interpreted as text

That's oversimplifying it because some standard file types such as jpegs and mp3s must always start with a "header" that basically says "this is an mp3," but let's ignore that for a moment.  Let's just talk about the 1s and 0s that represent the song in the mp3 file.

How many puppies does "11" represent - A, B, or C?

A


B


C

The answer again is context.
  • If "11" is read as decimal (base 10, which we all use in everyday life), then it's A.
  • If "11" is read as binary (base 2), then it's B.
  • If "11" is read as octal (base 8), then it's C.

For a quick read on this, see Information Science - A Simple Explanation of Base Number Systems: Binary, Decimal, Octal, Hexadecimal

So when a program like iTunes sees a string of 1s and 0s it's going to read it as sound, and if it's just a random string of 1s and 0s, it'll be pretty noisy.  An .exe file is supposed to be read by the operating system, so if you open it with a text editor it tries to turn the 1s and 0s into a bunch of letters, so it just looks like garbled text.

Other examples

Human Languages

To think of this yet another way, I could write "Plato" on a piece of paper and give it to someone who speaks English and they'd probably interpret it as the name of the Greek philosopher.  If I give that same piece of paper to someone who speaks Spanish, they'd probably interpret it as a flat round thing you eat food off of ("plato" is Spanish for the English word "plate").

Character encoding schemes

Similarly to the decimal vs binary example, we have different standards that make a specific set of bits represent different readable symbols.  Some examples you may have heard of are ASCII and ANSI.  Let's take this set of bits:
01001000 01000101 01011001 00100001

ASCII was thought of long ago as a simple character set with a small range of 00000000 to 11111111 (a total of 255 unique characters), so each of the 4 chunks above represent a character... in fact it represents "HEY!"

Unicode is intended to be the last character set we'll ever need, so it has a huge range of 1,114,112 unique characters, currently only around 100,000 of which are used.  It's a much more complex system, including the Latin, Greek, Cyrillic, Chinese, and Thai alphabets (along with others) and all kinds of symbols for music, math, just about anything you can think of.  Suffice to say, if you give a long string of 1s and 0s, interpreting it as ASCII and Unicode will likely not yield the same result.

To sum up...


and reiterate (to a hopefully tediously clear degree): information in a file is just like information in the real world - it can only be correctly understood if the context is correctly understood first.

Information Science - A Simple Explanation of Base Number Systems: Binary, Decimal, Octal, Hexadecimal

Motivation


I Googled for a quick, simple explanation of this and was very surprised when I failed to find one, so here we go.  Oh, and the whole puppies thing is a reference to this post.

Bases


The base is just defined by how many characters we have to represent numbers.
  • In binary (base two), we use characters 0, 1
  • In octal (base eight), we use characters 0, 1, 2, 3, 4, 5, 6, 7
  • In decimal (base ten), we use characters 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
  • In hexadecimal (base sixteen), we use characters 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F
In decimal we can represent zero to nine puppies using only one character.  At that point if we add another puppy, we have to move over a spot and start over with the one character, so we count:
  • zero 00
  • one 01
  • two 02
  • three 03
  • four 04
  • five 05
  • six 06
  • seven 07
  • eight 08
  • nine 09
  • ten 10  --  here we start reusing characters...
  • eleven 11
  • twelve 12

but for binary we only have two characters with which to represent puppies, so we have to move over a lot quicker:
  • zero 000
  • one 001
  • two 010  --  quickly we start reusing characters...
  • three 011
  • four 100
  • five 101
  • six 110

similarly for octal, we count:
  • zero 00
  • one 01
  • two 02
  • three 03
  • four 04
  • five 05
  • six 06
  • seven 07
  • eight 10  --  here we start reusing characters...
  • nine 11
  • ten 12

and finally, for hexadecimal, we have to "make up" some extra characters that we're not used to (since most humans are used to the decimal, or base 10, system), so someone long ago chose the letters A-F:
  • zero 00
  • one 01
  • two 02
  • three 03
  • four 04
  • five 05
  • six 06
  • seven 07
  • eight 08
  • nine 09
  • ten 0A
  • eleven 0B
  • twelve 0C
  • thirteen 0D
  • fourteen 0E
  • fifteen 0F
  • sixteen 10  --  finally we start reusing characters...
  • seventeen 11
  • eighteen 12

Hexadecimal can be a little weird to think about but try thinking about it this way:  if someone created five hundred unique characters that we used to count things, we could just point to a herd of, for example, four hundred and eighty puppies in a park, and you could use a single character to represent that number. That character would uniquely identify the number of puppies you see sniffing and playing and rolling in the dirt.  My guess is that you would spontaneously explode from how cute it was.

A generic formula for figuring out what a number means in your base


So the generic formula for figuring out how many puppies we're talking about is to take the base multiplied by itself the number of times we've had to "move over a spot" and then multiply it by the number in that spot.

So "432" in decimal:
  • ten * ten * four +
  • ten * three +
  • two =
  • Four hundred and thirty two

"432" Octal:
  • eight * eight * four +
  • eight * three +
  • two =
  • Two hundred and eighty two

"2BC" Hex:
  • sixteen * sixteen * two +
  • sixteen * eleven +
  • twelve =
  • Seven hundred

"1101" Binary:
  • two * two * two * one +
  • two * two * one  +
  • two * zero +
  • one =
  • Thirteen

The formula can also be expressed as "the base raised to the power of the number of times you've had to "move over a spot."  It's just the same.  For example:

"432" in decimal:
  • ten to the second power * four +
  • ten to the first power * three +
  • ten to the zeroth power * two =
  • Four hundred and thirty two

Thursday, November 29, 2018

Automation Strategy (Email): Putting Intelligence into Your Library Methods

Summary

This is an email I sent to my team who had written some if statements which were dependent on the customer in use (customers could customize their UIs to some extent).  I let them know that since several customers will have similar UIs, we don't need to do an if based on each customer ID, we can put some intelligence into the method so it can decide what to do based on what the UI looks like.

In general, I'm not interested in testing that the UI is correct and that the test data is set up correctly - I'm interested in testing the logic, e.g. add to cart, change quantity, make a purchase.  That statement is relevant here because let's say customer 1234's should be set up to use UI version A and there's a bug in UI version A.  The test should fail.  But if the test data has customer 1234 set up to use UI version B, the automation will happily use UI version B and may pass.  But I would argue that's a data setup problem, not an automation problem, and would be a problem even if the test were run manually.

Email

Currently in ProductFlow.java we have code that looks like this:


 public void addProductToShoppingCart(CheckOutProduct checkOutProductString catalog) {
     
if (catalog.equals(StaticData.US_CATALOG_1234) {
         
//do one thing
     
else {
         
//do a different thing
     
}


If we use lots of manufacturers, this list will get big.  This method addProductToShoppingCart() does not have much intelligence in it, and theoretically the manufacturer's page could change.  Something we can do instead of blindly relying on the manufacturer ID is to make the method more intelligent.  The method could analyze the page, such as:


 public void addProductToShoppingCart(CheckOutProduct checkOutProductString catalog) {
     
if (navigationLinkA.isPresent()) {
         
//do one thing
     
}
     
if (navigationLinkB.isPresent()) {
         
//do a different thing
     
}


An example of this would be in the selling portal – some customers have a side navigation link with text "Part Numbers" and other have a link that has text "List by Part Number"

In fact, now that I think about it, a better solution would be to have a method called "navigateToPartList()" and put these if statements in there, then all addProductToShoppingCart() would do is call navigateToPartList()... 

As I've said many times before, let's keep pushing that work farther down the road (make someone else do it), and have lots and lots of very simple, reusable methods.

As always, this is not the best solution in all cases.  Using the customer ID is perfectly fine sometimes, this is just an alternative way of doing things.

Monday, November 26, 2018

Automation Strategy (Email): We Will Achieve Efficiency Through Correctness, Not Speed

I just thought this was important enough to make a post about.  At first I thought it would be self-explanatory, but in case it isn't - I'm talking about team efficiency, not processor efficiency, although the case could be made there as well.

This is a team of contractors who, like most contractors, wanted to pump out tests and executions.  I consistently ask them to slow down and write good code so that our pull requests go faster and our code requires less debugging during automation execution.



From: JW

Date: Monday, November 26, 2018 at 1:20 PM

To: AP, VG

Subject: Re: Test_Automation(22-Nov-2018)


Team,
Please always pay attention to details.  Our Pull Request numbers and SQA Project numbers are very close.  Below it says PR 475 but should be SQA-475.  Not a big deal, but just go a little slower - we will become the most efficient by prioritizing correctness over speed.  The same is true for writing clear, understandable code.  We need to think more about the next developer than we do about ourselves.  If it takes you extra time to save your teammate time, please do it - a week later you may be the one receiving this gift!


JW
Automation Lead



From: AP
Date: Thursday, November 22, 2018 at 10:45 AM
To: JW
Subject: Test_Automation(22-Nov-2018)

Hello Team,
As per plan of action, team worked on automated suite execution and failure analysis of 18.12 release.

AP:         
  • Worked on failure analysis of OR and fulfiller-15569 automated regression tests(SQA-469)
  • Updated the Wiki page for 11 known issues of 18.12 release. 
VG:
  • Reviewed PR 475 and updated comments in JIRA for it
  • Reviewed PR-419 and updated comments in JIRA for it
  • Created PR-423,PR-424 for SQA-453 and completed it

Tomorrow's plan of action:- To continue work on fixing the failed test-script and handling prescripts and further work on SQA board tickets.

Thanks!

Automation Strategy (Email): Method Naming - Do What You Promise

Team,
I have always made clear how critically important method names are.  Sometimes the difference is very big and sometimes is it more subtle.  I just want to point out a couple of examples I've just seen.

1.
A pretty big one is
verifyUserExistence()

which looks to see if a user exists, but if it does not, it creates a new user.  Creating a new user has NOTHING to do with verifying a user's existence.  I have created an issue to refactor the name to verifyOrCreateUser()


2.
adminViewFulfillerUsersPage.enterNewUserDetails()
I would expect this method to enter text fields on a page.  What it actually does is enter all the text fields, then click the Create User button.  This is a subtle difference, but in some refactoring, Vibhav renamed it to

adminViewFulfillerUsersPage.createAdminFulfillerUser()
which I believe is a better name, because this method does more than simply entering details.

Every time you write a new method, give serious thought to what its name should be.  The next developer to come along will waste a lot of time and be upset with you if your method says one thing but actually does another (or does more than what it says).  It would be like if you asked someone "Please get me a drink" and they said "No problem," then they got you a drink, drank all of it, and handed you an empty cup.  Don't be that guy!

Note that we didn't change the name from
enterNewUserDetails()
to 
enterNewUserDetailsAndClickCreateButton()

instead, we go up a level in abstraction and just called it
createAdminFulfillerUser()

This helps avoid creating methods with very long names.  If you have any questions or would like suggestions for method names, please ask.
Thanks-

Thursday, September 20, 2018

Automation Strategy: Externalize All Your Strings


Motivation

We need to externalize all the strings we use in our locators, such as "User order number" or validation methods, such as "Order Created."  Why?
  1. It'll make your code more maintainable if that string changes
  2. It keeps things organized
  3. Internationalization (i18n)
  4. Multi-platform automation
From the outset, you might as well plan for i18n by including a switch for locale (not language). Language is "English."   Locale is "American English" and looks like "en_US" or "en-US" depending on who you ask.  Google it.  I like to model the strings exactly as they are, including capitalization and punctuation:

This works well for automating small to medium apps that themselves maximize code reuse.  But for big (or legacy) applications it may not be the best approach due to non-standardization, your mileage may very.

And for multi-platform automation... (there is a lot more work involved, but this is part of the deal...)

Ok I'm going to be honest right now. I wrote the part above tonight and this next part 2 years ago and I don't even know what language this is. The filenames are .cs but I don't even know C# well enough to know if you can define methods like this, I might have just used .cs because gist colorization was good.  Hmm, now I'm thinking it's just pseudocode.

Anyway, the last thing I'll say about externalizing strings is about dynamic strings.
Here's option 1, strings live in the data file and you compose them in the page object class:

This is fine, but will require everyone who uses those strings to do all that string concatenation themselves, which is code duplication. It will also become a problem when the translation is weird. Maybe in American English we'd say "Order number 404 is on its way!" but in Canadian English they'd say "404 is the number of the order comin' at ya, eh!" If this were the case, our stringA + orderID + stringB wouldn't work, or would be wonky. Let's just write methods to generate those strings instead, and notice how it's used in the page class:

And you could even get really crazy and do kind of a mix, which lets you have both the individual strings as well as the pre-composed dynamic strings. It just depends on what you need for your situation:

Automation Strategy: Keep Going With Page Objects (Don't Stop at Locators)

Stopping at the Simplest Implementation of POM

I don't love the Page-Object Model (POM).  Actually, I just don't love the way many teams implement it.  When they stop at giving their page elements locators, they end up with elements that look like


Reusing Page Objects in Other Page Objects

It's these last few that get me. Any time I see redundant code it makes me cringe. Let's level-up:


This can probably only be achieved if you've wrapped your Selenium tool's WebElement and WebDriver class. Hopefully you did this. It will take a little cleverness and adding some constructors, but it should be doable.

Dynamic Elements Using Methods, Not Variables

I find myself kicking the can down the road, or pushing the work down to a lower level like this all the time. "Make someone else do the work" I say. If I'm in the middle of writing a method and I think for one second that what I'm about to write could be used somewhere else, I try to encapsulate that work in a method. Better yet, I imagine what I'd pass into that method, what it would return to me, and continue writing my current method as if that helper method exists and keep going. Later on I'll write the helper methods into existence. 

In this case, while we're writing createAndViewOrder(), we don't want to worry about how we find the orderConfirmation element, we just want to assume it works and use it, so let's move it out of the method.  Before:


After:


Does this buy you a lot right now? Not really. But
  1. it might buy you something down the road during a refactor
  2. it makes your functional method easier to read
  3. it literally takes 15 extra seconds

Summary

It goes to a frame of mind that is good to get into when writing automation code - avoid UI specifics in functional methods, because you're likely to re-use that UI element again. There's a LOT of patterns when writing automation code from how your tests are constructed to how you find page objects to how you interact with the app, so you really have to be on guard and work against code duplication, this is just one of the ways.