Google Analytics

Search

To search for specific articles you can use advanced Google features. Go to www.google.com and enter "site:darrellgrainger.blogspot.com" before your search terms, e.g.

site:darrellgrainger.blogspot.com CSS selectors

will search for "CSS selectors" but only on my site.


Monday, March 1, 2010

How to reduce code duplication, part 2

In a previous entry I blogged about how to reduce code duplication. My solution is using libraries.

Let's say I have a web application for writing blogs. Okay, I'm actually going to use the Blogger software as my example. First thing I'm going to do is break the application down into manageable parts. Across the top is:
  • Posting
  • Settings
  • Layout
  • Monetize
  • View Blog
Each one will take me to a new page. I typically automate with Selenium using Java. So I might create one class for each of these areas. However, when I select Posting I see it has the following subsections:
  • New Post
  • Edit Posts
  • Edit Pages
  • Comment Moderation
So I might want to create the following packages:
  • com.google.blogger.posting
  • com.google.blogger.settings
  • com.google.blogger.layout
  • com.google.blogger.monetize
  • com.google.blogger.viewblog
Then within the com.google.blogger.posting package I'm going to create the following classes:
  • NewPost
  • EditPosts
  • EditPages
  • CommentModeration
Next I'm going to focus on one class at a time. Additionally, I'm going to create Java classes to represent the data on the application. For example, in the Posting section, on the New Post page I have the following:
  • Title
  • Link
  • HTML (the HTML text in a text editor)
  • Labels
  • Post Options
    • Reader comments
    • Backlinks
    • Post date and time
    • Edit HTML Line Breaks
    • Compose Settings
All this is data for the page so I'd create:

class NewPostForm {
        String title;
        String link;
        String htmlBody;
        String labels;
        PostOptions postOptions;
    }

    class PostOptions {
        boolean allowReaderComments;
        boolean allowBacklinks;
        boolean enterIsLineBreak;
        boolean htmlShownLiterally;
        boolean automaticPostDateTime;
        String scheduledAtPostDate;
        String scheduledAtPostTime;
    }

I would also add getters, setters and constructors to these classes, e.g.

public void setTitle(String title) {
        this.title = title;
    }

    public String getTitle() {
        return title;
    }

Now, if I create a library function to fill in a new post, I don't have to pass in a dozen or more parameters. I can create an instance of NewPostForm, populated it then pass it in to my library function. So back to the NewPost class. This is the library of routines to be used by my test suite:

class NewPost {
        Selenium selenium;

        public NewPost(Selenium selenium) {
            this.selenium = selenium;
        }

        public void gotoNewPost() {
            // if a link to New Post exists then
                // click the link
                // wait for the new post page to appear
                // this might be a page load so WaitForPageToLoad
                // or it might be AJAX so waitForCondition or
                // whatever the case may be
           // else
                // fail the test case
        }

        public void fillInNewPostForm(NewPostForm npf) {
            assertNotNull(npf);
            assertNewPostForm();
            if(npf.getTitle() != null) {
                // set the title input field to npf.getTitle()
            }
            // more of the same thing for each field in npf
        }

        public void assertNewPostForm() {
            assertTrue(selenium.isElementPresent(titleTextFieldLocator));
            assertTrue(selenium.isElementPresent(linkTextFieldLocator));
            // more of the same for all the inputs in the form
        }

        public void clickPublishPost() {
            // if a link for Publish post exists then
                // click the link
                // wait for the post to get published
            // else
                // fail the test case
        }
    }

I've left out things like the values for titleTextFieldLocator but that is easy enough. It would be something like:
String titleTextFieldLocator = "xpath=//input[@id='postingTitleField']";
As you build things up you will have a library of methods for the different 'pages' of the application. So if I wanted to test all the different ways of posting something I could have an instance of NewPost as a class variable for a test case then initialize the variable in the @Before method, just after the initialization of the selenium instance. For example:

class NewPostTestCases extends SeleneseTestCase {
    NewPost np;

    @Before
    public void setUp() throws Exception {
        super.setUp(); // initializes inherited selenium
        np = new NewPost(selenium);
    }

    @TestCase
    public void someTest() throws Exception {
        np.gotoNewPost();
        NewPostForm npf = new NewPostForm();
        npf.setTitle("How to reduce code duplication, part 2");
        npf.setHtmlBody("this is my new blog body");
        npf.setLabels("selenium, java, testing, development");
        np.fillInNewPostForm(npf);
        np.clickPublishPost();
        // do some sort of assertion to confirm the post went okay
    }

    @After
    public void tearDown() throws Exception {
        super.tearDown();
    }
}

And that is the basic idea. I'd used my IDE to generate all the getters/setters and constructors. I'd also let it suggest when I needed to throw an exception (I'd always throw the exception rather than try/catch and deal with it; junit was made assuming the exception gets thrown up to the TestRunner). And there are things like, put the datatypes in a different package, e.g. com.google.blogger.posting.datatypes for the NewPostForm datatype plus let the IDE tell you when to add the appropriate import statements.

No comments: