Thursday, February 20, 2014

Selenium and reusable scenarios

In my last post I've shown you how to structure your code describing the application under test so that it allows for easier construction of more readable integration tests. Today I'll try to complement that with the concept of reusable blocks of tests - scenarios.

Let me give you an example of a scenario that everyone can relate to: logging in to an application. Let's consider what does it actually take to perform this task:

  • navigation to the login page
  • entering proper credentials
  • clicking the "Login" button

Doing that in every test every time will soon make the tests unmaintainable. For example changing username and/or password or adding language to the login page when you do this 200 times is just going to be a huge pain in the neck.

Scenario concept to the rescue.

Let's imagine our base Page object contains this kind of method:

public <I extends Page, O extends Page> O run(Scenario<I, O> scenario) {
    return scenario.run(this);
}

Having defined this let's see the Scenario interface:

public interface Scenario<I extends Page, O extends Page> {
    O run(I entry);
}

The I generic parameter is the type of the page that you'd like to start the scenario on. The O generic parameter is the resulting page the scenario ends on.

Now the definition of the login scenario from the last post would look something like this (extended with language selector):

public class LoginScenario implements Scenario<LoginPage, HomePage> {
    private final String username;
    private final String password;
    private String language = "english";

    public LoginScenario(String username, String password) {
        this.username = username;
        this.password = password;
    }

    @Override
    public HomePage run(LoginPage entry) {
        return entry
            .assertHasProperUsernameLabel()
            .assertHasProperPasswordLabel()
            .enterCredentials(username, password)

            .selectLanguage(language)
            .clickLogin()
            .assertPageTitleIs("Welcome to this cool application!");
    }


    public LoginScenario language(String language) {
        this.language = language;
        return this;
    }
}

There are 2 types of parameters for this scenario: required (username and password) and optional with default values (language). The usage of fluent interface in setting up the scenario plays very nicely with the overall style of the integration tests. The fancy-looking generic Scenario interface allows to chain the execution of this reusable block of code in a fluent interface of the actual test:

public class LoginTest {
    @Test
    public void will_login_and_logout() {
        new LoginPage("http://localhost/myapp")
            .run(new LoginScenario("johndoe", "secret").language("german"))
            .logout();
    }
}

I hope this will help you keep your integration tests code DRY and maintainable!

Happy coding!

Tuesday, January 28, 2014

Writing selenium tests with style!

I know it's been more that a while sine I last blogged but there's actually a good reason for it: I've been reviving a legacy application sentenced to death by decommission for the better part of the last year. With success I might add :) Now everybody wants a piece of it - but that's not what I wanted to talk about...

During this exercise beside obvious technology updates like switching to Tomcat from a God forsaken version of some other application server and making the project actually compile in a continuous integration environment, yata yata yata,  I've fiddled a little bit with selenium tests to make them at least do a sanity check while doing all those naughty changes. In doing so I've realized that actually Selenium can be a real beauty if used properly.

Without further due let's jump into the meat!

The problem

Selenium tests are integration tests and they are hard to write and maintain

The solution

This is actually not so hard and if you structure your tests properly, apply some rules and enforce a couple of design decisions then writing those tests is actually extremely easy and fun!

Let's see how we can turn the beast into a beauty!

Originally the recipe that you get when you arrive to Selenium's getting started page presents you with the basics (and for a good reason): create the driver, navigate to a page, search for an element, interrogate the element for some information. This works really nicely if all you want to do is automate Google's search engine and nothing else.

If you want to build some structure around the pages you test the Page Object design pattern comes to the rescue. Unfortunately the original documentation fails to mention the @FindBy annotation which is crucial for making the code look good and perform as expected.

Let's take a look at the following: we have an application (a hello-world style one) that presents one heading on the page with the text "Hello, world!". To describe the page using the @FindBy annotation simply declare the field as a WebElement and annotate it with @FindBy(id = "greeting")

public class HomePage {
    @FindBy(id = "greeting")
    public WebElement greeting;
}

