Chapter 7. Testing GWT Applications

This book shows you how to develop GWT applications. As testing has become an integral part of the software development process, we feel that a book about building GWT applications wouldn't be complete without a chapter on testing.

In this chapter, we'll first explore what testing actually is and what types of testing we can distinguish. Then we'll go into more detail on unit testing, introducing JUnit, the unit test tool that comes with GWT. Next, we'll focus on a tool for doing functional testing, Selenium. Then we'll end the chapter by discussing the support GWT provides for benchmarking code developed using GWT.

What Is Testing?

A great deal of misunderstanding surrounds testing. Many developers consider it to be something that should be done by a separate testing department. We consider testing to be an integral part of software development. But before discussing that in more detail, let's first take a closer look at testing and what it constitutes.

Testing can be roughly divided into three main types:

  • Unit testing—tests the minimal software component, or module. Each unit (basic component) of the software is tested to verify that the detailed design for the unit has been correctly implemented. In an object-oriented environment, this is usually at the class level, and the minimal unit tests include the constructors and destructors.

  • Functional testing—exposes defects in the interfaces and interaction between users and the system. On this level, we no longer test single units of work, but focus on functionality and verify that it actually works as expected.

  • System/integration testing—tests a completely integrated system to verify that it meets its requirements.

The third type of testing is typically done by a separate QA department or at least dedicated testers. However, the first two, unit and integration testing, are considered an integral part of software development, and should therefore be done by the developers themselves. In particular, unit testing is valued highly, as it provides developers with more confidence over the correct functioning of their code. Writing unit tests also encourages programmers to write code in small chunks that can be tested independently. There is a large amount of literature on testing, and why it's important to consider it part of the software development process. More information about unit testing using JUnit can be found in Test-Driven Development: A J2EE Example by Russell Gold, Thomas Hammell, and Tom Snyder (Apress, 2004).

As this book is aimed at software developers who want to build robust GWT applications, we should discuss the types of testing most appropriate to them. Therefore, we'll focus on unit and functional testing in the remainder of this chapter. System and integration testing, important as they are, fall outside the scope of this book.

Unit Testing

As mentioned earlier, unit testing is the procedure of verifying that individual units of source code work properly, where a unit is the smallest testable part of an application. In object-oriented programming, the smallest unit consists of a method, which in turn belongs to a class. So in the context of developing GWT applications, unit testing is about testing individual methods of specific classes.

Unit testing is usually done by writing several test cases and combining them into test suites. A test case is a test instance where one set of conditions or variables will be used to determine whether a requirement or use case of a unit is (partially) satisfied. It usually takes several test cases, at least, to determine whether a requirement is fully satisfied. An important aspect of unit testing is the need to test each unit in isolation. To ensure this, ideally each test should be independent of other units.

Basic Unit Testing

In order to get a more detailed picture of unit testing, assume a BasicCalculator class (see Listing 7-1) that offers the four basic arithmetic operations add, subtract, multiply, and divide.

Example 7-1. The BasicCalculator Class

public class BasicCalculator {

  public int add(int num1, int num2) {
    return num1 + num2;
  }
  public int subtract(int num1, int num2) {
    return num1 - num2;
  }
  public int multiply(int num1, int num2) {
    return num1 * num2;
  }

  public int divide(int num1, int num2) {
    return num1 / num2;
  }
}

Now, let's start by writing one or more test cases for this BasicCalculator class. For the sake of this example, the divide method is the most interesting, so let's focus on writing a test case for that method.

Example 7-2. The BasicCalculatorTests Class Skeleton

public class BasicCalculatorTests extends TestCase {
  private BasicCalculator calculator;
  protected void setUp() throws Exception {
    calculator = new BasicCalculator();
  }
  // actual tests go here
}

The code in Listing 7-2 illustrates a basic test case skeleton for the BasicCalculator class's divide method. Actually, the BasicCalculatorTests class will encompass multiple test cases, as it will test several scenarios with different variables and checks, hence the plural name Tests. Important to note in Listing 7-2 is that the class extends from the TestCase base class provided by JUnit (see sidebar "JUNIT") and that its setup method is overridden in order to provide all test case instances with an already-instantiated BasicCalculator instance.

So let's start by writing a test to establish whether the divide method works as we expect it to work. In order to do this, we need to write a test case; in JUnit terms, this translates to a method in the TestCase class named testXXX(). Unless configured otherwise, by default JUnit and most build tools such as Ant and Maven will run all methods whose names start with test. In this case, we want to write a method to test the divide method of the BasicCalculator, so we add a method named testDivide() to the BasicCalculatorTests class (see Listing 7-3).

Example 7-3. The testDivide() Method Implementation

public void testDivide () {

  int result = calculator.divide(10, 1);
  assertEquals(10, result);
  result = calculator.divide(10, 2);
  assertEquals(5, result);
  result = calculator.divide(10, 3);
  assertEquals(3, result);
  ...
  result = calculator.divide(10, 10);
  assertEquals(1, result);
  result = calculator.divide(10, 11);
  assertEquals(0, result);
}

As you can see, the implementation of the test case consists of multiple calls to the already-instantiated calculator instance. The divide method is called several times, each time with arguments to the method that are considered valid. Of course, calling the method is one thing, but making sure that the result of the calculation is as expected is just as important. Luckily, the TestCase base class, through the Assert base class, provides several convenience methods for doing just that. In this case, we use the assertEquals method to test whether the actual result is the same as the result we expected. The most important other convenience methods for asserting the results of method calls are displayed in Table 7-1. Note that all methods listed come in two variants, one basic variant and one where the user can customize the detail message of the AssertionFailedError that's thrown if the assertion fails.

Table 7-1. Convenient Assertion Methods Provided by JUnit's Assert Class

Method name

Description

assertEquals

Asserts that two values are equal. Several convenience methods are provided for different types of objects and primitives, such as int, double, String, Object, and many more.

assertTrue

Asserts that a specific condition is true.

assertFalse

Asserts that a specific condition is false.

assertNull

Asserts that an object is null.

assertNotNull

Asserts that an object is not null.

assertSame

Asserts that the two arguments refer to the same object instance.

assertNotSame

Asserts that the two arguments do not refer to the same object instance.

So far we've created a test case to verify that the calculator behaves as expected when provided with valid arguments. However, another important part of unit testing is to verify the expected behavior in case of arguments that might cause problems, such as null arguments or, in the case of the divide method, providing zero as the second argument. As you probablyknow, division by zero isn't allowed. Therefore, let's add a method to verify the expected behavior when we call the divide method with 0 as a second argument (see Listing 7-4).

Example 7-4. The testDivideByZero Method Implementation

public void testDivideByZero() {
  try {
    calculator.divide(10, 0);
    fail("An arithmetic exception was expected");
  } catch (ArithmeticException e) {
    // do nothing, was expected
  }
}

As you can see, this is a bit more complicated. We call the divide method with zero as the second argument. In this test case, we expect an exception to be thrown by the calculator. So we surround the method call with a try-catch block and ignore the expected exception. But please note the fail() statement that's called if, and only if, the exception is not thrown. The fail method is another convenience method provided by the Assert base class and allows the developer of test cases to programmatically make a test fail.

GWT JUnit Integration

In the previous section, you saw how to unit-test specific features that are part of our application. However, as we'll see in this section, it gets more complicated when we want to apply this technique to some of GWT's client code, such as widgets. Obviously it's just as important to test our logic and server-side classes as it is to test the UI code of our GWT applications.

Let's assume the basic widget shown in Listing 7-5.

Example 7-5. The HelloWorldLabel Class

public class HelloWorldLabel extends Label {
  public HelloWorldLabel() {
    super("Hello World!");
  }
}

Let's assume we want to write a unit test to verify that this widget really behaves the way we expect it to. In order to do this, we might write the test case in Listing 7-6, similar to the one we wrote in Listings 7-2 through 7-4.

Example 7-6. The Incorrect HelloWorldLabelTests Class

public class HelloWorldLabelTests extends TestCase {
  public void testText() {
    HelloWorldLabel widget = new HelloWorldLabel();
    assertEquals("Hello World!", widget.getText());
  }
}

However, if we run the test in Listing 7-6, the test case will fail with output similar to the following:

java.lang.ExceptionInInitializerError
    at ...HelloWorldLabelTests.setUp(HelloWorldLabelTests.java:10)
    ... 5 more
Caused by: java.lang.UnsupportedOperationException: ERROR: GWT.create() is only
usable in client code!  It cannot be called, for example, from server code.  If
you are running a unit test, check that your test case extends GWTTestCase and
that GWT.create() is not called from within an initializer, constructor, or
setUp()/tearDown().
    at com.google.gwt.core.client.GWT.create(GWT.java:68)
    at com.google.gwt.user.client.ui.UIObject.<clinit>(UIObject.java:126)
    ... 19 more

As you can see from the output, the creation of the widget in the setup method fails. This is because the Label, or more specifically one of its superclasses UIObject, relies on the GWT.create() method. This method is used by GWT to accomplish deferred binding (discussed in more detail in Chapter 8), but only works in real client-side code. This effectively means that you can't unit test client-side GWT application code by extending the standard JUnit TestCase class. Note that this only applies to code that directly or indirectly depends on GWT-specific code such as GWT.create().

However, as you can see from the output of the previously failing test case, there's a workaround for this. GWT provides its own base class for test cases that verify things such as widgets. This base class, GWTTestCase, itself extends from the JUnit TestCase class, but what it does internally is start its own test runner, which starts an (invisible) hosted browser to run the widget. However, in order to do this, we need to tell the test case which module to load to test the widget in. So to test widget code, we need have a separate module just for testing. Note that you only need one module that's used by multiple client code tests.

In order to run our HelloWorldLabelTests, we first need to define a module to be run by the test case (see Listing 7-7).

Example 7-7. The DefaultModule.gwt.xml Module Definition

<module>
  <inherits name="com.google.gwt.user.User"/>
</module>

And of course we need to rewrite the previously introduced test case, in this case to extend the GWTTestCase base class (see Listing 7-8).

Example 7-8. The Rewritten and Correct HelloWorldLabelTests Class

public class HelloWorldLabelTests extends GWTTestCase {

  public String getModuleName() {
    return "com.apress.beginninggwt.ch07.DefaultModule";
}
  public void testText() {
    HelloWorldLabel widget = new HelloWorldLabel();
    assertEquals("Hello World!", widget.getText());
  }
}

Warning

Caution Make sure you put the test module descriptor in the parent package of the client package where the unit test and the code under test reside. If you get this wrong, the test will fail with an error indicating that the compiled test class can't be found.

It's important to get two things from the code in Listing 7-8. First, note that instead of extending directly from JUnit's TestCase base class, HelloWorldLabelTests extends from GWT's GWTTestCase base class. This leads to the second difference, namely the added implementation of the abstract method provided by the superclass, the getModuleName method. It's called by the GWT test runner to determine which module it needs to load. In this case, we instruct it to load the module specified in the file with the name DefaultModule.gwt.xml. This file is placed inside the corresponding package.

The junitCreator Utility Script Instead of creating the test and corresponding module manually, you can use the script that's provided by GWT. This script, junitCreator, is provided by the default distribution of GWT and is part of the package of scripts introduced in Chapter 3. The junitCreator script generates a test case as described previously, and also generates convenient scripts for running the test in both hosted and web mode.

Calling the script with no arguments will display the argument options. Note that because we've added the GWT installation directory to our PATH environment variable, we don't have to refer to the installation directory. Instead, we can just call the script directly:

>junitCreator
Google Web Toolkit 0.0.2415
JUnitCreator -junit pathToJUnitJar -module moduleName [-eclipse projectName]
 [-out dir] [-overwrite] [-ignore] className
where
  -junit      Specify the path to your junit.jar (required)
  -module     Specify the name of the GWT module to use (required)
  -eclipse    Creates a debug launch config for the named eclipse project
  -out        The directory to write output files into (defaults to current)
  -overwrite  Overwrite any existing files
  -ignore     Ignore any existing files; do not overwrite
and
  className   The fully-qualified name of the test class to create

As you can see, the script takes three mandatory arguments: the path to the JUnit jar, the module name, and the class name of the test. All other script arguments are optional, and the preceding code supplies some explanation on how to use them.

So let's run the script to generate a skeleton for the test comparable with what we created earlier. Please note that we use another class name for the test class in order to avoid a collision with the already-created test. Also note that we reuse the module that we've already created:

> junitCreator -junit libjunit-3.8.1.jar
 -eclipse HelloWorldLabel
 -module com.apress.beginninggwt.ch07.DefaultTest
 com.apress.beginninggwt.ch07.HelloWorldLabel2Tests
Created file testcomapresseginninggwtch07HelloWorldLabel2Tests.java
Created file HelloWorldLabel-hosted.launch
Created file HelloWorldLabel-web.launch
Created file HelloWorldLabel2Tests-hosted.cmd
Created file HelloWorldLabel2Tests-web.cmd

Please note that running the generated HelloWorldLabel2Tests-hosted.cmd script will result in the test being run as Java bytecode in a JVM. Running HelloWorldLabel2Tests-web.cmd will run the test as compiled JavaScript. The launch configurations do the same thing in Eclipse.

This section has shown that GWT provides support for testing your client-side UI code that you can't test due to dependence on the GWT.create() method. The next section will compare the default unit testing with the mechanism introduced in this section and will provide pointers on when to use which.

Comparing Basic and GWT Unit Testing

So far we've seen basic unit testing using JUnit as well as the unit testing support provided by GWT. It's important to know that the GWT unit testing starts a hosted-mode browser for each test case. Therefore, tests written using the unit-test support provided by GWT tend to run slowly when compared to basic unit testing using JUnit.

So as a very important rule of thumb, only use the unit-test support provided by GWT as a last resort. Also, because the startup time of the hosted mode is the biggest bottleneck, try to do as much testing as possible in one test method, without sacrificing too much in the way of test granularity. If you have a choice, always favor fast, basic unit testing over the slow alternative.

Functional Testing

As discussed earlier, functional testing is a way to test the functionality provided by a piece of software. Therefore, functional testing can be used to perform integration, system, and system integration testing. But as mentioned earlier, we consider system and integration testing to be outside of the scope of this book, so we'll focus solely on functional testing in this section.

The means of testing that we've discussed so far are useful when testing individual parts of an application. However, if we want to functionally test an application, we have to resort to other mechanisms. So we'll now look at Selenium, a more comprehensive tool that can be used for extensive functional testing.

An important aspect of functional testing and more specifically testing using Selenium is that it lets you test the application as part of a larger environment. In the case of GWT applications, it's important to test the developed application in different browsers to avoid running into problems later due to browser incompatibilities.

Introducing Selenium

Selenium is a test tool that allows you easily to functionally test your web applications. It's far from specific to GWT applications; it enables testing of all web applications. Selenium is open source and was originally developed by ThoughtWorks.

As mentioned in the introduction to this section, to correctly test a web application, we need to test it in a browser (actually multiple browsers). This is what Selenium allows us to do. Selenium works by letting you define test scripts that Selenium can run in a browser for you. Depending on your needs, you can choose different browsers and platforms to run your script on. You can even include these tests as part of your continuous build/integration environment. But before we go into these more advanced topics, let's first look at the different ways you can use Selenium. Selenium itself distinguishes four separate ways of using it:

  • Selenium IDE—a plug-in for the Firefox browser that allows you to record and run -Selenium tests from within the browser environment.

  • Selenium Core—this encompasses the core libraries to run the tests as part of your web applications.

  • Selenium Remote Control (RC)—a more advanced Selenium setup that requires more technical knowledge to set it up, but also allows you to perform more advanced tests because it allows you to write your tests in your preferred language (in our case, Java).

  • Selenium Grid—an advanced version of Selenium RC that allows you to speed up tests by running them in parallel on different machines. This setup is only interesting when you want to run code on different platforms as part of one test. However, the setup is similar to Selenium RC, so we aren't going to deal with it in this book. More information on how to use Selenium Grid can be found at the Selenium Grid web site: http://selenium-grid.openqa.org.

In order to provide you with the most gentle and pragmatic introduction to functional testing with Selenium, it's easiest to look at the Selenium IDE first.

Selenium IDE

As mentioned earlier, the Selenium IDE is a Firefox plug-in that allows developers to record and play back Selenium test scripts in the browser. The most important feature of the IDE is the ability to record test scripts easily and make them part of your functional testing setup. The main advantage of using this plug-in is that even nontechnical people can record test scripts. This allows a nontechnical user or customer to record test scripts and then hand them off to the developers to include them as part of the test suite. However, in the context of this book, starting off with the Selenium IDE provides a gentle introduction to using Selenium in general.

The Selenium IDE (as a separate window)

Figure 7-1. The Selenium IDE (as a separate window)

In order to start using the Selenium IDE, you first need to install it. We assume you already have Firefox installed; if not, go to the Firefox download site to get it: http://getfirefox.com. First, go to the Selenium IDE download page (http://selenium-ide.openqa.org/download.jsp) to download the latest version of the IDE. Allow the web site to install the necessary plug-in if your security settings dictate. You may need to restart Firefox to finish the installation.

Once you have the plug-in installed, you can either use it as a separate window or as a sidebar, depending on your preference. In order to use a separate window to view the Selenium IDE, go to the Extra menu and select the Selenium IDE menu item (see Figure 7-1). To use it as a sidebar, go to View > Sidebar and check the Selenium IDE menu item. It will then show as a sidebar item (see Figure 7-2). In the remainder of this chapter, we'll use the sidebar, but there's really no difference in terms of usage.

As mentioned earlier, Selenium isn't limited to just GWT or Ajax applications. You can use Selenium (and therefore its IDE) to test any web application. So let's just take another web site that you might know: www.google.com. In this section, we'll pretend to be the QA department of Google that's responsible for functionally testing Google's search application. Let's start off by writing a simple test case using the Selenium IDE.

The Selenium IDE (as a sidebar)

Figure 7-2. The Selenium IDE (as a sidebar)

First, go to the Google home page and make sure the Now Recording button (the red, round button) is pressed/active. Now, let's type in a search query in order to test the search functionality. In line with the topic of this book, let's search for "gwt".

As you can see in Figure 7-3, the content panel (the right part) shows the normal Google search results page, while in the Selenium IDE sidebar, some items have been added to the Command list. This command list is visualized as a table with three columns:

  • Command—the action that was performed

  • Target—the target to apply the command on

  • Value—the value (if any) for the command

After issuing the "gwt" search query, you can see that the Selenium IDE has added three actions to the command table. Let's go over each of these commands one by one to introduce them individually.

The open Command

The open command is primarily used as the first command of every test case, but you can have several open commands in the same test case. As the name suggests, this command opens a specific URL, which you provide as the target argument. This can be either an absolute or a relative URL. In this case, it's a relative URL pointing to the root URL specified in the top of the Selenium IDE: http://www.google.com. Note that this command will force the IDE to wait until the page is completely loaded before continuing to issue the next command.

The Google result page and the first command in the IDE

Figure 7-3. The Google result page and the first command in the IDE

The type Command

The next command when issuing the Google search query is the type command, which as the name suggests types a certain value in a certain target. In this case, the value "gwt" is typed into an element that can be located using q. We'll discuss element locators later in this chapter, but in this case, the target is an element with the name q, which happens to be the main input field of the Google home page.

The clickAndWait Command

The last command that is issued is the clickAndWait command that, again as the name suggests, clicks on something and waits. In this case, it clicks on an element that can be located by the string "btnG", which happens to be the Google Search button on the Google home page. The "wait" part refers to the fact that the command will wait for the resulting page to load completely.

So far, we've seen how to issue some sample commands and record them using the -Selenium IDE, but this doesn't really constitute a test case. In order to test, we need to verify that the result of the commands is something we expected. Luckily, the Selenium IDE provides convenient support for this as well. In order to illustrate how this verification works, let's assume the following two test cases that might be interesting from a Google QA department perspective:

  1. We want to make sure that the first result page returned by Google actually contains a link to the official home page of the Google Web Toolkit project.

  2. We want to check that the query that was issued by the user is reflected in the result page, and more specifically that it's prefilled in the query input field of the result page.

These two test cases are just simple examples of things a QA department wants to test, but they provide a convenient introduction to the assertion support offered by Selenium and more specifically the Selenium IDE.

The verifyTextPresent Assertion

In order to check that the official home page of the GWT project is listed when querying Google for the term "gwt", we want to verify that the returned result page contains a link to the specific page http://code.google.com/webtoolkit. Luckily, the Selenium IDE provides easy support for this. The only thing we have to do is select the text we want to assert and bring up the context-sensitive pop-up menu (see Figure 7-4).

The context-sensitive menu items provided by the Selenium IDE (1)

Figure 7-4. The context-sensitive menu items provided by the Selenium IDE (1)

In the context-sensitive menu, the bottom menu items are assertions and actions provided by the Selenium IDE. As you can see from the screenshot, one of the assertions is verifyTextPresent, with the selected text already added as an argument. If we select this menu item from the -pop-up menu, the assertion is added to the Selenium IDE in the command table. Note from Figure 7-5 that the assertion was added to the Selenium sidebar. Also note that the actual text to verify is added to the target column and not to the value column, which might be conceptually better.

That's all we need to do in order to add a simple assertion to our test case that verifies that a certain piece of text is present on the result page.

The verifyValue Assertion

