Testing Java with Ruby

Print
Getting Started

There are three different parts needed to get started with JtestR. Two of them are usually necessary for every project.

JtestR and Ant

The first step to get started with JtestR is to download the binary jar file from http://dist.codehaus.org/jtestr. The file is quite large, since it contains several interesting libraries. Once you have downloaded the jar file, the only thing you need to get started is to add it to your classpath for the project you want to test, and then make sure you define the tasks necessary for JtestR to work. There are two tasks you might be interested in adding to your build.xml. The first one is for general testing:

<target name="test" description="Runs all tests">
  <taskdef name="jtestr" classname="org.jtestr.ant.JtestRAntRunner" classpath="lib/jtestr.jar"/>

  <jtestr/>
</target>

Make sure that change the classpath variable of the taskdef to point to the real jtestr.jar. Having the build.xml and jtestr.jar files in the same directory will not work. When you run the test target in Ant, jtestr will check in several directories for the place where you have put your tests. When you run the test target in Ant, jtestr will by default look for and run tests from a directory named "test" and its subdirectories.

If you want to specify a different directory for your tests, use the "tests" property of the jtestr task:

   <jtestr tests="my_tests"/>

Where my_tests is a directory relative from the build.xml file. The jtestr task takes these arguments:

  • failOnError (boolean, default is true): Whether JtestR should fail the Ant build when the testing fails.
  • port (int, default is 22332): The port to try checking for a JtestR server on.
  • tests (String, default is "test"): The directory to look for JtestR configuration file and tests.
  • groups (String, default is ""): The groups to run, separated by comma. If not specificed, runs default groups.
  • logging (String, default is "WARN"): The logging level, valid values are: "NONE","ERR","WARN","INFO","DEBUG"
  • configFile (String, default is "jtestr_config.rb"): The name of the config file. Note that this is not a path, just the name of the file itself.
  • outputLevel (String, default is "QUIET"): The unified result handling in JtestR has different levels of verbosity. Valid values are: "NONE","QUIET","NORMAL","VERBOSE","DEFAULT"
  • output (String, default is "STDOUT"): The place output from test runs should end up. This can be any Ruby code. The resulting object needs to be IO-like.
  • load (String, default is ""): A string detailing the load strategy to use if any needed. Format: "rspec = abc, foo; active_support=another,dir"

You can also set a system property to use a specific test. This isn't one of the options to configure, but instead is only a Java system property:

  • jtestr.test (String, default is ""): The name of a specific test to run. If empty it isn't used.

If you set this as an Ant property, it will be picked up and inserted as a system property too.

If you are running your tests often - as you should - you will soon realize that it takes some time for JtestR to get started. This is because the JRuby runtime is a bit heavy weight to start. To solve this problem, there is a second ant task available that starts a background server. If a background server is started, the regular jtestr task will automatically use that instead. Both jtestr and jtestr-server takes an optional port to use for the server:

   <target name="test-server" description="Starts test server">
    <taskdef name="jtestr-server" classname="org.jtestr.ant.JtestRAntServer" classpath="lib/jtestr.jar"/>

    <jtestr-server port="20333" runtimes="3"/>
  </target>

Classes and resources loaded by the server JVM classloader from the taskdef classpath will not be dynamically replaced when you rerun your tests. It is therefore recommended that you keep the server classpath to a minimum (i.e. just the jtestr.jar).

The jtestr-server task takes these arguments:

  • port (int, default is 22332): The port to start the JtestR server on.
  • runtimes (int, default is 3): How many runtimes to cycle through for testing.
  • debug (boolean, default is true): Should the server write information about each connection or not?

The server takes an optional argument for how many JRuby runtimes to start. In most cases 2 or 3 is good enough. It will take some time for the server to start, but once it's started it will cycle through runtimes so you should be able to start test runs very quickly. You need to keep the test-server ant task running to keep the server running. The task also provides some logging for every time it runs a test task.

The option for specifying load strategy is a bit special and needs to be explained. JtestR includes several different libraries, and uses these internal libraries for everything. In some cases this might not be ideal, and in that case you might want to use a version of a library that you have locally instead. You accomplish that by setting the "load" parameter. This parameter should be a list of keys and values. The values can be separated with commas, while each key value pair should be separated with semi colons. The value should be one or more directories where the code for the library lives. So for example, say that you have ActiveSupport in the directory /usr/lib/ActiveSupport-2.1.1, and you need something else on the load path for ActiveSupport to work, which lives in /usr/lib/RubyFoobar-1.2. Then you can specify this by setting load to be the string "active_support = /usr/lib/ActiveSupport-2.1.1, /usr/lib/RubyFoobar-1.2". Note that spaces are ignored and can be added for readability. The only keys supported at this point is: "active_support", "expectations", "mocha, "spec" and "dust". Using this feature might fail in strange ways, since JtestR is written based on the versions of the libraries bundled.

