Lightest Documentation

This documentation provides some useful reference material related to using Lightest. For a more hands-on reference, try the Lightest tutorial!

What Is Lightest?

Lightest is a task-oriented functional and integration test automation framework built on Groovy and TestNG. It's a "framework framework" that is intended to greatly simplify the process of creating your own custom framework by implementing the testcase management, execution, and reporting for you - all you do is define the test environment and write the tasks!

You might want to try Lightest if:

The Lightest project is hosted on Google Code. You may download releases, browse the source, or file issues there.

Running Lightest

The Lightest framework comes with a runner packaged in an executable JAR. The standalone distribution includes all required dependencies; no external libraries are needed. Tests are run from the command line by specifying a configuration file (see below for syntax) and either a list of Groovy test files (which subclass LightestTestCase, also see below) or a TestNG suite XML file which specifies the test classes and groups to run. The most important configuration to get right is the classPaths element; most other configurations have sensible defaults.

$ java -jar lightest-core-0.4-standalone.jar config.txt MyTest.groovy MyTest2.groovy
$ java -jar lightest-core-0.4-standalone.jar config.txt testng.xml

You can start the runner in interactive mode by providing the right switch:

$ java -jar lightest-core-0.4-standalone.jar --interactive config.txt MyTest.groovy

Lightest requires at least Java 1.5 . Surprisingly, you shouldn't need to install Groovy on your machine to run Lightest. However, you'll probably want to have a reasonably recent version (1.5.4 or later) to use when developing your tasks. If you don't have it, get it here!

The runner produces an HTML report, which in turn is generated from an XML report. HTML is probably the most convenient reporting format; however, it is possible to customize Lightest to create reports in other formats. See the tutorial for screenshots of the reports that are produced.

Configuration File Reference

The configuration file specified as the first argument to the runner follows a Groovy builder syntax, with the config element as its root node.

Element Description Example
classPaths

Contains path elements which enumerate the class paths that should be used to locate task, testcase, preference, and environment classes. By default, the current directory . is included in the path. There is no limit to the number of entries.

You will probably get an error if any class specified in a configuration cannot be loaded from the class paths set here.

config {
...
    classPaths {
        path ('/path/to/tasks')
        path ('../path/to/tests')
    }
...
}
outputDir Specifies the report output directory. The report files will be placed directly in this directory. If unspecified, by default the report will be created in the lightest-report directory in the directory where the test runner was invoked. If more than one directory is specified, only the first entry will be used.
config {
...
    outputDir ('/path/to/report')
...
}
prefs Specifies name-value pairs for preferences to be shared across all tests being configured in this run. The preferences must have a corresponding class, which has publicly-accessible properties to match the preference names. There should be only one prefs element.
config {
...
    prefs (class: 'my.package.Preferences') {
        timeout (5000)
        corpus ('main')
    }
...
}
envs

Enumerates test environments that are available to the tests being configured in this run. Tests may be run concurrently across these environments.

All environments correspond to a single class, but have name-value pairs which may differentiate them. Testcases and tasks are able to access these values for the purpose of interacting with their respective environment. These name-value pairs must match properties of the environment class. Each environment must have a unique String identifier.

There should be only one envs element. If none are specified, 3 default environments identified by unspecified1, unspecified2, and unspecified3 will be provided.

config {
...
    envs (class: 'my.package.Environment') {
        env (id: 'windows') {
            homeDir ('C:\\Documents and Settings\\me')
            appName ('my.app')
        }
        env (id: 'linux') {
            homeDir ('/home/me')
            appName ('my.app')
        }
        env (id: 'macos') {
            homeDir ('/Users/me')
            appName ('my.app')
        }
    }
...
}
reporters

You may specify reporter classes that will be used instead of the DefaultSummaryReporter and DefaultDetailsReporter to generate the report output. This class should implement at least one of org.testng.IReporter and ILightestReporter; implementing both is ok. Additional properties of each reporter may be specified here as well.

Classes that implement only IReporter will be wrapped with an adapter class that implements ILightestReporter fully, and support having the generateReport() method be called after each test run. To enable this behavior, set the updateEnabled property to true in the configuration; it is disabled by default. Additionally, "scheduling" behavior may be enabled by setting the scheduled property to true; it is disabled by default. The cooldown multiplier may then be adjusted by setting the cooldownFactor property to a positive number; the default value is 25.

Additionally, you may override the following built-in XML-based reporters - com.googlecode.lightest.core.XMLReporter, com.googlecode.lightest.core.PendingReporter, and org.testng.reporters.FailedReporter - by specifying the respective reporter role name with the role attribute. The following role name constants are defined in the Configuration class: XMLReporter; PendingReporter; FailedReporter .

config {
...
    reporters {
        reporter (class: 'my.package.Reporter') {
            updateEnabled (false)
            scheduled     (false)
        }
        reporter (class: 'my.package.Reporter2')
        reporter (class: 'my.package.Reporter3', role: 'XMLReporter') {
            prop3 ('value3')
            prop4 ('value4')
        }
    }
...
}
listeners Listeners can be registered with the TestNG engine directly. One or more custom listeners can be specified by class, which must implement org.testng.ITestNGListener; typically either ITestListener or ISuiteListener. Additional properties of each listener may be specified here.
config {
...
    listeners {
        listener (class: 'my.package.Listener') {
            prop1 ('value1')
            prop2 ('value2')
        }
        listener (class: 'my.package.Listener2')
    }
...
}
dispatcherAssignmentStrategy You may specify a strategy class that will be used instead of the default SimpleDispatcherAssignmentStrategy to decide how task dispatchers (which are associated with test environments) get assigned to testcase classes. This class must implement IDispatcherAssignmentStrategy, and may for convenience extend QueuedDispatcherAssignmentStrategy to handle concurrency. There should be only one dispatcherAssignmentStrategy element.
config {
...
    dispatcherAssignmentStrategy (class: 'my.package.Strategy')
...
}
taskDispatchStrategy

Sets the dispatch strategy to use instead of the default. The strategy must implement ITaskDispatchStrategy; if it also implements IInterruptibleTaskDispatchStrategy, the test runner will invoke interrupt() on the strategy when requested to enter interactive mode.

Bean properties can be specified on the strategy in this configuration.

config {
...
    taskDispatchStrategy (class: 'my.package.Strategy') {
        prop1 ('value1')
        prop2 ('value2')
    }
...
}

Core Classes Overview

SimpleApi

The SimpleApi class (com.googlecode.lightest.core.SimpleApi) provides a convenient way to define the set of tasks that are available to a given testcase. It can search one or more Java package paths for tasks by name, and returns the first task found. By default, all tasks in the current working directory of the test runner are included by SimpleApi instances.

Use addPackage(String packageName) to add a new package path to the API. For example, if your tasks are defined in the package com.sample.tasks, you could do:

def api = new SimpleApi()
api.addPackage('com.sample.tasks')

Or simply:

def api = new SimpleApi('com.sample.tasks')

SimpleApi implements IDomainSpecificApi, specifically its getTask(String name) method. You may use a custom implementation of this interface that doesn't obtain tasks by package at all, and uses some other mechanism.

LightestTestCase

All Lightest tests extend LightestTestCase (com.googlecode.lightest.core.LightestTestCase), which is responsible for properly interpreting tasks specified with the builder syntax in its test methods. Subclasses should call setApi(IDomainSpecificApi api) before invoking any tasks. This can be done in the constructor of the test class, or in a @Before method. In its simplest form:

class MyTest extends LightestTestCase {

    MyTest() {
        setApi(new SimpleApi())
    }
}

LightestTestCase's are run by the TestNG runner; you must always use the @Test annotation to mark test methods and @Before / @After to mark fixture setup and teardown methods, respectively. To make use of JUnit assertions, add the appropriate static import to the top of the file:

import static org.testng.AssertJUnit.*

You can access the test environment (implementing ITestEnvironment) assigned to the testcase in any given test method directly via the env property. You can access the preferences via prefs. You may also obtain either from the context, e.g. context.env:

    @Test
    void myTest() {
        def port = env.port  // equivalent to "context.env.port"
        ...
    }

You can add properties to the context in the scope of any test method as if it were a Map, and these can be accessed by any tasks that are invoked, also via the context. Each test method always starts with a fresh context. Note that some special property names are reserved, i.e. env and prefs .

LightestTask

Each task used in a testcase should have as its implementation a corresponding subclass of LightestTask (com.googlecode.lightest.core.LightestTask). The task is responsible for performing some action, and populating a result object (see the next section) depending on whether the action succeeded or failed. Subclasses of LightestTask only have to implement a single method, doPerform(ITaskResult result):

class MyTask extends LightestTask {

    void doPerform(ITaskResult result) {
        println 'Been there, done that.'
        result.fail()
    }
}