Now, let's do the same thing to verify that the search term is correctly prefilled in the search query field of the result page. In this case, we don't just want to check whether the term is actually present in the page, but take it a little further. We want to check if the value of the input field is what we expect. Again, the Selenium IDE helps us a lot. By doing the same thing as previously, bringing up the context-sensitive pop-up menu, we can select the appropriate assertion (see Figure 7-5).

The context-sensitive menu items provided by the Selenium IDE (2)

Figure 7-5. The context-sensitive menu items provided by the Selenium IDE (2)

In this case we select the verifyValue item, which subsequently is also added to the Selenium sidebar. As you can see from Figure 7-6, the verifyValue assertion has two arguments, the target, in this case an element that can be located by q, and the value "gwt".

Now that we've created our simple test case, let's verify that it works. To do this, all we need to do is click the Play button (the first button containing a green arrow). This forces the IDE to jump out of recording mode and replay the script. As you can see if you look closely (it will probably happen very fast), the script performs all recorded actions: load the root page, type in the search query, and click the Search button. Then it will also run the assertions that we added. As you can tell from Figure 7-7, the assertions are run and the result of each assertion is indicated by coloring the assertion (in this case green because they succeeded).

Now, to verify that it actually works, let's deliberately break the test. In the Source tab of the command table, let's change the value of the last assertion into something else (in this case "flex") and rerun the test. The assertion is now colored red and the test fails (see Figure 7-8).

The resulting command table of the Selenium IDE

Figure 7-6. The resulting command table of the Selenium IDE

The result of running the test case (success)

Figure 7-7. The result of running the test case (success)

The result of running the test case (fail)

Figure 7-8. The result of running the test case (fail)

Just a couple pointers before we move on to a more detailed description of using Selenium. First, as you can see from Figure 7-5, below the commands table there's an area that contains several tabs. If you ran the test cases, you'll notice that the Log tab fills up with log entries that result from running the test. However, a far more interesting tab is the Reference tab. When you click on a command in the command table, the Reference tab shows some reference documentation on the command and its usage. This often contains interesting and useful information.

Another thing to note, as you can see in Figure 7-5, is that the context-sensitive pop-up menu described earlier also contains a submenu item labeled Show All Available Commands. This option (as the name suggests) opens a menu listing all available commands for the current selection and/or position in the document (see Figure 7-9).

Saving the Test Case

So far we've used the Selenium IDE to record a test case and use its built-in support for adding assertions to the test. But now we also want to keep the test for later reference. As we'll see in the next section, we can easily reuse the test cases that were recorded using the Selenium IDE when we start working with the Selenium Core and more advanced parts. So let's look at how we can save the test case that we previously created.

First of all, we need to know that the default format the Selenium IDE uses is HTML. As we'll see later, other formats are also supported, but for the next section, HTML will do fine. To make sure we're saving the test case as HTML, please check the format under Options > Format. If another format is selected, please check HTML.

The Show All Available Commands context-sensitive submenu

Figure 7-9. The Show All Available Commands context-sensitive submenu

Now, actually saving the generated test case is easy. Just select the Save Test Case menu item, select a location, and type a file name (make sure to use the .html suffix). Now to conclude this section, let's look at the output of the save action, the generated HTML version of the test case (see Listing 7-9).

Example 7-9. The Generated Test Case (in HTML Format)

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
                      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  <head profile="http://selenium-ide.openqa.org/profiles/test-case">
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <link rel="selenium.base" href="" />
    <title>selenium_test</title>
  </head>
  <body>
    <table cellpadding="1" cellspacing="1" border="1">
      <thead>
        <tr><td rowspan="1" colspan="3">selenium_test</td></tr>
      </thead>
<tbody>
        <tr>
          <td>open</td>
          <td>/</td>
          <td></td>
        </tr>
        <tr>
          <td>type</td>
          <td>q</td>
          <td>gwt</td>
        </tr>
        <tr>
          <td>clickAndWait</td>
          <td>btnG</td>
          <td></td>
        </tr>
        <tr>
          <td>verifyTextPresent</td>
          <td>code.google.com/webtoolkit</td>
          <td></td>
        </tr>
        <tr>
          <td>verifyValue</td>
          <td>q</td>
          <td>gwt</td>
        </tr>
      </tbody>
    </table>
  </body>
</html>

Selenium Core

So far we've seen how to use the Selenium IDE to record and play back test cases. This is convenient, but it has one fairly large limitation: it only allows you to test your application in Firefox. There's no version of the Selenium IDE for other browsers (yet). Luckily, Selenium Core comes to the rescue.

Selenium Core provides a mechanism that allows you to run the same test case we created in the previous section as part of your web application. As we'll see in a second, in order to use this mechanism, you need to have control over the application that you want to test and you need to know how to deploy the application (including the test runner and accompanying test cases). But before we go into the specifics of using the Selenium Core mechanism, let's first look at the element locator mechanism, the commands and built-in assertions provided by Selenium in general.

Element Locators

Element locators are strings that tell Selenium which HTML element a command refers to. In the previous section, we touched on element locators twice. Both the type and the verifyValue commands used a basic element locator to locate the element the command applied to. In both cases, it referred to an element with the name q. But let's look at some more sophisticated element locators provided by Selenium (see Table 7-2).

Table 7-2. Element Locator Types Provided by Selenium

LocatorType

Description

identifier=id

Select the element with the specified id attribute. If no match is found, select the first element whose name attribute is id.

id=id

