Skip to content

Instantly share code, notes, and snippets.

@stefanbirkner
Last active November 12, 2015 07:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save stefanbirkner/684aeca9308ed3128d0d to your computer and use it in GitHub Desktop.
Save stefanbirkner/684aeca9308ed3128d0d to your computer and use it in GitHub Desktop.

##About Rules and Runners

Stefan Birkner

@stefanbirkner

##JUnit – The Basics

public class MyTest {
  @Test
  public void successfulTest() {
    assertTrue(true);
  }

  @Test
  public void failingTest() {
    assertTrue(false);
  }

  @Test
  public void ignoredTest() {
    assumeTrue(false);
    assertTrue(false);
  }
}

##Setup – Basic Test

public class MathTest {
  @Test
  public void calculates_minimum() {
    int min = Math.min(3, 5);
    assertEquals(3, min);
  }
}

##Setup – Test Starter

public class TestStarter {
  public void main(String... args) {
    Result result = JUnitCore.runClasses(MathTest.class);
    if (result.wasSuccessful())
      System.out.println("Everything is fine.");
    else
      System.out.println("Test failed");
  }
}

##Output of Simple Test

Everything is fine.

##Failing Test

public class MathTest {
  @Test
  public void calculates_minimum() {
    int min = Math.min(3, 5);
    assertEquals(4, min);
  }
}

Output:

Test failed

Result

  • wasSuccessful() – true if all tests succeeded
  • getRunCount() – number of tests run
  • getRunTime() – how long did the entire suite run
  • getIgnoreCount() – number of ignored tests
  • getFailureCount() – number of failed tests
  • getFailures() – information about the failed tests

Failures

public class TestStarter {
  public void main(String... args) {
    Result result = JUnitCore.runClasses(MathTest.class);
    System.out.println(result.getFailures().get(0));
  }
}

Output:

calculates_minimum(MathTest): expected:<4> but was:<3>

##Failure

+-----------------------------+
|          Failure            |
+-----------------------------+
| description : Description   |
| thrownException : Throwable |
+-----------------------------+

##IDE Output

java.lang.AssertionError: 
Expected :4
Actual   :3
<Click to see difference>

at org.junit.Assert.fail(Assert.java:88)
at org.junit.Assert.failNotEquals(Assert.java:789)
at org.junit.Assert.assertEquals(Assert.java:118)
at org.junit.Assert.assertEquals(Assert.java:601)
at org.junit.Assert.assertEquals(Assert.java:588)
at MathTest.calculates_minimum(MathTest.java:9)
...

#RunListener

##Add RunListener

public class TestStarter {
  public void main(String... args) {
    JUnitCore core = new JUnitCore();
    core.addListener(new YourRunListener());
    core.run(MathTest.class);
  }
}

##RunListener

The RunListener has seven methods:

  • testRunStarted(Description description)
  • testRunFinished(Result result)
  • testStarted(Description description)
  • testFinished(Description description)
  • testFailure(Failure failure)
  • testAssumptionFailure(Failure failure)
  • testIgnored(Description description)

#Runner

##Runner

+-----------------------------------+
|              Runner               |
+-----------------------------------+
| getDescription() : Description    |
| run(inout notifier : RunNotifier) |
| testCount() : int                 |
+-----------------------------------+

##JUnitCore.runClasses(MathTest.class)

Request request = createRequestForClasses(MathTest.class):
Runner runner = request.getRunner();
RunNotifier runNotifier = createRunNotifier();
runner.run(runNotifier);

##Create Runners

AllDefaultPossibilitiesBuilder.runnerForClass(Class<?> testClass) throws Throwable {
    List<RunnerBuilder> builders = Arrays.asList(
            ignoredBuilder(),
            annotatedBuilder(),
            suiteMethodBuilder(),
            junit3Builder(),
            junit4Builder());

    for (RunnerBuilder each : builders) {
        Runner runner = each.safeRunnerForClass(testClass);
        if (runner != null) {
            return runner;
        }
    }
    return null;
}

##IgnoredBuilder

public class IgnoredBuilder extends RunnerBuilder {
    public Runner runnerForClass(Class<?> testClass) {
        if (testClass.getAnnotation(Ignore.class) != null) {
            return new IgnoredClassRunner(testClass);
        }
        return null;
    }
}

##IgnoredClassRunner

public class IgnoredClassRunner extends Runner {
    private final Class<?> clazz;

    public IgnoredClassRunner(Class<?> testClass) {
        clazz = testClass;
    }

    public void run(RunNotifier notifier) {
        notifier.fireTestIgnored(getDescription());
    }

    public Description getDescription() {
        return Description.createSuiteDescription(clazz);
    }
}

##JUnit4Builder

public class JUnit4Builder extends RunnerBuilder {
    @Override
    public Runner runnerForClass(Class<?> testClass) throws Throwable {
        return new BlockJUnit4ClassRunner(testClass);
    }
}

##BlockJUnit4ClassRunner – Checks

protected void collectInitializationErrors(List<Throwable> errors) {
    validatePublicVoidNoArgMethods(BeforeClass.class, true, errors);
    validatePublicVoidNoArgMethods(AfterClass.class, true, errors);
    validateClassRules(errors);
    applyValidators(errors);
    validateNoNonStaticInnerClass(errors);
    validateConstructor(errors);
    validateInstanceMethods(errors);
    validateFields(errors);
    validateMethods(errors);
}