Now to initialize such a page object instantiate it as you'd normally do

HomePage page = new HomePage();

and later on use the PageFactory.initElements(driver, page) method to initialize a set of proxies to elements. The "proxies" element is crucial: they don't need to appear on the page up front and you get all the usual stuff like waiting for them to load absolutely for free! It's like magic - only better :)

The Problem

Applications have structure and I need to repeat lots of elements

The Solution

Applications have structure. Their individual pages are not disconnected from each other, each presenting a totally different content. Usually pages are contained in some form of layout that'll act as a common experience for the user. Luckily for us we have decided to describe pages as classes and classes can do ... inheritance!

Let's say the HomePage is of some system that has a layout with logout button for the users to take a leave if they get bored using it.

public abstract class ApplicationPage {
  @FindBy(id = "logout")
}

Next we declare our HomePage as before, only denoting the application structure using inheritance:

public class HomePage extends ApplicationPage {
    @FindBy(id = "greeting")
    public WebElement greeting;
}

And you instantiate it as usual:

HomePage page = new HomePage();
PageFactory.initElements(driver, page);

This way we're expressing an "is-a" relation between the layout page and the actual page: The home page is an application page. In Java 8 we'll be able to put some of the stuff directly into interfaces and have even more capability to construct pages from functional bits and pieces. Until now for most cases this is more than enough.

The Problem

My integration tests are unreadable because of all the fiddling with Selenium.

The Solution

This is actually a huge problem, not only with Selenium or any other integration/unit tests. At least in Unit tests we have the universal layout ("given/when/then" or if you're more a Microsoft type guy then it'd be "arrange/act/assert"). But what about integration tests? They are expected to have assertions in the middle, they are expected to manipulate our application in many ways and verify the state in the middle because that's what the user would do!

No worry! There's this perfect little design pattern called fluent interface that we can easily employ to nicely structure our tests and to have cool, reusable place for everything! Let's start with the description of the test we're about to write:

- the user navigates to the login page
- verify that the login prompt actually says "Username" and "Password"
- upon entering the proper credentials and clicking "Login" the user lands on the home page
- verify that the login succeeded by checking the page's header or some other element

I suggest you take the description and follow it to the letter using fluent interface which might look something like this:

public class LoginTest {
    @Test
    public void will_login_properly() {
        new LoginPage("http://localhost/myapp")
            .assertHasProperUsernameLabel()
            .assertHasProperPasswordLabel()
            .enterCredentials("johndoe", "secret")
            .clickLogin()
            .assertPageTitleIs("Welcome to this cool application!");
    }
}

As you can see I've deliberately shifted all the specifics to the page objects, including navigation which is especially neat if you're working in an IDE and if you already have a set of page objects to work with. That way the IDE will actually tell you what you can do after you have "executed" a particular action (clickLogin for example).

Constructing such page objects isn't anything extremely sophisticated. Let's take a look at the LoginPage:

public class LoginPage {
    private final WebDriver driver;

    @FindBy(id = "username")
    WebElement username;

    @FindBy(id = "username-label")
    WebElement usernameLabel;

    @FindBy(id = "password")

    WebElement password;

    @FindBy(id = "password-label")
    WebElement passwordLabel;

    @FindBy(id = "login-button")
    WebElement loginButton;

    public LoginPage(WebDriver driver, String url) {
        this.driver = driver;
        if (url != null && url.length() > 0) {
            driver.navigate().to(url);
        }
    }

    public LoginPage assertHasProperUsernameLabel() {
        Assert.assertEquals("Username:", usernameLabel.getText());
        return this;
    }

    public LoginPage assertHasProperPasswordLabel() { ... }


    public LoginPage enterCredentials(...) { ... }

    public HomePage clickLogin() {
        loginButton.click();
        return new HomePage(driver);
    }
}

As you can see the assertions are contextual and can be re-used at will in any test scenario. The same goes for any operation you'd normally run on pages like filling in the login form and clicking on the login button and you can reuse them as many times as you want. If you combine that with inheritance you'll get a extremely powerful way to describe your application in an object-oriented way. With the fluent interface you'll get a chance to exercise that model in a way that will not make your eyes bleed when you'll get back to the code to fix that one button's location that has moved and all of the sudden all integration tests are failing.

The Problem

My integration tests run every time I build the project using Maven and it takes to much time

The Solution

The solution lies in proper Maven project configuration. Maven has this nice idea of profiles. I'm using a profile called "integration-test" to actually run everything integration-test-related only if that profile is enabled.

It's quite a lot of XML as you can probably imagine so I've prepared an example project that demonstrates the configuration. The crucial part in all this is the configuration of surefire, failsafe and tomcat7 plugins. You can grab the example here. If you do a mvn clean install then no integration tests will get executed but with mvn clean install -Pintegration-test you'll see Tomcat starting for the duration of tests and all the Selenium tests executed against that it.

Epilogue

We've been using this way of writing unit tests with great success for more than a few months now. It works great if you have regular web applications (request/response) and adopting it to any dynamic pages isn't all that difficult mainly because the @FindBy annotation does such an amazing job of hiding the complexity of Selenium.

Happy integration testing!

Friday, October 4, 2013

The longest route

I found myself in need today to share my local installation with a co-worker so that he can test a massive memory optimization I made.

Unfortunately me working from office and a ton of infrastructure with firewalls and what not between us I was forced to come up with a kind of non-standard solution to make it work. Here's what I've done:

1. The application runs locally on Tomcat (using Maven's tomcat6 plugin so mvn tomcat6:run)
2. Local Apache installation forwards requests from port 81 to 8080
3. An ssh tunnel is made that forwards my local port 81 to a remote port 8080 on a machine in Amazon
4. That remote machine has haproxy installed with a certificate that forwards local ports 80 and 443 to 8080 effectively allowing the user to login to my local installation

To test this I've used another Linux machine at home to login to with X forwarding enabled to locally view a remote Google Chrome running and accessing my local installation.

How can you not love Linux for that?

Sunday, September 29, 2013

Removing executable bit from files

I don't want to bore you with the details but I've been looking for a nice solution for this problem for a very long time. Today I came across this post:

http://superuser.com/questions/234647/how-to-remove-executable-bit-recursively-from-files-not-directories

chmod -R -x+X *

And life's simple again :)

Thanks to Pabouk (whoever you are)! You made my day!

Friday, August 23, 2013

Why documentation doesn't matter?

Well, it's not really true, it does matter - just not post mortem. It does tell the developer what to do - sometimes even how to do it. It tells the tester how to test it. After the release it's no longer valid nor accurate for anyone to use. Wanna know why?

A simple case


Imagine for a second there's a new position you just stepped into. It's this glorious application that everyone keeps talking about on the 'net. You took a position in one of the teams because you were certain that this can't be wrong - people have been doing that for years so good established processes and a proper technical documentation will be accompanied by a proper introduction training - something to take the edge off from new hires.

Introductory shock


I mean, woooow!!! You just came to the office, suddenly you realize nobody knows who you are, nobody seems to care. People seem to be rushing for no apparent reason through the floors - it's nothing like the place you've envisioned. But that's ok, right? You came here to do the job so let's get that notebook and let's get this show on the road!

Still positive, still passionate, still a foreigner - but you feel this is your chance! In this company, in this corporation there are endless possibilities! Here you will shine!

The second disaster


Ok, so you finally know where you sit, you've got your shiny new notebook on your desk trying to figure out what you've been given, trying to get your familiar tools installed so that you can finally kick start that 'being a developer at ' dream come true.

First of all you don't get to install whatever you want on company's property. No no.. There are rules in place preventing you to utilize whatever power Today's software market has to offer (commercial or OpenSource) so you're kinda stuck with what your new employer has carefully selected for you. You keep telling yourself that it's just a matter of time, that chemistry doesn't happen over night and that you'll get used to it. Why wouldn't you?

Anyways,... There comes the big moment: you're getting the sources! Obviously it's all going to be professional, high quality, top-to-bottom properly designed and brilliantly described in a few pages of perfect documentation. Well.. Our heroes rarely live up to our expectations and my friend this ain't no different. Suddenly it looks like some soft-skill course is more important than getting things done (the famous 'complete this course by today midnight or you'll be terminated' email floating around). Product training is something nobody heard within this walls since the company grew from 7 enthusiast to 1300 employees most of which have better things to do than to sit here, with you and explain obvious things - that seems like a giant waste of time, right?