Select the element with the specified id attribute.

name=name

Select the first element with the specified name attribute. The name may optionally be followed by one or more element filters (see below), separated from the name by whitespace.

dom=expr

Find an element using JavaScript to traverse the HTML Document Object Model. DOM locators must begin with document.

Example: dom=document.forms['myForm'].myDropdown

xpath=expr

Locate an element using an XPath expression.

Example: xpath=//img[@alt='The image alt text']

link=pattern

Select the link (anchor) element that contains text matching the specified pattern.

The default element locator is the identifier (like the ones we discussed earlier), so when a locatorType prefix is missing, the identifier is used. Please note that the table of element locators isn't exhaustive; it only lists the most important and most widely used locators. Also note that Selenium provides more sophisticated mechanisms, such as so-called element filters and built-in string-match patterns, but those are considered out of scope for this book, as they're generally not needed.

Commands

Commands are the instructions that tell Selenium what to do. Selenium commands come in three specific flavors: actions, accessors, and assertions. As illustrated in the previous section, they all come in the form of command, target, and value.

Actions Actions are commands that directly manipulate the state of the application under testing. So the open, type, and clickAndWait commands are all actions. Other actions include goBack, dragdrop, close, keyPress, refresh, select, and so on. All are pretty straightforward; for a full description, look at the Selenium reference.

Accessors Accessors are used to examine the state of the application under test. They typically store the results in variables that can later be used for assertions. Accessors include things such as storeTitle, storeBodyText, storeAlert, and storeValue. Typically, these aren't the ones used directly while creating simple test cases. But they're used by Selenium to automatically generate the corresponding assertions.

Assertions Before we go into the assertions that Selenium provides, we should note that assertions come in three modes: assert, verify, and waitFor (see Table 7-3).

Table 7-3. The Three Different Modes of Assertions

Mode

Description

assert

When an assert fails, the test is aborted.

verify

When a verify fails, the test will continue to execute.

waitFor

Will cause the test to wait for a certain condition to become valid. Note that the test will fail and halt when a certain timeout is reached before the condition becomes valid.

Taking these three modes, the storeTitle accessor automatically generates six assertions: assertTitle, assertNotTitle, verifyTitle, verifyNotTitle, waitForTitle, and waitForNotTitle. The same goes for the storeAlert accessor, leading to assertAlert, assertNotAlert, and so on. The pattern should be clear by now.

Using the Selenium Core

The mechanism provided by Selenium Core depends heavily on using iframes and JavaScript for its internal workings. Because JavaScript is restricted inside a browser environment by the so-called Same Origin Policy, you can only use this mechanism to test applications that you host yourself. So we can't use it to test, for instance, the Google web application, as we previously did using the Selenium IDE. This is because we can't ask (or at least don't expect to get) Google to add Selenium and our test cases to their hosted application. But that's okay, as we want to use Selenium to test our own web application anyway. So let's look at how we can use Selenium Core to test our own application.

Installing Selenium Installing Selenium Core is easy: the only thing you have to do is to navigate to the Selenium download page (http://selenium-core.openqa.org/download.jsp) and download the latest distribution of the Selenium Core. Next, just unpack the core folder of the downloaded zip file to the root of your web server or servlet runner (depending on your setup). That's all you have to do to get Selenium Core installed.

Running Selenium Once installed, using your favorite web browser, just navigate to the TestRunner.html page in the core folder on your web site. You should see something that closely resembles the picture depicted in Figure 7-10. Please note that your screen might look slightly different depending on the exact version of Selenium Core.

The TestRunner.html page in Firefox

Figure 7-10. The TestRunner.html page in Firefox

As you can see, the screen is divided into four sections:

  • Test Suite—the part where the test suite is managed. In Figure 7-10, this shows the selector for choosing the test suite to open, but as we'll see in a later section, this part can also show the content of a test suite.

  • Current Test—the part that shows the test that's currently being run.

  • Control Panel—the control panel part that allows you to control Selenium and how it actually runs your test. The control panel will be discussed in more detail next.

  • Main Application—the part where the actual application being tested will show.

Before we continue to show how to actually create a test suite and add a test to it, let's first take a more detailed look at the Control Panel of the Selenium test runner page (see Figure 7-11).

As you can see, it closely resembles the Selenium IDE in the sense that it allows you to run individual tests (but also all tests) and provides feedback about the result of the tests.

Creating a Test As mentioned before, we can't use Selenium Core to test applications that aren't under our control, so we need to create a different test case for our own application. Let's go back to the UserRegistrationForm application introduced in Chapter 4. If we create a test manually or using the Selenium IDE, the end result will be something like Listing 7-10. Be sure to save that test in a folder named tests in the root of your web application, under the name UserRegistrationFormTests.html.

The Control Panel of the Selenium test runner

Figure 7-11. The Control Panel of the Selenium test runner

Example 7-10. A Selenium Test Case for the UserRegistrationForm Application

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
                      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  <head profile="http://selenium-ide.openqa.org/profiles/test-case">
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <link rel="selenium.base" href="http://localhost/" />
    <title>UserRegistrationFormTests</title>
  </head>
  <body>
    <table cellpadding="1" cellspacing="1" border="1">
      <thead>
        <tr><td rowspan="1" colspan="3">UserRegistrationFormTests</td></tr>
      </thead>
      <tbody>
        <tr>
          <td>open</td>
          <td>/...chap4.UserRegistrationForm/UserRegistrationForm.html</td>
          <td></td>
        </tr>
        <tr>
          <td>type</td>
          <td>//input[@type='text']</td>
          <td>bram</td>
        </tr>
<tr>
          <td>type</td>
          <td>//input[@type='password']</td>
          <td>lullo</td>
        </tr>
        <tr>
          <td>type</td>
          <td>//td[@id='retypePasswordField']/input</td>
          <td>lullo</td>
        </tr>
        <tr>
          <td>click</td>
          <td>//button[@type='button']</td>
          <td></td>
        </tr>
        <tr>
          <td>assertAlert</td>
          <td>username: 'bram', password: 'lullo'</td>
          <td></td>
        </tr>
      </tbody>
    </table>
  </body>
</html>

Adding a Test to the Test Suite Now that we've added the specific test to the tests folder of our web application, we need to add it to a test suite. Selenium uses test suites to group individual test cases together. Creating a test suite and adding tests to it is simple when using Selenium Core. Test suites, like individual test cases, are represented by HTML pages, each containing a table. In the case of test suites, the table in the HTML document contains all tests to run. So a test suite that contains only the test we created for our application will have only one entry in its table referencing that one test case (see Listing 7-11).

Example 7-11. A Selenium Test Suite Containing Only One Test Case

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
                      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  <head profile="http://selenium-ide.openqa.org/profiles/test-case">
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <link rel="selenium.base" href="http://localhost/" />
    <title>My Test Suite</title>
  </head>
  <body>
    <table cellpadding="1" cellspacing="1" border="1">
      <thead>
<tr><td rowspan="1" colspan="3">My Test Suite</td></tr>
      </thead>
      <tbody>
        <tr>
          <td>
            <a href="UserRegistrationFormTests.html">
              UserRegistrationFormTests
            </a>
          </td>
        </tr>
      </tbody>
    </table>
  </body>
</html>

Just save the code using the default Selenium test suite name TestSuite.html. Then navigate back to the test runner page.

Running the Test Suite If you now click on the Go button of the test runner, it opens the selected, default test suite and shows its contents. It also shows the current test as the first test of the test suite (see Figure 7-12).

The Selenium test runner with the selected test suite

Figure 7-12. The Selenium test runner with the selected test suite

Now, the only thing we need to do is run the test suite, by clicking the Run All Tests button. The result (shown in Figure 7-13) looks much like what we saw earlier using the Selenium IDE. But in this case, we can navigate to the test runner page using any browser on any platform to test the application and still verify that it functions correctly in that browser.

The result of running the test suite in the test runner

Figure 7-13. The result of running the test suite in the test runner

Advanced Features You can also do more advanced things in test cases and test suites, such as using setup and teardown hooks similar to the hooks in JUnit. You can also make the test runner run all tests in the specified suite by specifying an extra parameter (auto=true) when opening the test runner page. This is useful when you want to include Selenium testing as part of your automated test environment and/or continuous integration. For this and other more advanced features, consult the Selenium Core home page (http://selenium-core.openqa.org).

Selenium Remote Control

Selenium Remote Control (RC) is another extension of Selenium Core that allows you to use a much more powerful mechanism to describe your test cases. We found that the most effective way to write our tests was in Java, but Selenium RC has support for other languages as well, including C#, Perl, PHP, Ruby, and JavaScript.

The setup of Selenium RC is also slightly different, in that it uses a remote control server, which acts as a proxy server between the remote test runner and the application under test. It would be tedious to describe this setup in detail. But with the information you've read so far about Selenium, you should easily be able to use Selenium RC if you wish. The main advantage of Selenium RC is that it allows you to orchestrate the same test on different browsers and different platforms. For more information on Selenium RC, have a look at the Selenium RC home page (http://selenium-rc.openqa.org).

Benchmarking

In addition to being able to unit test your normal Java code, GWT's support for testing UI code, and using Selenium for functional testing and testing for browser incompatibilities, GWT also provides out-of-the-box support for benchmarking. This section will describe that support and how it may benefit you as an application developer.

What Is Benchmarking?

In computing, benchmarking is running a program or operation to assess its relative performance. You usually do this by running multiple trials with different input data to see what effect the changes in data have on performance.

When developing GWT applications, it's interesting (and for some operations even essential) to do some benchmarking. Because the entire GWT application is eventually compiled into JavaScript, benchmarking becomes important. Overall, but in some browsers even more than in others, JavaScript tends to run far slower than normal Java code. Therefore, being able to trace and fix performance bottlenecks is truly important.

Luckily for us, GWT comes with some built-in support for benchmarking. It enables you to write benchmarks easily and then run them on different platforms and browsers to compare their performance.

Writing a Benchmark

Writing a benchmark using GWT's built-in support is easy (see Listing 7-12). It's just like writing a normal unit test, only we need to extend from another base class: Benchmark.

Example 7-12. The BasicCalculatorBenchmark Class

public class BasicCalculatorBenchmark extends Benchmark {
  private BasicCalculator calculator;
  protected void setUp() throws Exception {
    calculator = new BasicCalculator();
  }

  public String getModuleName() {
    return "com.apress.beginninggwt.ch07.DefaultModule";
  }
  // actual benchmark methods go here
}

As you can see from the sample benchmark skeleton, there's not much difference between this benchmark class and the unit test that uses GWT's unit test support. We just need to extend the Benchmark class and implement the getModuleName() method. But now, let's write the actual benchmark.

Let's first write a simple benchmark for the multiply() method of the BasicCalculator class (see Listing 7-13). We just add a method called testMultiply() as we would do using JUnit. Only in this case, we want to provide the test method with different input on each run, so we need to provide it with an argument to the method. We give the method a single argument, so it takes a single integer as argument.

Example 7-13. The testMultiply Method with One Argument and Without Arguments

public void testMultiply(@RangeField("testRange") Integer arg) {
  calculator.multiply(arg, 2);
}
public void testMultiply() {
  // just placeholder for junit
}

As you can see, the first test method just calls the calculator multiply method with one variable argument and one fixed argument. The main thing to get from this code listing is that the testMultiply method takes one argument, an integer that's annotated with a range (which we'll discuss in a moment). So, this test method will be called with a specific range of input variables. Also note that we need to provide an empty placeholder method with the same name and no arguments. This is because of JUnit's test runner, which doesn't run if there are no test methods, as the method with arguments doesn't count as a real test method according to JUnit. So every benchmark method should have a corresponding test method with no arguments.

Now, in order to perform a benchmark, we need to define the range of input variables to use as input. This is conveniently done by creating a field that defines a range and then referencing it using the corresponding annotation (see Listing 7-14). There are other ways to do it, but this is by far the easiest.

Example 7-14. Defining the testRange Field to Use for the Benchmark

protected IntRange testRange =
    new IntRange(0, 1000000, Operator.ADD, 200000);

We just define a range of integers to use as input data. We start the range at 0 and work our way up to 1,000,000 by adding 200,000 for each test run, so we complete in five steps. We can now run the benchmark, which will write an output report (using the naming convention:

report-<timestamp>.xml).

The next step is to view the generated report. Luckily, GWT provides a basic tool to view the generated report. This utility script called benchmarkViewer can be used by just specifying the path where the report resides. Starting the viewer will bring up a tool that resembles the hosted mode and lists all known reports. Please note that the appearance and content of the report viewer may differ from those shown in Figure 7-14, depending on the specific version of GWT that you're using.

The benchmark report viewer after the initial benchmark run

Figure 7-14. The benchmark report viewer after the initial benchmark run

If you now click on the single report that's presented, the viewer will show the details of the selected benchmark report, as shown in Figure 7-15.

The benchmark report viewer showing the detailed report of the benchmark

Figure 7-15. The benchmark report viewer showing the detailed report of the benchmark

The graph that's part of the report shows how long it took to complete the multiply() operation, relative to the value of the argument that was passed in. As you can see from the detailed report, the time taken to complete the operation doesn't fluctuate much based on the input. The small fluctuations are more because of JavaScript's inability to measure small amounts of time. So in order to provide us with a better example, we have to add a more convenient benchmarkable method to the BasicCalculator. Let's assume for some reason that BasicCalculator also contained the method shown in Listing 7-15.

Example 7-15. The slowMultiply Method on the BasicCalculator Class

public int slowMultiply(int num1, int num2) {
  for (int i = 0; i < num1; i++) {
    for (int j = 0; j < num2; j++) {
      multiply(i, j);
    }
  }
  return multiply(num1, num2);
}

Although probably not very useful for calculating, it comes in handy from a benchmark perspective. Now, let's assume we want to benchmark this method, using the same range. You might expect the graph to look a little different, because of the way the slowMultiply() method is implemented. But, let's add the method (and corresponding no-argument method) shown in Listing 7-16 to the BasicCalculatorBenchmark class.

Example 7-16. The Methods to Benchmark the slowMultiply Method

public void testSlowMultiply(@RangeField("testRange") Integer arg) {
  calculator.slowMultiply(arg, 2);
}
public void testSlowMultiply() {
  // just placeholder for junit
}

As you can see, the benchmark method does exactly the same thing as the previous benchmark method, only it calls the slow version of the multiply method.

Now, let's run the benchmark once more and start the report viewer to view the outcome (see Figure 7-16 for the overview and Figure 7-17 for the details). Note that you can also just click Refresh in the report viewer after the benchmark has finished.

Benchmark report viewer after the second benchmark run

Figure 7-16. Benchmark report viewer after the second benchmark run

In the last part of the detailed report—the testSlowMultiply part—the graph shows an obvious trend in that each increment in the argument passed in leads to a large increment in time taken to complete the test method.

There's a lot more to be said about benchmarking and the support GWT provides for it. But as far as this book is concerned, our discussion of benchmarking is now complete. If you want to know more about the features GWT provides for doing more complex benchmarking, take a look at the Javadoc documentation in the Benchmark class of GWT.

The benchmark report viewer showing the detailed report of the second benchmark

Figure 7-17. The benchmark report viewer showing the detailed report of the second benchmark

Summary

This chapter introduces different kinds of testing and goes into some detail on unit testing and functional testing. For unit testing, the chapter introduced JUnit as a tool to easily unit test your code, including client-side code, without dependencies on GWT's deferred binding mechanism.

When you need to test client UI code that depends heavily on the deferred binding mechanism, JUnit is insufficient. In that case, we resort to the unit testing support provided by GWT out of the box. However, this testing tends to be a lot slower than basic unit testing. So when offered a choice, basic unit testing should be favored over GWT's unit test support.

Next, the chapter introduced Selenium and showed how you can leverage its power to test your application functionally and hunt for browser incompatibilities. Selenium allows you to test your application in every conceivable browser on every platform.

Last, the chapter discussed benchmarking, why it's important, and how GWT provides out-of-the-box support for it, including a graphical report viewer to view the result of the benchmark.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.17.25.23