All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.junit.runners.BlockJUnit4ClassRunner Maven / Gradle / Ivy

There is a newer version: 2.12.15
Show newest version
package org.junit.runners;

import static org.junit.internal.runners.rules.RuleMemberValidator.RULE_METHOD_VALIDATOR;
import static org.junit.internal.runners.rules.RuleMemberValidator.RULE_VALIDATOR;

import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.Test.None;
import org.junit.internal.runners.model.ReflectiveCallable;
import org.junit.internal.runners.statements.ExpectException;
import org.junit.internal.runners.statements.Fail;
import org.junit.internal.runners.statements.FailOnTimeout;
import org.junit.internal.runners.statements.InvokeMethod;
import org.junit.internal.runners.statements.RunAfters;
import org.junit.internal.runners.statements.RunBefores;
import org.junit.rules.MethodRule;
import org.junit.rules.RunRules;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runner.notification.RunNotifier;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.InitializationError;
import org.junit.runners.model.MultipleFailureException;
import org.junit.runners.model.Statement;

/**
 * Implements the JUnit 4 standard test case class model, as defined by the
 * annotations in the org.junit package. Many users will never notice this
 * class: it is now the default test class runner, but it should have exactly
 * the same behavior as the old test class runner ({@code JUnit4ClassRunner}).
 * 

* BlockJUnit4ClassRunner has advantages for writers of custom JUnit runners * that are slight changes to the default behavior, however: * *

    *
  • It has a much simpler implementation based on {@link Statement}s, * allowing new operations to be inserted into the appropriate point in the * execution flow. * *
  • It is published, and extension and reuse are encouraged, whereas {@code * JUnit4ClassRunner} was in an internal package, and is now deprecated. *
*

* In turn, in 2009 we introduced {@link Rule}s. In many cases where extending * BlockJUnit4ClassRunner was necessary to add new behavior, {@link Rule}s can * be used, which makes the extension more reusable and composable. * * @since 4.5 */ public class BlockJUnit4ClassRunner extends ParentRunner { private final ConcurrentHashMap methodDescriptions = new ConcurrentHashMap(); /** * Creates a BlockJUnit4ClassRunner to run {@code klass} * * @throws InitializationError if the test class is malformed. */ public BlockJUnit4ClassRunner(Class klass) throws InitializationError { super(klass); } // // Implementation of ParentRunner // @Override protected void runChild(final FrameworkMethod method, RunNotifier notifier) { Description description = describeChild(method); if (isIgnored(method)) { notifier.fireTestIgnored(description); } else { runLeaf(methodBlock(method), description, notifier); } } /** * Evaluates whether {@link FrameworkMethod}s are ignored based on the * {@link Ignore} annotation. */ @Override protected boolean isIgnored(FrameworkMethod child) { return child.getAnnotation(Ignore.class) != null; } @Override protected Description describeChild(FrameworkMethod method) { Description description = methodDescriptions.get(method); if (description == null) { description = Description.createTestDescription(getTestClass().getJavaClass(), testName(method), method.getAnnotations()); methodDescriptions.putIfAbsent(method, description); } return description; } @Override protected List getChildren() { return computeTestMethods(); } // // Override in subclasses // /** * Returns the methods that run tests. Default implementation returns all * methods annotated with {@code @Test} on this class and superclasses that * are not overridden. */ protected List computeTestMethods() { return getTestClass().getAnnotatedMethods(Test.class); } @Override protected void collectInitializationErrors(List errors) { super.collectInitializationErrors(errors); validateNoNonStaticInnerClass(errors); validateConstructor(errors); validateInstanceMethods(errors); validateFields(errors); validateMethods(errors); } protected void validateNoNonStaticInnerClass(List errors) { if (getTestClass().isANonStaticInnerClass()) { String gripe = "The inner class " + getTestClass().getName() + " is not static."; errors.add(new Exception(gripe)); } } /** * Adds to {@code errors} if the test class has more than one constructor, * or if the constructor takes parameters. Override if a subclass requires * different validation rules. */ protected void validateConstructor(List errors) { validateOnlyOneConstructor(errors); validateZeroArgConstructor(errors); } /** * Adds to {@code errors} if the test class has more than one constructor * (do not override) */ protected void validateOnlyOneConstructor(List errors) { if (!hasOneConstructor()) { String gripe = "Test class should have exactly one public constructor"; errors.add(new Exception(gripe)); } } /** * Adds to {@code errors} if the test class's single constructor takes * parameters (do not override) */ protected void validateZeroArgConstructor(List errors) { if (!getTestClass().isANonStaticInnerClass() && hasOneConstructor() && (getTestClass().getOnlyConstructor().getParameterTypes().length != 0)) { String gripe = "Test class should have exactly one public zero-argument constructor"; errors.add(new Exception(gripe)); } } private boolean hasOneConstructor() { return getTestClass().getJavaClass().getConstructors().length == 1; } /** * Adds to {@code errors} for each method annotated with {@code @Test}, * {@code @Before}, or {@code @After} that is not a public, void instance * method with no arguments. */ @Deprecated protected void validateInstanceMethods(List errors) { validatePublicVoidNoArgMethods(After.class, false, errors); validatePublicVoidNoArgMethods(Before.class, false, errors); validateTestMethods(errors); if (computeTestMethods().size() == 0) { errors.add(new Exception("No runnable methods")); } } protected void validateFields(List errors) { RULE_VALIDATOR.validate(getTestClass(), errors); } private void validateMethods(List errors) { RULE_METHOD_VALIDATOR.validate(getTestClass(), errors); } /** * Adds to {@code errors} for each method annotated with {@code @Test}that * is not a public, void instance method with no arguments. */ protected void validateTestMethods(List errors) { validatePublicVoidNoArgMethods(Test.class, false, errors); } /** * Returns a new fixture for running a test. Default implementation executes * the test class's no-argument constructor (validation should have ensured * one exists). */ protected Object createTest() throws Exception { return getTestClass().getOnlyConstructor().newInstance(); } /** * Returns the name that describes {@code method} for {@link Description}s. * Default implementation is the method's name */ protected String testName(FrameworkMethod method) { return method.getName(); } /** * Returns a Statement that, when executed, either returns normally if * {@code method} passes, or throws an exception if {@code method} fails. * * Here is an outline of the default implementation: * *

    *
  • Invoke {@code method} on the result of {@code createTest()}, and * throw any exceptions thrown by either operation. *
  • HOWEVER, if {@code method}'s {@code @Test} annotation has the {@code * expecting} attribute, return normally only if the previous step threw an * exception of the correct type, and throw an exception otherwise. *
  • HOWEVER, if {@code method}'s {@code @Test} annotation has the {@code * timeout} attribute, throw an exception if the previous step takes more * than the specified number of milliseconds. *
  • ALWAYS run all non-overridden {@code @Before} methods on this class * and superclasses before any of the previous steps; if any throws an * Exception, stop execution and pass the exception on. *
  • ALWAYS run all non-overridden {@code @After} methods on this class * and superclasses after any of the previous steps; all After methods are * always executed: exceptions thrown by previous steps are combined, if * necessary, with exceptions from After methods into a * {@link MultipleFailureException}. *
  • ALWAYS allow {@code @Rule} fields to modify the execution of the * above steps. A {@code Rule} may prevent all execution of the above steps, * or add additional behavior before and after, or modify thrown exceptions. * For more information, see {@link TestRule} *
* * This can be overridden in subclasses, either by overriding this method, * or the implementations creating each sub-statement. */ protected Statement methodBlock(FrameworkMethod method) { Object test; try { test = new ReflectiveCallable() { @Override protected Object runReflectiveCall() throws Throwable { return createTest(); } }.run(); } catch (Throwable e) { return new Fail(e); } 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; } // // Statement builders // /** * Returns a {@link Statement} that invokes {@code method} on {@code test} */ protected Statement methodInvoker(FrameworkMethod method, Object test) { return new InvokeMethod(method, test); } /** * Returns a {@link Statement}: if {@code method}'s {@code @Test} annotation * has the {@code expecting} attribute, return normally only if {@code next} * throws an exception of the correct type, and throw an exception * otherwise. */ protected Statement possiblyExpectingExceptions(FrameworkMethod method, Object test, Statement next) { Test annotation = method.getAnnotation(Test.class); return expectsException(annotation) ? new ExpectException(next, getExpectedException(annotation)) : next; } /** * Returns a {@link Statement}: if {@code method}'s {@code @Test} annotation * has the {@code timeout} attribute, throw an exception if {@code next} * takes more than the specified number of milliseconds. */ @Deprecated protected Statement withPotentialTimeout(FrameworkMethod method, Object test, Statement next) { long timeout = getTimeout(method.getAnnotation(Test.class)); if (timeout <= 0) { return next; } return FailOnTimeout.builder() .withTimeout(timeout, TimeUnit.MILLISECONDS) .build(next); } /** * Returns a {@link Statement}: run all non-overridden {@code @Before} * methods on this class and superclasses before running {@code next}; if * any throws an Exception, stop execution and pass the exception on. */ protected Statement withBefores(FrameworkMethod method, Object target, Statement statement) { List befores = getTestClass().getAnnotatedMethods( Before.class); return befores.isEmpty() ? statement : new RunBefores(statement, befores, target); } /** * Returns a {@link Statement}: run all non-overridden {@code @After} * methods on this class and superclasses before running {@code next}; all * After methods are always executed: exceptions thrown by previous steps * are combined, if necessary, with exceptions from After methods into a * {@link MultipleFailureException}. */ protected Statement withAfters(FrameworkMethod method, Object target, Statement statement) { List afters = getTestClass().getAnnotatedMethods( After.class); return afters.isEmpty() ? statement : new RunAfters(statement, afters, target); } private Statement withRules(FrameworkMethod method, Object target, Statement statement) { List testRules = getTestRules(target); Statement result = statement; result = withMethodRules(method, testRules, target, result); result = withTestRules(method, testRules, result); return result; } private Statement withMethodRules(FrameworkMethod method, List testRules, Object target, Statement result) { for (org.junit.rules.MethodRule each : getMethodRules(target)) { if (!testRules.contains(each)) { result = each.apply(result, method, target); } } return result; } private List getMethodRules(Object target) { return rules(target); } /** * @param target the test case instance * @return a list of MethodRules that should be applied when executing this * test */ protected List rules(Object target) { List rules = getTestClass().getAnnotatedMethodValues(target, Rule.class, MethodRule.class); rules.addAll(getTestClass().getAnnotatedFieldValues(target, Rule.class, MethodRule.class)); return rules; } /** * Returns a {@link Statement}: apply all non-static fields * annotated with {@link Rule}. * * @param statement The base statement * @return a RunRules statement if any class-level {@link Rule}s are * found, or the base statement */ private Statement withTestRules(FrameworkMethod method, List testRules, Statement statement) { return testRules.isEmpty() ? statement : new RunRules(statement, testRules, describeChild(method)); } /** * @param target the test case instance * @return a list of TestRules that should be applied when executing this * test */ protected List getTestRules(Object target) { List result = getTestClass().getAnnotatedMethodValues(target, Rule.class, TestRule.class); result.addAll(getTestClass().getAnnotatedFieldValues(target, Rule.class, TestRule.class)); return result; } private Class getExpectedException(Test annotation) { if (annotation == null || annotation.expected() == None.class) { return null; } else { return annotation.expected(); } } private boolean expectsException(Test annotation) { return getExpectedException(annotation) != null; } private long getTimeout(Test annotation) { if (annotation == null) { return 0; } return annotation.timeout(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy