5 Minute Guide to JExample
JExample is an extensions of JUnit that improves defect localizaton. JExample introduces first-class dependencies. If test B depends on A, the return value of A can be used as B’s fixture. And if test A fails, then B and all other dependents of A are skipped and marked as white. In an upcoming XP 2008 paper, we show that JExample improves performance, and defect localization compared to plain JUnit tests.
In this tutorial, we are going to write a simple JExample test. We write a test for Java’s Stack class. Testing a stack is not trivial, there is an intrinsic dependency between pushing and popping elements. An element can not be popped without pushing it first. Intrinsic dependencies are evil. If there is a bug in push, both push’s and pop’s test fail, even if there is no bug in pop! In a large project, it may happen that a single bug causes dozens if not hundreds of tests to fail. Usually, only one of these tests covers the actual failure, whereas all others are false positives.
Download either the JAR file or use the Eclipse update-site to install the library plug-in.
We start with a simple test case, annotated with @RunWith. This annotations is provided by JUnit as an extension point for plugins. To run the test case with JExample, we pass JExample.class as the annotation’s value.
import ch.unibe.jexample.JExample;
import org.junit.runner.RunWith;
@RunWith(JExample.class)
public class StackTest {
}
Next we create our first test method: testEmpty creates an empty stack and asserts that its size is indeed zero. As JExample extends JUnit, the test method is annotated with @Test. But unlike with JUnit, the test yields its unit under test as return value! When executing the method, JExample caches that value for later use.
import static org.junit.Assert.*;
import java.util.Stack;
import org.junit.Test;
@Test
public Stack<Integer> testEmpty() {
Stack<Integer> stack = new Stack<Integer>();
assertEquals(0, stack.size());
return stack;
}
Now, we are going to write a consumer test that uses the above return value as its fixture: testPush takes an empty stack as input parameter, adds an element and, again, returns the under under test. The dependency is declared using JExample’s @Given annotation. To inject the result of testEmpty, we pass "testEmpty" as the annotation’s value. When executing the method, JExample will pass the cached result of testEmptyStack as input parameter to testPush. This is called dependency injection.
import ch.unibe.jexample.Given;
@Test
@Given("#testEmpty")
public Stack<Integer> testPush(Stack<Integer> stack) {
stack.push(42);
assertFalse(stack.isEmpty());
return stack;
}
The same is done for testPop, which depends on testPush.
@Test
@Given("#testPush(java.util.Stack)")
public void testPop(Stack<Integer> stack) {
int element = stack.pop();
assertEquals(42, element);
assertTrue(stack.isEmpty());
}
Now, let’s assume there is a bug in Stack.push(). The framework will run testEmpty, inject its return value into testPush, run it—which fails!—and thus skip testPop. Given these results we can quickly locate the bug.
More information is available on the JExampe wiki pages…
April 29th, 2008 at 01:40
[...] 5 Minute Guide to JExample [...]
November 6th, 2008 at 21:58
a very interesting and nice opportunity indeed to create chains of logical dependencies among test cases.
i’d never doubt of its performance increase