And that's really all you need to know on how to get started from the Ant side of things. The next step in getting started is to look at how to get started with Maven integration, or how to get started writing tests with JtestR.

JtestR and Maven

JtestR currently only supports Maven2. It should be fairly simple to integrate it with Maven 1 too, though, since the Ant tasks are available. These instructions are only for Maven2. To test your project using Maven2, you need to add this to your pom.xml:

<plugins>
  <plugin>
    <groupId>org.jtestr</groupId>
    <artifactId>jtestr</artifactId>
    <version>0.6</version>
    <executions>
      <execution>
        <goals>
          <goal>test</goal>
        </goals>
      </execution>
    </executions>
  </plugin>
</plugins>

That's the basic parts needed for standalone test execution. Exactly like with Ant, there are several configuration values you can add, though. These are added in the regular manner of Maven plugin configuration:

<plugins>
  <plugin>
    <groupId>org.jtestr</groupId>
    <artifactId>jtestr</artifactId>
    <version>0.6</version>
    <configuration>
      <tests>ruby_tests</tests>
    </configuration>
    <executions>
      <execution>
        <goals>
          <goal>test</goal>
        </goals>
      </execution>
    </executions>
  </plugin>
</plugins>

The configurations supported are:

  • failOnError (boolean, default is true): Whether JtestR should fail the Maven build when the testing fails.
  • port (int, default is 22332): The port to try checking for a JtestR server on.
  • tests (String, default is "test"): The directory to look for JtestR configuration file and tests.
  • groups (String, default is ""): The groups to run, separated by comma. If not specificed, runs default groups.
  • logging (String, default is "WARN"): The logging level, valid values are: "NONE","ERR","WARN","INFO","DEBUG"
  • configFile (String, default is "jtestr_config.rb"): The name of the config file. Note that this is not a path, just the name of the file itself.
  • outputLevel (String, default is "QUIET"): The unified result handling in JtestR has different levels of verbosity. Valid values are: "NONE","QUIET","NORMAL","VERBOSE","DEFAULT"
  • output (String, default is "STDOUT"): The place output from test runs should end up. This can be any Ruby code. The resulting object needs to be IO-like.
  • load (String, default is ""): A string detailing the load strategy to use if any needed. Format: "rspec = abc, foo; active_support=another,dir"

You can also set a system property to use a specific test. This isn't one of the options to configure, but instead is only a Java system property:

  • jtestr.test (String, default is ""): The name of a specific test to run. If empty it isn't used.

You can use another Maven goal to start a background server, just like with Ant. The best way of starting it is by running this command:

mvn org.jtestr:jtestr:server

You can provide two other pieces of configuration that is only for the background server:

  • runtimes (int, default is 3): How many runtimes to cycle through for testing.
  • debug (boolean, default is true): Should the server write information about each connection or not?

The port argument affects both the test goal and the server goal.

Also note that JtestR work as expected in projects using submodules. Look in the source distribution, in the examples/multi_maven_project for an example that shows how it could look.

If you define your project dependencies in the Maven POM you do not need to add these dependencies to the classpath in the configuration file. The default classpath in a Maven2 project will instead be the default test classpath that Maven uses.

The option for specifying load strategy is a bit special and needs to be explained. JtestR includes several different libraries, and uses these internal libraries for everything. In some cases this might not be ideal, and in that case you might want to use a version of a library that you have locally instead. You accomplish that by setting the "load" parameter. This parameter should be a list of keys and values. The values can be separated with commas, while each key value pair should be separated with semi colons. The value should be one or more directories where the code for the library lives. So for example, say that you have ActiveSupport in the directory /usr/lib/ActiveSupport-2.1.1, and you need something else on the load path for ActiveSupport to work, which lives in /usr/lib/RubyFoobar-1.2. Then you can specify this by setting load to be the string "active_support = /usr/lib/ActiveSupport-2.1.1, /usr/lib/RubyFoobar-1.2". Note that spaces are ignored and can be added for readability. The only keys supported at this point is: "active_support", "expectations", "mocha, "spec" and "dust". Using this feature might fail in strange ways, since JtestR is written based on the versions of the libraries bundled.

That is really all there is to the Maven2 integration.

JtestR and JUnit