So you're a professional, right?


And so you've got the code, you've got all the tools you shall need, you have access to the password-protected Maven repository and by all means - let's get to hacking! Obviously we need to start that thing first on that shiny new piece of equipment to compensate the lack of documentation with a debugger session. But wait! What's that error during compilation? Does that look like a broken test? NO!!! It's the code in one of the core modules that a colleague from abroad committed a few moments ago - just forgot to add that new class to version control before and it just so happened it landed this way on your machine. No worries! This is a good version control system so people's email addresses are there to chase that person and to give 'em hell! And so you do:

Dear John,

I've tried compiling our solution a moment ago but it seems like the code you committed Today lacks one of the new classes. Can you please check what's missing? Thanks!

Best regards,
Francisco

One should thing that such an error like "code does not compile" would be spotted by some continuous integration environment where it's constantly tested to meed the high quality standards everyone talked about on the interview. It just so happens that the server CI is on went down 3 weeks ago and nobody noticed. Since everyone sat so far in the same room with nice flowers and A/C if John would commit the code with errors Sally would come over for a coffee and tell him to fix it. This time John is some 6000 miles away on another continent and just called it a day.

No worries - I'm a professional!


And so I'm capable of at least learning the inner workings of the system by going through that clear-cut set of unit and integration tests the project is obviously equipped with. So where are they?

The real life


As with any corporation there are rules: you don't moan, you be creative, think outside of the box, be proactive - and all that politely and politically correct. Following this path 2 weeks later you've got the application running, the database it talks to is across several VPN connections so it's ridiculously slow, most of the external systems don't respond since their security just isn't setup for your location (you being the new developer in the team abroad) and quite frankly you start to miss on the boss that told you you can go to hell because he's not going to be giving you anything you didn't earn with blood and sweat and tears.

So why doesn't documentation matter?


Plain and simple: if you think you have any form of documentation other than the application code itself you're mistaken. Anything talking about the code, telling you how things are is bullshit and does not mean a thing. Wanna know why? Because your compiler doesn't read wiki, it doesn't have access to your email account nor does he care at all about Word documents and Excel spreadsheets. All it's interested in is the funky looking wall of text in source files - that's the only specification, the only documentation, the only rule book that will tell you everything.

So for the love of God: KEEP IT CLEAN! You're gonna need it 'cause you're gonna read it over and over and over and over again. If your co-workers (co-located and those miles, miles away) don't understand the need for keeping things clean tell them it's important to you, that you don't want to work in manure.

And if they will not listen - to hell with them! Tell the world how things are so that the next person drown by the lucrative perspective of working in young and highly motivated team will stay away. Let other learn from your experience.

Post scriptum


If any of you felt this matches any of your previous experience I rush to say it's fiction (well at least some of it). The situation as a whole didn't happen at all (would have been a disaster and a case for a shrink afterwards :] ). It's just to underline the importance of clean code and the fact that the code is the ultimate documentation out there. Don't undermine it.

Thursday, June 27, 2013

It's been a while...

.. but I have not forgotten :) There was just not much going on in my life. A nice and quiet 6 months.

Recently I've been working on some health issues but nothing really serious. My doctor asked me to collect some statistics for heart parameters (pressure, pulse) for review. Since I hate dead-tree versions of pretty much everything I decided that it's the best time to renew my friendship with Ruby and Sinatra.


The challenge

