GWT Unit Tests running slowly? Use a TestSuite.

If you've written GWT unit tests for your app, you've probably realised that they run quite slowly. Most of the time, the delay seems to be in creating the context in which that test will run rather than in the test itself. This delay can be quite painful in situations where a large number of test cases are being run - as part of a Continuous Integration build process for example.

We were able to significantly reduce the running time of all the GWT test cases in our build by simply placing them in a TestSuite. Unfortunately, TestSuite is not one of the classes that is extended by the GWT's implementation of JUnit, but we didn't notice any real problems while using it.

Of course, since TANSTAAFL is one of the laws which governs our universe there is a trade-off to be made. When running a TestSuite you have to allocate enough memory for all the tests in all the TestCases in that TestSuite. GWT is quite a memory hog and can easily require over a Gigabyte of memory allocated to it. However this shouldn't pose a big problem and the reduction in build feedback time is more than worth it.

Using TestSuite creates a new problem though. People routinely forget to add the TestCases they create to the TestSuite which is run by the build. Consequently you end up with bits of functionality being broken but not being detected simply because the relevant tests are not being run during a build.

Around the third time this happened, I got frustrated and decided to automate the process. We used Ruby to write a little script which would pick up all the TestCases in a particular directory and add it to a template. It's pretty rough but it works, so whatever...
Just remember that it doesn't auto-generate imports, so you'll have to add those to the template manually. It isn't hard to modify the script to do this too, but it happened sufficiently rarely in our case that I didn't bother.

On Windows, we needed to check in just two files to use Ruby - msvcrt-ruby18.dll and ruby.exe

Here's the code listing:

suite.template

import junit.framework.TestSuite;
import junit.framework.Test;
import com.whatever.imports.you.need.*;

public class GWTTestSuite extends TestSuite {
  public static Test suite() {
    // Five hashes are what the ruby code replaces
    Class[] tests = {
      #####
    };

    TestSuite suite = new TestSuite();
    for (int i = 0; i < tests.length; i++) {
      suite.addTestSuite(tests[i]);
    }
    return suite;
  }
}


gen.rb

java_files = Array.new
gwttestfiles = Array.new

jfiles = File.join("**", "*.java")
#Change working directory to the GWT test directory
Dir.chdir("\\your\\project\\path\\testgwt")
Dir.glob(jfiles) {|x|
  java_files<<x if "#{x}".include?("Test")
}

java_files.each do |element|
  file = File.new(element)
  matched_lines = file.select{|line|   line.match('\s*public\s+void\s+test.*')}
  if matched_lines.size > 0
    name = File.basename(file.path)
    gwttestfiles<<name.gsub("\.java","\.class")
    puts "Located: " + name
  end
end

classes_string = gwttestfiles.inject { |result, klass| result + ",\n" + klass }

file = File.new("\\your\\project\\tools\\ruby\\suite.template")
File.open("GWTTestSuite.java","w") do |output|
  file.each do |line|
    line.include?("#####") ? output.write(classes_string) : output.write(line)
  end
end


The Ant target to run the ruby script:

<target xmlns="" name="generate.gwt.test.suite">
  <exec dir="\your\project\tools\ruby\" executable="\your\project\tools\ruby\ruby.exe" failonerror="true">
    <arg value="gen.rb"/>
  </exec>
</target>

7 comments:

akshay said...

I use the DirectorySuiteBuilder to build test suites dynamically. This comes as a part of the Junit Addons library

seiju said...

Using TestSuite, how do u run the test before?

I'm trying with the same script that gwt made (with the TestSuite class instead of the individual GWTTestCase class), but it fails :(

Blaxter said...

@seiju,
Make the testSuite class out of the gwt module, for example if u have a package com.urcompany.App, create test.com.urcompany.App.TestSuite, in that class follow the instructions of this wonderful post, and then execute it with the script that generates the junitCreator. it just works :D

Sidu said...

@seiju,
Yup, exactly as blaxter said. Remember, TestSuite isn't a part of GWT so you need to locate in such a way that the GWT compiler doesn't try to compile it. Then you should be able to run it easily from within IntelliJ or Eclipse.

dave said...

Hi Sidu,

Interesting tip. I'm not noticing any speed improvement over just running all of the GWTTestCaes independently -- is there perhaps something I'm missing?

(FYI, I'm running these through Eclipse)

Sidu said...

Dave, I'm somewhat out of touch - I haven't looked at this since GWT 1.2 was in beta. I know that one of the significant optimisations in 1.3 and 1.4 was in the area of test performance. I'm guessing you'd need several hundred tests to tell the difference with 1.4 - but unfortunately I don't have the time to do a unit test performance comparison between 1.2 and 1.4 so I can quote hard numbers. If you're able to do this, I'd be grateful if you'd let me know the outcome so I can decide if this post is no longer applicable to newer versions of the GWT.

zaccret said...

Now there is a GWTTestSuite class in GWT.