JWalk software testing tool suite

Lazy systematic unit testing for agile methods

You are here: JWalk Home / Custom Generators /
Department of Computer Science

How to Customise JWalk

JWalkTester selecting a custom Generator

Taking Control

Sometimes the tester may want to take control of how the JWalk 1.1 Tool Suite synthesises test inputs. Likewise, the tester may wish to establish a particular context in which the test class is to be executed. Both of these concerns are addressed by supplying one or more custom generators and uploading them to the tool.

The JWalk 1.1 Tool Suite provides a small library of ready-made custom generators. Several of these are loaded in the standard configuration at startup of the tools. Others must be loaded explicitly by the tester. Here, we describe why you might need a custom generator and how to build your own. For help on how to upload a custom generator, please refer to the main User Guide.

Master and Custom Generators

All of the tools in the JWalk 1.1 Tool Suite synthesise test input values for the constructors and methods of the test class using components called generators, which are Java classes satisfying the Generator interface. Each generator is capable of synthesising values (primitive values, or object instances) for one or more types in the Java language. All generators must synthesise their values in a predictable and repeatable way (rather than randomly). The the tools in the JWalk 1.1 Tool Suite use a combination of MasterGenerator and CustomGenerator generators to synthesise all test inputs.

Master Generators

A MasterGenerator is any Java class that implements the MasterGenerator interface. A MasterGenerator uses a number of standard algorithms which generate a quasi-unique value or object upon each request. It may also delegate requests to supply values of certain types to one or more CustomGenerators, if these were previously added as delegates of that master. By default, the MasterGenerator handles the synthesis of all types, but if a CustomGenerator declares that it handles certain types, the master will delegate to it instead. A delegate CustomGenerator may synthesise values for one type, or for a group of types, using a different custom algorithm.

The standard MasterGenerator used by the tools is ObjectGenerator. This generator synthesises all basic values in a quasi-unique monotonically increasing sequence. Values are said to be "quasi-unique", since boolean values will inevitably cycle between false and true. Values for other primitive types are typically unique, for example, numeric values increase steadily from 1. Object values will be uniquely instantiated using either their default constructor, or the first constructor with arguments that succeeds (the arguments are synthesised recursively). ObjectGenerator delegates the synthesis of values for certain types to custom generators.

Custom Generators

A custom generator is any Java class that implements the CustomGenerator interface. Such a class may be uploaded by any of the tools in the JWalk 1.1 Tool Suite, to take control of how input values are synthesised during testing. Four standard custom generators are always uploaded by the JWalk tools in the standard configuration, as delegates of ObjectGenerator. These include:

  • StringGenerator - which generates a conventional sequence of strings
  • InterfaceGenerator - which generates concrete instances for interfaces
  • EnumGenerator - which generates all values of enumerated types
  • PlatformGenerator - which generates values with platfom-specific ranges
These take control of how specific types are instantiated. For example, there is no automatic means in Java for supplying instances of interface types, so this must be handled by a custom generator, which maps the interface type to a suitable concrete type and then requests an instance of this instead. Again, for the String type, the default generation strategy would produce an empty string, so a custom strategy is used to cycle through a series of 26 conventional strings, sorted alphabetically.

Reasons for Customisation

If the tester supplies a home-made custom generator, this can be made to synthesise values for particular types in any order desired. In this way, it is possible to tailor the testing process to meet the expectations of the test class. Reasons for doing this might include:

  • wanting to re-order the default sequence of test values
  • wanting to specify a particular value sequence for a given type
  • wanting to specify values for interface and abstract class types
  • wanting to redirect standard input to read from a text file
  • wanting to define closely the environment of the test class
For example, the default ObjectGenerator synthesises int values in monotonically increasing order, starting from 1. (The default strategy tries to supply values that will cause object states to change, rather than stay unchanged; also the default strategy supplies a different value on each request, so the user can see differences in method results). However, you might want to test a class that expects a different pattern of integer inputs on successive method calls. In order to test the indexing behaviour of classes like Vector or ArrayList, you might prefer to generate pairs of repeating indices, starting with 0, in order to verify that put() and get() operations store and fetch the same value at the same index. For this, you might invent an IndexGenerator, which takes control of synthesising int values (see also below).

For example, you might have a test class whose methods read from standard input. This might be the case when testing the main class for your application. Testing such a class exhaustively in the standard tool configuration would require the tester to keep on entering input data at the console, which is tedious. However, if you instead upload the RedirectInGenerator, this redirects standard input to read from a data file called input.txt, which by default contains lines of text and integers. You can edit this file to provide whatever inputs your test class expects (and maybe some which it does not expect, to check your data validation). This custom generator is provided in the download bundle, in the package org.jwalk.gen, and you can upload it from there into the tool.

For example, you might have a test class that needs to be plugged in to a particular local environment. Your test class has a constructor or method which sets up links to objects in the environment. You can therefore provide a special ContextGenerator, which returns instances of these context-objects, suitably initialised, for your test class. Alternatively, you can arrange for your ContextGenerator to create stub-, or mock-objects which have the same name and API as the real context-objects, but which are easier to set up and control for the purposes of testing. One strategy for doing this in Java is to have both the context-object and mock-object implement the same interface. Another is to override all methods of the context-object in the mock-object, replacing production behaviour with test behaviour.

Providing Custom Generators

If the tester wishes to supply original custom generators, the following conventions must be observed. The custom generator must satisfy the interface specified by CustomGenerator. In particular, it must implement the following abstract methods:

  • boolean canCreate(Class<?> type), reporting whether the generator can create values of the requested type;
  • Object nextValue(Class<?> type), to synthesise the next value of the requested type in the expected order; and
  • void setOwner(MasterGenerator generator), to register its owning MasterGenerator.
