Functional Testing – Reducing Code Duplication

Let’s continue our functional testing examples by adding a few more tests. This will demonstrate why we need to start thinking about the structure of our tests and why we should be developing a framework that will allow us to write less code.

Let’s add one more test. We already have a test that verifies Google’s homepage title. We’ll do the same for Yahoo’s homepage. By the way, maybe it goes without saying, but normally you would be verifying your own site, during development and CI.

var expect = require('chai').expect;

describe('Example Functional Test', function() {
    it('should verify the title of Google', function() {
        var webdriverio = require('webdriverio');
        var options = {
            desiredCapabilities: {
                browserName: 'firefox'
            }
        };

        return webdriverio
            .remote(options)
            .init()
            .url('http://www.google.com')
            .getTitle().then(function(title) {
                expect(title).to.equal('Google');
            })
            .end();
    });

    it('should verify the title of Yahoo', function() {
        var webdriverio = require('webdriverio');
        var options = {
            desiredCapabilities: {
                browserName: 'firefox'
            }
        };

        return webdriverio
            .remote(options)
            .init()
            .url('http://www.yahoo.com')
            .getTitle().then(function(title) {
                expect(title).to.equal('Yahoo');
            })
            .end();
    });
});

There’s an awful lot of repetition in here. Just to add a simple new test, similar to the one we already had, we had to add almost 20 lines of code. Copy pasting code around never scales well in a project and you’ll end up with a lot of technical debt to refactor.

Both tests are made up of four distinct parts:

  • initializing WebDriverIO with the specified options (which are hardcoded and inlined on both tests, they could be coming from a common place)
  • navigating to Google’s homepage with the url method
  • examining the page and performing assertions
  • terminating WebDriverIO (with the end method)

A small side note: other tests will often have also an extra step after navigating:

  • interacting with the page (e.g. clicking a button)

Back to our tests: duplication is only one of the problems. The other problem is performance. Initializing the WebDriverIO object is something we can just do once, we don’t have to do it over and over. The navigating part is also expensive because it depends on how fast the target web page will load. Tests should be grouped in such a way so that we don’t perform unnecessary web page loads. This is important especially in the context of a large set of tests run during CI. An unoptimized test suite will slow you down and this is a cost that accumulates over time.

To avoid duplication, we can use mocha’s before and after hooks. Our goal is also to avoid performance penalties, that’s why we have to try to avoid the beforeEach and afterEach hooks; we need to use the before and after hooks that run only once before all tests.

var expect = require('chai').expect;

describe('Example Functional Test', function() {
    var browser;

    before(function() {
        var webdriverio = require('webdriverio');
        var options = {
            desiredCapabilities: {
                browserName: 'firefox'
            }
        };

        browser = webdriverio
            .remote(options)
            .init();
        return browser;
    });

    it('should verify the title of Google', function() {
        return browser.url('http://www.google.com')
            .getTitle().then(function(title) {
                expect(title).to.equal('Google');
            });
    });

    it('should verify the title of Yahoo', function() {
        return browser.url('http://www.yahoo.com')
            .getTitle().then(function(title) {
                expect(title).to.equal('Yahoo');
            });
    });

    after(function() {
        return browser.end();
    });
});

We’ve added a before and after hook. The same basic rule of promises applies here as well: always return the promise. We also added a variable called browser in which we store the initialized WebDriverIO API. The before hook initializes the WebDriverIO API. The tests are now much shorter. The after hook just terminates the WebDriverIO session.

We can shorten this even further by making our first steps towards building our framework. The common plumbing of the before and after hooks can move into a separate library file and all our tests can reuse it. We’ll do that in a next post. Another thing we’ll examine is the usage of chai-as-promised plugin to make our assertions more fluent. Finally, we’ll start looking at more tests and selectors to get elements on the page.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s