Tuesday, January 3, 2017

Libraries and API design

Some time back, I ran across this: http://www.softwaretestinghelp.com/test-automation-frameworks-selenium-tutorial-20/

And I was taken aback at the description of the framework, as he was ignoring the parts of framework development that seemed to me to be the most important parts.

Consider, if we use just Selenium and write our tests to it, then everybody we hire who knows selenium (and our target language) will be able to write automated tests. We will probably set up page objects, and that's about the minimal set of things to know. Right?

Well, yes, but...

From my experience, many QA organizations would prefer to be able to graduate successful testers into at least some automation. This often means training less experienced testers to write automation.

And if your framework is more complex than the above (and most of them are), then there's going to be some kind of a learning curve, even for more experienced automators.

My job as an architect is to 'design and maintain the framework'. And how well I do that design part will directly influence how productive the people who use that framework will be.

At my first automation gig, there were 3 of us, and we all were pretty good programmers, so we just started banging out tests. We had a set of shared page objects, and wrote any other functions that helped us out as we needed them.

This meant that, at different times, we each wrote something to remove all non numeric characters from a string. Because we happened to need that, and because there was no orchestration of our work. No good standards for code sharing, no limitations on what we could create. Doh!

At one point, I even ran into a test I'd written a month earlier that implemented some function or other (I no longer remember what) that I'd just implemented over again because I'd forgotten that it existed. My own code!

This was when I learned about interface proliferation. The tendency of good programmers to try and reuse code had led to more interface endpoints than we could keep track of.

From then on, I started looking at test automation interface design as it's own discipline. And not only the design of the interfaces, but how I communicate about them as well. What documentation I create and where.

These two pages have strongly influenced my API designs:

http://martinfowler.com/bliki/HumaneInterface.html
http://martinfowler.com/bliki/MinimalInterface.html

Now, I try to create minimal interfaces, and the interfaces I do create require the bare minimum of data to do the most common action. So for instance, when I started in my current gig, they had created a function called 'does_control_exist(selector)' to wrap the fact that selenium has no exist function. They also had a 'wait_for_control_to_exist(selector, time)'. Two interfaces to determine existence. What if it was just .exist([time])? The [time] is in brackets because it's optional. Two calls compressed into one.

It's also the case that when I used our framework's click(), I'd often get 'control not found' if the code tried to click on something that existed more than once.  And there was nothing to tell me if a control was unique or not.

Now I have .click([time]), so a call might look like thingie.click(), which does the following:

since no timeout was provided, use 20 seconds
get the element for the selector
assert that the item exists
assert that the item is unique
assert that the item is visible
assert that the item is enabled
element.click()

And that happens each time we click on something. This is how most test automation tools worked before selenium.

So my page object may look something like this:

class PageHome(MyPageClass):

    complete_page_button = ButtonClass("css=[id='Next-Button']")

    def complete_page(self):

        if self. complete_page_button.exist() is False:
            self.invoke_page()
        self.complete_page_button.click()
        ...

So for exist, we now have one function instead of 2. For click, we've collapsed at least 6 actions into one function. This means my test automation engineers have fewer end points to remember, and for the most common use of an endpoint, no additional parameters are required. I can get new people up to speed faster, and we can debug failed tests more quickly because we get more useful feedback in the logs.

For me, this is what framework design is all about.