The challenge for me was to write the app in about an hour, with possible extensions at a later time, but to start collecting evidence as soon as possible. Although I'm a huge Grails fan (like you didn't know that) I think it is just too damn heavy for simple data collection applications like the one I have had in mind - thus the obvious dilemma between PHP and Ruby/Sinatra. I really like the embedded nature of PHP but to write anything in a sane way one needs to employ frameworks like CakePHP and the like. I hate that. I wanted to have as minimalistic as possible, with everything just working in a matter of minutes.


My sweet Ruby, Dear Sinatra,

You all know what Sinatra in the Ruby space is, right? If you don't - just go to http://www.sinatrarb.com and find out. Here's what I did with it:

1. All in one construct with everything in one place (configuration, controller, views, css, javascript)
2. All of it takes 210 lines of code (63 Ruby, 143 Erubis and a few while lines for convinience)
3. Deployed to Heroku in a matter of minutes
4. Using MySQL backend because it was just convenient.

Heroku provided the infrastructure for free (the one web worker that's always for free), a free database and Git repository. With all that in place I was ready with the first usable version in about 30 minutes (that's how quick Ruby and Sinatra and Heroku are).


Iterate iterate iterate

As with everything in software development there are going to be bugs, there are going to be new requirements and there are going to be "things that could be done better". One of them was for me to be able to input new measurements using my phone's web browser (the simplest thing that could possibly work). And it worked right out of the box but the default font size is just so small on my S3 that I've had to go through a number of zoom-ins to make out what's in there. Every navigation to another page returned me to that ugly small fonts. Changing the font size to like 40 did the trick :)

Then there was the matter of inputting numbers into the edit boxes. See, the default keyboard is an alpha-numeric-on-request type of keyboard which means that to enter numbers you have to either long-tap on the screen every number you'd like to enter or to switch to the numeric-special-characters-page on every input box which was just plain ugly. Changing the input types to "number" did the trick. Fortunately I'm not using Firefox on my Mobile where this type of inputs are just plain text inputs (as per some sophisticated page that knows all about browser compatibility).

And there was light! I have had enough measurements to actually display them on a chart. I wanted it to be an interactive graph, obviously, so my first choice was Google Charts. Even though beautiful and easy to use almost out of the box I felt kind of cheated because it used Flash underneath the skin. That obviously didn't work on S3 so I needed some alternative. Dygraph to the rescure (with GViz interface) and in a matter of 10 minutes (including research) I've had my charts up for display.


A small snag

As it turns out MySQL by default doesn't understand time the way it's supposed to. It just knows that 13:21:43 is almost half past one but with no particular interest for time zone. An obvious way to overcome this issue was to set all times to UTC and convert them when interacting with the user. Piece of cake. I'd like to point out that the author of mysql2 gem could finally extend the thing to allow something else than 'utc' or 'local' for the automated conversion and life whould be a lot simpler.


Closure

I always believe that software should be written according to high standards, with proper code coverage, proper planning, agile mindset and clean code in the end. Even though I tend to not do TDD in the case of such simple project such as this I still think it makes sense in other cases. I know I shouldn't talk like this since I'm the "Mr TDD" among friends but guys let's face it: throw-away software happens all the time and there's no reason to follow the routine blindly even in the case of spikes. Production code you write for someone else - TDD all the way; spikes no.

I'll probably use the app for another week or two, maybe even for a couple of months, who knows. I just hope it'll give my doctor enough information to get me back on track.

Wednesday, December 26, 2012

Did you noticed Spring Loaded is here?

Well... I didn't! Not for almost a whole month! And it's the best damn thing ever!

Here's how you use it:
MAVEN_OPTS="-javaagent:/path/to/springloaded.jar -noverify" mvn tomcat:run
Next you open Eclipse, do changes, reload pages - all changes occur immediately! Sure you can't add new Spring artifacts (which is odd), change class hierarchy (no surprise there) but you can add/remove methods which is more that what you had before.

Well - it isn't JRebel - but it is good enough!

Have fun!