Three key properties are available for inspection in task classes - config, prefs, and env .

The config object is essentially a groovy.util.Node from which the parameters to the task may be accessed. Attributes are accessed using the .'@attributeName' notation, and values can be accessed with .nodeValue() . For example:

MyTest.groovy
    @Test
    void myTest() {
        MyTask (attr1: 'foo', attr2: 'bar, 'baz')
    }
MyTask.groovy
    void doPerform(ITaskResult result) {
        println config.'@attr1'     // prints "foo"
        println config.'@attr2'     // prints "bar"
        println config.nodeValue()  // prints "baz"
    }

Two attributes on config have special meaning in the context of Lightest - description and breakpoint. If set, the String representation of description will be automatically visible with the task in the report. If for a given task breakpoint is set to any value that evaluates to true, the test will pause at that task if the runner is in interactive mode. You don't have to do anything in the task definition for these features to work.

Any preference values specified in the configuration file may be accessed from prefs. And any environment values may be accessed from env. Also available is the context object.

ITaskResult

Executing a task always produces a ITaskResult (com.googlecode.lightest.core.ITaskResult). The values populated on this object will appear in the report for the test run. The important values to consider setting are status, message, detailedMessage, and links . The ITaskResult object is passed into the doPerform() method, and should be populated therein.

The status of the result is what determines whether the task succeeded or failed. By default, the status is set to indicate success (STATUS_OK). Setting it to anything else indicates failure at some level. In order of increasing severity, you can invoke flag(), fail(), or doom() on the result object to do this.

Use setMessage(String message) to set an informational message to be displayed in the report. In particular, display any information related to why the task may have failed.

Use setDetailedMessage(String detailedMessage) to set larger chunks of text that may have been produced in performing the task.

Finally, use addLink(String link) to add a link to related resources, such as documents or screenshots. The link text will appear in the report, exactly as specified using this method. Multiple links may be specified. Use addLink(TaskResultLink link) if you need greater control of the HTML rendering of the link.

Task Lifecycle

The following table describes the lifecycle of a Lightest task, which is an implementation of ITask (and typically, but not necessarily, a subclass of LightestTask). This may be useful to know if you want to inject custom logic at specific points of the lifecycle.

# Step Name Step Description
1 Build TaskNode A task is "invoked" in the test when a call for a missing method is encountered in the test. The test class' (i.e. LightestTestCase's) methodMissing() logic assumes the missing method call conforms to Groovy builder syntax, and builds a TaskNode representing the task its child tasks, if any.
2 Create Task Instance The test class' api object, which implements IDomainSpecificApi, is queried for an instance of the task. If a SimpleApi is being used, a new task is instantiated simply by invoking the task class' no-argument constructor.
3 Call configure()

The task instance's configure() method is called. The env, prefs, and context members are all available and correctly populated by this time, via the dispatcher. For instances of LightestTask, auto-wiring of task attributes to properties is performed in configure(); to employ logic that runs before the auto-wiring, override the method, and call super.configure() after the custom logic is finished.

Auto-wiring takes effect for String, int, and boolean properties. For each attribute in the TaskNode representing the task, if an identically named property has been declared in the test class, configure() will assign the value of the attribute to its corresponding same-named property, performing some basic type conversions along the way. For example if I have the following:

class MyTask extends LightestTask {
    boolean waitForSignal
    ...
}

and the task was invoked as:

MyTask (waitForSignal: 'false')

the boolean (not String!) value false would be assigned to the waitForSignal property in the task instance by configure(). In other words, the following would be true:

waitForSignal == config.'@waitForSignal'

The two exceptional cases are for the attributes name and value. These must always be referenced from the config object, because they have special meaning in the context of tasks. Use:

config.'@name'
config.'@value'
4 Dispatch Task dispatch() is invoked on the ITaskDispatchStrategy associated with the test, passing the configured task instance. In many cases (including the default case, where the SimpleTaskDispatchStrategy is in play), the strategy simply invokes the task's perform() method. Subclasses of LightestTask enjoy protection from exceptions being thrown from the task; their task logic is implemented within doPerform().
5 Perform Child Tasks If the task has any child tasks, and if the task succeeded, child tasks are now performed, starting at creating them via the api (they should already exist in the TaskNode tree). If the task failed, all child tasks are skipped.

Other Resources

- Haw-Bin Chai (hbchai @t gmail d0t com)