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.

Monday, September 17, 2018

Automation Strategy: Positively Do Negative Testing With should_fail()

Motivation

An old adage is "don't code for the exception," which is along the lines of Keep It Simple Stupid. This doesn't mean you don't expect and handle exceptions, bad data, etc., but that you should try to avoid extensive, complex coding for very rare cases.

I try to employ this in automation development just like any other software development, therefore when I write higher-level methods, I write them for the 95% case (the rule), and avoid the 5% case (the exception). For example, if I'm writing a login() method and after logging in, there's a popup that I will dismiss in 95% of my test cases, I will include the dismissal in the login() method. If I'm using a language that supports optional parameters, God forbid I have to work in a language that doesn't, maybe I'll take an optional parameter dismiss=True.

I'm actually quite proud of this should_fail() stuff.  I always aim to simplify the test scripts so they can be written by less technical people (hey, isn't that the point of cucumber/gherkin blah blah blah) and should_fail() aids in that as well as keeping your tests user-centric instead of UI-centric. This way, you can use your great Flow methods even though they feel like they're designed for positive-path testing.

For this example, we're logging in, then verifying we're on the Home page.  In this system, if the "passed" variable isn't true by the time the test is over, the test is considered to have failed.
Here's the positive test:


Now For Negative Testing

Everything's hunky dory, which now that I type it is a very strange phrase. For the simple negative test, we'll try to login and then verify that we're still on the login page... that we have not advanced to the homepage (we could also look for an error message such as "User not found"). We can't use login() because it will try to dismiss that modal after we login and throw an ElementNotFoundException or something like that, so we'll have to use clicks:


Using Exceptions the Simple Way

Well that doesn't look too bad, but it was very-UI centric, and as our automation gets more complex, maintainability of these negative tests is going to become a factor. If all our positive testing is done using Flows, we've thrown it out the window and reverted to using page objects. Ok, let's actually try it with login() and just catch the exception:


Using should_fail()

That works, but it's a lot of code. Let's hide and generalize that code in our should_fail() method:


Ok, there's definitely some syntax to learn there, but not too much. Let's see how should_fail() works:

One thing you might notice is the hardcoding of the error messages. Those would be in some kind of dataStrings.rb file or something. The other is that I'm throwing around exceptions here quite a bit. This is not necessarily something you can shove into a pre-existing system that doesn't use exceptions very much, or doesn't use custom exception messages. All my wrapper click() and find() and type_text() and everything else methods throw exceptions with clear, relevant messages in order to make debugging easier.

Summary and Tying in Flows

So again, the whole point of this is so you can write your negative tests using Flow methods. Let's say in the example below that returning an order is a multi-step process. If we were to write this negative test using the page objects only similar to my Test_Negative_Using_Clicks() above, we would be reverting to a long test that was very tied to the UI. So by using positive testing of negative tests, we can stay user-centric and high-level, even if your task is complex. In the example below we're going to try to return an non-returnable item:


The last thing I'll say is the error I'm looking for here is very high-level. Sometimes it's difficult or tedious to make your Flow methods do this, so maybe in this example, we would have tried to click a button as part of the return process but it wasn't there or was disabled, etc. In that case, we'd just have to catch the more generic "... does not exist" exception, and this does tie us to the UI. The usefulness of this system, like all automation, depends on the context of the system you're testing.

Saturday, September 15, 2018

Automation Strategy: Flow vs Page Objects Part 2 - Test Data Abstraction

In my first post on Flow vs Page Objects, it became clear that as we move up in layers of abstraction, our tests get shorter, but the data required to execute those tests remains the same. If we were just using page objects, we could do something simple such as...


Using Flows

...but again, we want to focus on the user, and if you asked the user what they do on this page, they wouldn't say "This is the page where I put in my first name, then last name, then street, then city, then state, then zip code," they would say "This is where I put in my address." So let's do that:


Maintenance

This is going to get overwhelming quickly, on top of which it's not maintainable. You might think the structure of a user's address doesn't change much, but maybe it's a voting website and they now require you to put in your state representative district number. Yeah, it can and will change. It needs to be maintainable. Now all our address tests need to be updated.

If you ask a user what their address is, they don't say "My street is 123 Main St. My city is Austin. My state is Texas," they say "My address is 123 Main St, Austin, Texas." So instead of passing in all the pieces of information that form an address, let's just pass in an address:


Maintenance Moves from Tests to the Data Source and Related Classes

The trick here of course is that our data source is going to have to get smarter. That's up to you, whether it's coming from a file, a DB, XML, JSON, who knows. The point is, when we add the state rep district number, the things that have to change are:

  1. The data itself.  And yes, if we have 1 data file for every test, then yes, we'll have to update a bunch of files.  This will have to be done in any system, and if you reuse the same data file in N tests, then it won't be a ton of files to update.
  2. The code to get the data from the file.  If well designed, this should be trivial, and if very well designed, this might not have to change at all.
  3. The classes or containers (such as Structs in C++) that hold the data. This should be trivial.
  4. The PO class needs a new object for the new UI element. Trivial.
  5. The Flow code that actually enters the data into the UI using that PO. Trivial.

Let's say our data file looks like this, then we add the district number to the end:

and our code for holding the data looks like this, then all we have to do is update that last line:

and our Page class (the Flow doesn't do much in this trivial case):

Payoff

So that's it. You're going to have to update the data and how the data gets input into the UI regardless, there's no magic bullet for those. But this way, your tests don't change. Again, this is a simple example. The more complex it gets or the bigger a change to the application's workflow, the more this extra abstraction will pay off.

The last thing I'll say is this: notice we're updating the address, but the flow isn't updateAddressFlow, it's just addressFlow. I encourage grouping related functionality together. In fact, where we're doing this now, for the smaller of our dozen or so applications, there's just a single flow for each application. How you break that up is a trade-off you make between having tons of code files and having tons of code in just a few files.

Friday, March 30, 2018

Email: Git best practice - break up Pull Requests into smallish commits

Hi All,
We have talked about the size of commits, the size of PRs, etc. and here is an example where I’m creating a small/medium sized feature which touches a lot of parts of the code
  • environment data
  • test data
  • helper methods
  • extending existing classes
  • the test method itself

My goal here is to show an example of breaking a PR up into small, related commits so that it’s easier to code review.  The whole feature has to go in 1 PR because all the parts of this PR depends on all the other parts, and together it’s several hundred lines of code.  But if you look at the individual commits, it’s easier to see what I was trying to do.  I did an ok job with this but surely could have done better, especially the last commit, which is pretty big.

An important thing here is that just because you have changed lot of files or even if you have changed several parts of a single file, you do not have to commit everything you have changed.  Sourcetree makes it easy to “stage” files or parts of a file which will get committed when you call commit (for more on staging, see here).  This way, even if you have a lot of changes, you can still make commits that group related pieces of code together to make life easier for the code reviewer – and hopefully for all of us since the reviewer is more likely to catch any issues with your code.


Saturday, January 20, 2018

Automation Strategy: Flow vs Page Objects Part 1 - or - If You Think Page Objects are Enough, Think Harder

Motivation

The Page Object Model (POM) has been the de facto standard in UI automation using Selenium for years now.  Here* is a typical primer on the subject.  This primer does a perfectly reasonable job of showing how people normally use POM, and it also clearly shows the reason that using POM the way I've seen a lot of people use it is insufficient for writing maintainable tests.

The problem is illuminated in the author's 5th reason for using POM: "Any change in UI can easily be implemented, updated and maintained into the Page Objects and Classes."  If you've done this for a while, you'll notice what is not said here - that a change in business process or higher-level user interaction with the site cannot be easily implemented in our automation.  For that type of change, if we were to only use POM, we'd have to update all of the related tests. That is to say, this style of POM is sufficient for encapsulation and maintainability of UI elements and structure, but we need something higher-level to encapsulate and maintain business processes.

Typical POM

As a trivial example, let's say you have this PO class and test:

You're going to have hundreds of other tests that contain this same login code of course.  What happens when the id of the username field changes?  With POM, you're golden.  You just update the username element on your LoginPage class.  But what happens when there's a new step in logging in?  Now all of your tests have to change to look like:

As an automation developer, you can be certain that your site will change - and not just in rearrangement or renaming elements on a page. Some people will say "Hey, I've got a method called login() in my LoginPage class, so that's all I'll have to update." Well kudos, you're a big step closer, let's check it out. We have the login() method to our LoginPage class, so all we do is add the element and the line in the login() method. While we're at it, we'll put the goto() in there. That way our test will be super clean and maintainable:

You will be good to go for this trivial example. But what about when your company decides to change a business process in your app? Let's say you want to add a step in a checkout process such as buying a couch. In the past, you had the code below... you were smart so you even wrapped clicking submitButton with a method, not wanting to mix the use of PO methods like loginPage.login() and use of the PO elements themselves like loginPage.submitButton.click():


The Issue...

The problem is that we're still modeling the interaction with the UI - we're focused on the website. What we should be modeling is what the user wants to do. The user doesn't want to select from a dropdown on pageA and click some buttons on pageB, they're trying to buy a couch, and we're not encapsulating that anywhere, except arguably in the test. That's too late, because we're going to have dozens of really similar tests where the user just wants to do a tiny thing different, such as choosing express shipping vs. regular shipping, so all that test code is going to be duplicated, and we're not going to be DRY at all.

In this example our app changed, and now we have an "assembly and delivery" step. All our tests have to change again:


Flow classes

The Flow class is intended to do this encapsulation and abstraction for us.  It models what the user is trying to accomplish (user stories?) when they're using your application.  Let's check it out:


And now your tests can model the use cases, and they simply looks like:

So maybe the title of this post shouldn't be Flow vs Page Objects, because both layers are useful and work together. The POs encapsulate the UI, and the Flow encapsulates the business rules. Then the tests simply encapsulate the use cases. Honestly, if your app is small enough, like a mobile app, you can do both in the Flow class. I've done this successfully, but a UI refactor probably would have cost me more than if I'd done a proper PO layer.

Looking at the test case, you probably see the potential problems:
  1. The more work we make our methods do, the more data we have to pass in to let them know how to do it. For this, we'll have to start consolidating data elements into their logical groups. I'll talk about that in the next post here.
  2. Using flows works great for positive tests, but what about negative tests?  I'll talk about that in this post.

Continue...

The next post in this series is Automation Strategy: Flow vs Page Objects Part 2



*In case the site I referenced is deleted, here is a webArchive of it

Automation Strategy: Cucumber for Testing - You're (Very Possibly) Doing it Wrong

Potential Issues

The first time I worked on test automation was around 2006, and when I started seeing people using Cucumber I didn't get it.  I always thought there were a lot of problems with thinking that a human-readable English DSL (domain-specific language) was going to solve your problems.  The most immediate one was that English doesn't compile.  Enter Step Definitions, which take a lot of time to write, translating the English to the method calls in your code.  In addition, having everyone agree on the exact syntax of the Gherkin seemed like a huge challenge.  For the simplest case of being logged in, is it "Given I am logged in" or "Given I'm already logged in" or "Given I have logged in?"  All of this just to have tests written in English... no way.

Flows

I have been using Flows in addition to or instead of Page Objects in my frameworks for years, and I wanted to show someone a side-by-side comparison of my Flow methods calls vs Gherkin.  Here's the example from a test a few companies ago.  This (flow code) is actual code from the test.

GherkinUsing Flows
Given a user is logged inflow.login(user01)
When a user adds a pen to the cartflow.add_to_cart(pen)
And the user checks outcheckout_total = flow.checkout()
Then the receipt is viewableflow.verify_receipt_exists()
And the cart is emptyflow.verify_cart_is_empty()
And the receipt total equals the order totalflow.verify_receipt_total_equals(checkout_total)

The beauty of having this flow class is that with a decent IDE, you type "flow." and you'll be given a list of all the methods in the class, which is easier than remembering the exact Gherkin phrase.

I had been having these thoughts for a while.  I have never seen people using Cucumber but always wanted to.  Some people I know and respect say it's useful or that they've seen it done.  Then I saw this post* by Aslak Hellesøy, the author of Cucumber, and was so happy to have my beliefs confirmed.  It's a quick read, but basically he says if you're just using Cucumber for automated testing, you're missing the point.

Cucumber is a BDD tool, and the automated tests are a byproduct of the process.  If the testing team is the ones "doing Cucumber" you likely need to reevaluate your situation.  Using Cucumber correctly involves buy-in from product and development as well as QA.

For more on Flow vs Page Object, check our my post here.

*web archive of Aslak's post