From JtestR 0.3, you can also use a JUnit harness to run your JtestR code. The reasons you would want to do this are several. Maybe the most interesting one is for IDE integration. If you IDE supports JUnit, it's extremely easy to get it to work with JtestR. If you want standard XML output from your tests, the JUnit integration makes this possible too.

Too configure for JUnit XML output, you can do it like this:

<junit haltonfailure="false" fork="yes">
  <jvmarg value="-Djtestr.junit.tests=test_tests/one_of_each"/>

  <formatter type="xml" usefile="false"/>

  <test name="org.jtestr.ant.JtestRSuite"/>
</junit>

As you can see, this example uses the JtestRSuite class, and this is the way you would configure any JUnit integration. The JtestRSuite takes care of everything for you, and you can configure the output just as you would any other JUnit test run.

There are a few configuration options you can send to the system, but since JUnit doesn't provide a standard way of configuring suites, this is done through system properties for JtestR. The available system properties you can set are these:

  • jtestr.junit.tests (String, default is "test"): The directory to look for JtestR configuration file and tests.
  • jtestr.junit.logging (String, default is "WARN"): The logging level, valid values are: "NONE","ERR","WARN","INFO","DEBUG"
  • jtestr.test (String, default is ""): The name of a specific test to run. If empty it isn't used.

JtestR command line tools

If you want to, you can also run JtestR independent of any specific build system. This will pick up and use the same configuration as the Ant and Maven2 configuration, but might be of use in certain circumstances when you want to try out more interesting tools on JtestR. The command line interface looks like this, assuming your classpath is set up correctly:

java org.jtestr.JtestRRunner [port] [tests] [logging] [configFile] [outputLevel] [output] [groups]

All command line arguments are optional:

  • port (int, default is 22332): The port to try checking for a JtestR server on.
  • tests (String, default is "test"): The directory to look for JtestR configuration file and tests.
  • logging (String, default is "WARN"): The logging level, valid values are: "NONE","ERR","WARN","INFO","DEBUG"
  • configFile (String, default is "jtestr_config.rb"): The name of the config file. Note that this is not a path, just the name of the file itself.
  • outputLevel (String, default is "QUIET"): The unified result handling in JtestR has different levels of verbosity. Valid values are: "NONE","QUIET","NORMAL","VERBOSE","DEFAULT"
  • output (String, default is "STDOUT"): The place output from test runs should end up. This can be any Ruby code. The resulting object needs to be IO-like.
  • groups (String, default is ""): The groups to run, separated by comma. If not specificed, runs default groups.

You can also set a system property to use a specific test. This isn't one of the options to configure, but instead is only a Java system property:

  • jtestr.test (String, default is ""): The name of a specific test to run. If empty it isn't used.

The main reason for using the standalone runner instead of a more simple build integration would be to facilitate using a test coverage framework like Emma. You can do this from Ant using something like this:

<emmajava libclasspathref="emma.lib"    fullmetadata="yes" sourcepath="${src.dir}" classname="org.jtestr.JtestRRunner">
  <classpath refid="build.classpath"/>
  <classpath path="build/classes"/>
  <classpath path="${src.ruby.dir}"/>

  <filter includes="org.jtestr.*"/>

  <txt outfile="build/coverage/coverage.txt" />
  <xml outfile="build/coverage/coverage.xml" />
  <html outfile="build/coverage/coverage.html"  />
</emmajava>

JtestR testing

When using JtestR for testing, you have several choices for test engine. The Ruby libraries Test::Unit, RSpec and Expectations are all included, and you can write Test::Unit tests using either the regular way of doing Test::Unit, or with dust syntax. This guide will quickly show you examples using each of these technologies. Unless you change it in the configuration file, all files ending with _spec.rb will be run using RSpec, and everything else will use Test::Unit. Of course, your existing JUnit and TestNG tests can also be integrated and run together with the rest of the tests in JtestR.

You will notice that the way Ruby test frameworks usually work is that you have a test directory that contains all the tests. They are usually organized in a directory structure with three different base directories: unit, functional and integration. In JtestR, all directories named unit will run first, then the directories named functional, then the integration directories, and finally all files that hasn't been run yet. You will see that as JtestR reports what it's doing. You will also notice that Test::Unit, RSpec, Expectations, JUnit and TestNG tests are run separately. This is because RSpec allows you to change output formats in different ways from the other engines.

Lets first take a look at a few tests for java.util.HashMap, written using classical Test::Unit style:

