Sometimes the tester may want to take control of how JWalk synthesises test inputs. Likewise, the tester may wish to establish a particular context in which the test class is to be executed. Both are accomplished using Custom Generators.
JWalk creates test sequences by exploring and evaluating sequences of interleaved methods. Some of these methods need test input values for their arguments, which are supplied by the tool.
JWalk has a very simple strategy for synthesising such inputs. For each Java type, it tries to find the next value, which is distinct from all previous values that it synthesised for that same type.
For object instances, this is easy, since every object has a unique identity, encoded as a memory reference. For primitive types, JWalk synthesises monotonically increasing sequences of values, such as:
Integers: 1, 2, 3, 4, 5, ...
Doubles: 1.1, 2.2, 3.3, 4.4, ...
These values are chosen to make noticeable state-changes (we avoid 0 for this reason) and tend to trigger different behaviours when methods are interleaved in a different order.
JWalk uses a MasterGenerator to synthesise values of most object types and primitive types. This can create default objects and monotonically increasing sequences of primitive types boolean, byte, char, int, long, float and double (booleans cycle between false and true).
JWalk uses CustomGenerators to synthesise values of certain types that need more explicit control over the sequence of input values. If the MasterGenerator receives a request to synthesise one of these types, it delegates instead to the appropriate CustomGenerator.
The following four custom generators are always loaded:
StringGenerator - is used to generate non-null, non-empty String values of different kinds, in a predictable alphabetic sequence;
InterfaceGenerator - redirects requests to instantiate interfaces to create instead instances of suitable implementing classes;
EnumGenerator - is used to cycle through the enum values of an enum type (rather than create new objects);
PlatformGenerator - is used to synthesise values for platform-specific types, such as Locale, Date and Charset.
Other custom generators may be available; and the tester may also supply custom generators of their own.
A custom generator can be made to synthesise values for particular types in any order desired. It is possible to tailor the testing process to meet any 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 concrete 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
An example might be: IndexGenerator, a kind of generator that synthesises integer values in pairs: 0, 0, 1, 1, 2, 2... so that you can test sequences of put() and get() operations on indexed types such as ArrayList at the same index-value.
Another example might be: RedirectInGenerator, a kind of generator that redirects standard input to read instead from a file input.txt, so that you can test a class that normally reads from standard input, and control the sequence of values that it reads.
Another example might be: MockingGenerator, a kind of generator that produces mock-objects for certain production types that are too expensive to include in tests. The mock-objects override the methods of the production types, simulating their behaviour.
A custom generator is any user-supplied Java class that implements the CustomGenerator interface and whose name ends in "Generator". The following methods must be provided:
boolean canCreate(class<?> type)
Object nextValue(class<?> type)
void setOwner(MasterGenerator generator)
Contract for canCreate: this method must return true if the custom generator can synthesise values of the named type, and false otherwise. It is used internally by a MasterGenerator to decide whether to delegate a request to this generator.
Delegate generators are polled in order of recency. The most recently-added custom generator for a given type, or types, takes precedence over earlier generators for the same type(s) and the MasterGenerator.
Contract for nextValue: this method must return the next value in sequence for the requested type. Results may be presented in any order, or even in cyclic fashion, so long as this is deterministic and not random, so that JWalk may predict repeatable test outcomes.
The requested type must be one for which canCreate() returns true. If not, a GeneratorException is raised. Otherwise, this method returns an object of this type (possibly an array, or boxed primitive type).
Contract for setOwner: this registers a MasterGenerator as the owner of this generator. This reference can be used to redirect a modified request back to the owner. The custom generator may ask its owner to synthesise a substitute (e.g. a concrete type for an interface).
The redirected request must never ask for the same type (this would lead to infinite recursion). A custom generator may ignore its owner, if redirection is not needed.
Uploading a RedirectInGenerator to the custom generators
Launch the Generators dialog in the Custom Settings panel to add or remove custom generators (see figure). To upload a new custom generator:
Make the Location point to the root binary directory, under which your generator package lives.
Make the Name refer to the full package-qualified name of your chosen generator.
Click Add to add this generator to the list of already loaded custom generators.
To remove any custom generator, select this generator in the displayed list and click Remove to remove it.
All changes to the loaded generators are only made when you close the dialog using the OK button.
The following is an example custom generator.
This is the IndexGenerator we described earlier, whose purpose is to synthesise integers in repeating pairs, starting from zero, so that indexed types may test combinations of put() and get() at the same index.
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) { // return old value
repeat = false;
return new Integer(seed);
}
else { // return new value
repeat = true;
result = new Integer(++seed);
}
}
public void setOwner(
MasterGenerator generator) {
} // nullop
}
IndexGenerator works by flipping a repeat flag between true and false, and returns the previous seed value when this is true, otherwise it increments and returns the new seed. It has no need to delegate any requests back to a MasterGenerator, therefore setOwner() is a null operation.
The following is another example custom generator.
This is a ListModelGenerator, whose purpose is to synthesise a concrete DefaultListModel whenever a request is made for a ListModel, which is an interface. This generator makes use of its MasterGenerator owner.
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;
}
This time, setOwner() stores the supplied generator as the owner of this generator. Whenever this generator receives a request for a ListModel, it delegates to its owner and requests a DefaultListModel instead.