A CustomGenerator may also choose to supply an explicit default constructor (with no arguments). If none is explicitly provided, Java will provide one automatically, which initialises all attributes of the generator to default initial values, or user-defined initial values (see Java rules for initialising values of different types).

The Contract for boolean canCreate(Class<?> type)

This method reports whether the custom generator can synthesise instances of the requested type. It should return true for all the primitive, or class types that the custom generator can handle, that is, for which it can generate instances. This method is called internally, when deciding whether to delegate a request to the custom generator. The method should return true, if the custom generator creates instances of the requested type, and false otherwise.

When the standard MasterGenerator receives a request to synthesise a value of a particular type, it checks first whether any of its delegates can create this value. If so, it forwards the request to the first delegate found in its list of delegates that is capable of creating the value. The list of delegates is ordered like a stack, such that delegates that were added most recently take priority over delegates that were added earlier by the MasterGenerator.

The Contract for Object nextValue(Class<?> type)

This method returns the next value in sequence for the requested type. Whereas by default this would be a quasi-unique, monotonically increasing value on each invocation, custom generators are free to return values in other orders, so long as the order is completely deterministic and not random. So, the sequence could skip or re-order values, or revisit values that were returned on a previous invocation. Random synthesis must be avoided, since this would defeat the capability of the JWalk tools to learn predictable behaviour.

The requested type must be a type that is acceptable to this custom generator, i.e. one for which canCreate() returns true. If not, the custom generator should raise a GeneratorException reporting failure to synthesise a value for the given type. The result of this method is an object, an array, or a "boxed" primitive value.

The Contract for void setOwner(MasterGenerator generator)

This method registers a MasterGenerator as the owner of this custom generator, and is automatically called when this custom generator is added as a delegate of the MasterGenerator. In this way, a custom generator may communicate with the master which owns it. This can be useful in the design of certain custom generators, which intercept requests to handle certain types, but translate these into requests for different, compatible types, delegating the modified requests back to their owner. An example is InterfaceGenerator, which maps from abstract interfaces to concrete classes, then recursively asks its owner to produce an instance of the concrete class. (Note: you should not delegate a request for the same type back to the owner - this would result in an infinite delegation loop!)

A custom generator is free to ignore its owner if it so chooses. Many custom generators need not communicate with their owner. In this case, a nullop implementation of the method should be provided.

Example Custom Generators

The following is an example custom generator. IndexGenerator takes control of how values are synthesised for the int type. Instead of a monotonically increasing sequence, it generates repeating pairs of integers, for use when testing array and sequence types, which may put and get values at the same index. Repeating pairs of indices are generated, so that it might be possible to observe that the value returned by a get() method is the value that was previously stored by a put() method at the same index.

IndexGenerator

import org.jwalk.GeneratorException;
import org.jwalk.gen.CustomGenerator;
import org.jwalk.gen.MasterGenerator;

public class IndexGenerator implements CustomGenerator {
  private int seed = -1;
  private boolean repeat = false;
  
  public boolean canCreate(Class<?> type) {
    return type == int.class;
  }
  
  public Object nextValue(Class<?> type) 
                             throws GeneratorException {
    if (type != int.class)
      throw new GeneratorException(type);
    if (repeat) {
      repeat = false;
      return new Integer(seed);  // uses old value
    }
    else {
      repeat = true;
      result = new Integer(++seed);  // uses new value
    }
  }  
  
  public void setOwner(MasterGenerator generator) {
  }   // nullop implementation
}

This IndexGenerator returns pairs of integer values in the sequence: {0, 0, 1, 1, 2, 2, 3, 3, ...}. It works by synthesising a new value if the flag repeat is set to false, otherwise it returns the previous value. The flag flips between false and true on each call. The result is returned as a boxed-up Integer object. This is un-boxed automatically when supplied as an argument to any method expecting an int index. Notice how the generator handles the synthesis of integers locally and does not need to communicate with its owner. The implementation of setOwner() is therefore a null operation.

This IndexGenerator also starts synthesizing from 0, to allow initial array indices to be accessed. The default sequence of integers normally starts from 1, because 0 is an identity element for arithmetical computations. JWalk's algebraic exploration will normally prune test paths that result in no change to the test class, so the standard value-synthesis algorithms try not to generate identity elements like 0. However, when you know that you will be using integers for accessing array indices, it makes more sense to start at zero.

The following is an example of a different kind of generator, which synthesizes instances of a particular concrete DefaultListModel type whenever an interface type ListModel is requested. This is the pattern to follow, when dealing with the synthesis of concrete values for abstract interface types. The supplied InterfaceGenerator (which is always loaded, by default) works this way for a number of commonly-used collection interfaces.

ListModelGenerator

import org.jwalk.GeneratorException;
import org.jwalk.gen.CustomGenerator;
import org.jwalk.gen.MasterGenerator;

public class ListModelGenerator implements CustomGenerator {
  private MasterGenerator owner;
  
  public boolean canCreate(Class<?> type) {
    return type == ListModel.class;
  }
  
  public Object nextValue(Class<?> type) 
                             throws GeneratorException {
    if (type != ListModel.class)
      throw new GeneratorException(type);
    // return a suitable concrete type
    return owner.nextValue(DefaultListModel.class); 
  }  
  
  public void setOwner(MasterGenerator generator) {
    owner = generator;
}

Here, the custom generator needs to communicate with its owner to handle the synthesis of the concrete type, recursively. The generator therefore declares a field for its owner, which is initialised by the method setOwner(). Note that this mutually-recursive style of delegating requests only works, because the requested type has been changed. If you ask the owner for the same type, this will result in a recursive loop, since the owning master generator will delegate back to this custom generator ad infinitum.

Regent Court, 211 Portobello, Sheffield S1 4DP, United Kingdom