class HashMapTests < Test::Unit::TestCase
  def setup
    @map = java.util.HashMap.new
  end

  def test_that_map_is_empty
    assert @map.isEmpty
  end

  def test_that_an_entry_can_be_added
    @map.put "foo", "bar"
    assert_equal "bar", @map.get("foo")
  end

  def test_that_it_returns_a_keyset_that_returns_an_iterator_that_throws_exception
    assert_raises(java.util.NoSuchElementException) do
      @map.key_set.iterator.next
    end
  end
end

As you can see, this code looks quite a lot like typical JUnit code. You have a setup method where a new HashMap is created. This gets called for every test. Test::Unit will use all methods that begin with test_ and run these. Test::Unit also includes several standard assertions that can be used to check expectations. You see three of them in the example above, assert, assert_equal and assert_raises. Available assertions can be found in the module Test::Unit::Assertions:

 assert
 assert_equal
 assert_in_delta
 assert_instance_of
 assert_kind_of
 assert_match
 assert_nil
 assert_no_match
 assert_not_equal
 assert_not_nil
 assert_not_same
 assert_nothing_raised
 assert_nothing_thrown
 assert_operator
 assert_raise
 assert_raises
 assert_respond_to
 assert_same
 assert_throws

Note that for testing exceptions, you should use assert_raise and assert_nothing_raised. The methods assert_throws and assert_nothing_thrown is for a different language feature in Ruby, that really doesn't have a counterpart in Java.

Testing with Test::Unit can also have a teardown method that runs after each test. All documentation you can find on Test::Unit online is as applicable for testing Java code as for testing Ruby code.

Many developers feel that the way Test::Unit requires you to specify tests is a bit unnatural and verbose. Jay Fields created dust to get around this problem. You can use dust anywhere in JtestR instead of regular Test::Unit syntax. Note that you still use the same assertions, setup and teardown methods with dust. The only thing that is different is how you define your tests:

unit_tests do
  test "that foo is not equal to bar" do
    assert_not_equal "foo", "bar"
  end
end

functional_tests do
  test "that foo is not equal to bar" do
    assert_not_equal "foo", "bar"
  end
end

And that's really all there is to dust. Behind the covers, a Ruby class wil be generated based on the file name this code resides in.

RSpec is a bit more different. The framework has a different goal than Test::Unit, but it works very well for most testing tasks. In JtestR all files ending in _spec.rb will be run with RSpec. You can change which files are used with the configuration file, though. A typical RSpec test file contains descriptions about something, and examples of how the behavior of it should work. In practical terms it looks like this:

import java.util.HashMap

describe "An empty", HashMap do
  before :each do
    @hash_map = HashMap.new
  end

  it "should be able to add an entry to it" do
    @hash_map.put "foo", "bar"
    @hash_map.get("foo").should == "bar"
  end

  it "should not be empty after an entry has been added to it" do
    @hash_map.put "foo", "bar"
    @hash_map.should_not be_empty
  end

  it "should be empty" do
    @hash_map.should be_empty
    @hash_map.size.should == 0
  end

  it "should return a keyset iterator that throws an exception on next" do
    proc do
      @hash_map.key_set.iterator.next
    end.should raise_error(java.util.NoSuchElementException)
  end
end

The structure of a typical RSpec specification uses describe to start a block describing something. The description of the thing under test should more or less match the common thing for all examples in that group. Each example is described with the it-method. You use before(:each) to do something before every test, and before(:all) to run something once, before all the examples are executed. There are corresponding after-methods. If you don't give a block to the it-method, you have created a pending test. That's legal and will be reported separately when run through JtestR.

The basic ways of checking values in RSpec depend on the should and should_not methods. You send them so called "expectation matchers", and that is the test in question. The combination ends up being quite readable and natural. The matchers in RSpec are quite flexible, and there are many of them. The best way to find out about them is to look at the RSpec site at http://rspec.info and read their documentation.

JtestR also have support for RSpec stories. To use that, first read up on RSpec stories, then check out the example in the JtestR test suite (in test/stories).

The Expectations support is brand new, and allows you to decide if you want to use Expectations instead of any of the other formats. You need to specifically point this out in the configuration file for it to work, but after you have done that it works exactly like the Test::Unit and RSpec support.

JtestR gives you some helpers that might be very useful in certain circumstances. Most of these are documented elsewhere, but one thing that can be good to know is the J module. If you get annoyed at having to write the fully qualified name of a Java class all the time, or having to import it, you can use the J-module:

J::HashMap
J::String

By default the J module will search java.lang and java.util. If you want to change this, it's quite easy to do:

J::packages << java.util.regex

If you want to get J back to the default search list or remove all the cached constants that J have created for you, do it with reset:

J::reset

This concludes the getting started part for JtestR testing. For more information see the examples and documents about specific features.

Powered by Atlassian Confluence