VALIDATORS = Arrays.asList(new AnnotationsValidator(), new PublicClassValidator());

##BlockJUnit4ClassRunner – run

public void run(RunNotifier notifier) {
    EachTestNotifier testNotifier = new EachTestNotifier(notifier, getDescription());
    try {
        Statement statement = classBlock(notifier);
        statement.evaluate();
    } catch (AssumptionViolatedException e) {
        testNotifier.addFailedAssumption(e);
    } catch (StoppedByUserException e) {
        throw e;
    } catch (Throwable e) {
        testNotifier.addFailure(e);
    }
}

##Statement

public abstract class Statement {
    /**
     * Run the action, throwing a {@code Throwable} if anything goes wrong.
     */
    public abstract void evaluate() throws Throwable;
}

##classBlock

Statement statement = () -> runChildren(notifier);
if (!areAllChildrenIgnored()) {
    statement = withBeforeClasses(statement);
    statement = withAfterClasses(statement);
    statement = withClassRules(statement);
}
return statement;

##runChildren

BlockJUnit4ClassRunner extends ParentRunner<FrameworkMethod>

void runChildren(RunNotifier notifier) {
    for (FrameworkMethod each : getFilteredChildren()) {
        runChild(each, notifier);
    }
}

List<FrameworkMethod> getFilteredChildren() {
    return getTestClass().getAnnotatedMethods(Test.class);
}

void runChild(final FrameworkMethod method, RunNotifier notifier) {
    Description description = describeChild(method);
    if (isIgnored(method)) {
        notifier.fireTestIgnored(description);
    } else {
        runLeaf(methodBlock(method), description, notifier);
    }
}

##runLeaf

void runLeaf(Statement statement, Description description, RunNotifier notifier) {
    EachTestNotifier eachNotifier = new EachTestNotifier(notifier, description);
    eachNotifier.fireTestStarted();
    try {
        statement.evaluate();
    } catch (AssumptionViolatedException e) {
        eachNotifier.addFailedAssumption(e);
    } catch (Throwable e) {
        eachNotifier.addFailure(e);
    } finally {
        eachNotifier.fireTestFinished();
    }
}

##methodBlock

Object test = getTestClass().getOnlyConstructor().newInstance();
Statement statement = methodInvoker(method, test);
statement = possiblyExpectingExceptions(method, test, statement);
statement = withPotentialTimeout(method, test, statement);
statement = withBefores(method, test, statement);
statement = withAfters(method, test, statement);
statement = withRules(method, test, statement);
return statement;

##InvokeMethod

public class InvokeMethod extends Statement {
    private final FrameworkMethod testMethod;
    private final Object target;

    public InvokeMethod(FrameworkMethod testMethod, Object target) {
        this.testMethod = testMethod;
        this.target = target;
    }

    @Override
    public void evaluate() throws Throwable {
        testMethod.invokeExplosively(target);
    }
}

##FrameworkMethod.invokeExplosively

public Object invokeExplosively(final Object target, final Object... params)
        throws Throwable {
    return new ReflectiveCallable() {
        @Override
        protected Object runReflectiveCall() throws Throwable {
            return method.invoke(target, params);
        }
    }.run();
}

##More Runners

  • Suite
  • Parameterized
  • Theories
  • Enclosed
  • Categories

##Suite

@RunWith(Suite.class )
@Suite.SuiteClasses({A.class, B.class})
public class TestSuite {
}

##Suite

Suite extends ParentRunner<Runner>

public Suite(Class<?> klass, RunnerBuilder builder) throws InitializationError {
    this(builder, builder.runners(klass, getAnnotatedClasses(klass)));
}

protected Suite(Class<?> klass, List<Runner> runners) throws InitializationError {
    super(klass);
    this.runners = Collections.unmodifiableList(runners);
}

@Override
protected List<Runner> getChildren() {
    return runners;
}

@Override
protected Description describeChild(Runner child) {
    return child.getDescription();
}

@Override
protected void runChild(Runner runner, final RunNotifier notifier) {
    runner.run(notifier);
}

##SfbRunner

public class SfbRunner extends ParentRunner<FrameworkMethod> {
    public SfbRunner(Class<?> testClass) throws InitializationError {
        super(testClass);
    }

    @Override
    protected List<FrameworkMethod> getChildren() {
        List<FrameworkMethod> testMethods = new ArrayList<>();
        for (Method method : getTestClass().getJavaClass().getMethods()) {
            if (method.getName().startsWith("sfb")) {
                testMethods.add(new FrameworkMethod(method));
            }
        }
        return testMethods;
    }

    @Override
    protected Description describeChild(FrameworkMethod method) {
        return Description.createTestDescription(getTestClass().getJavaClass(),
                method.getName(), method.getAnnotations());
    }

    @Override
    protected void runChild(FrameworkMethod method, RunNotifier notifier) {
        try {
            runSingleTest(method, notifier);
        } catch (InstantiationException | InvocationTargetException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }

    private void runSingleTest(FrameworkMethod method, RunNotifier notifier) throws IllegalAccessException,
            InvocationTargetException, InstantiationException {
        Object test = getTestClass().getOnlyConstructor().newInstance();
        Statement statement = new I
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment