/* * Copyright 2004-2015 the Seasar Foundation and the Others. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language * governing permissions and limitations under the License. */ package org.seasar.framework.unit; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.List; import org.junit.runner.Description; import org.junit.runner.Runner; import org.junit.runner.manipulation.Filter; import org.junit.runner.manipulation.Filterable; import org.junit.runner.manipulation.NoTestsRemainException; import org.junit.runner.manipulation.Sortable; import org.junit.runner.manipulation.Sorter; import org.junit.runner.notification.Failure; import org.junit.runner.notification.RunNotifier; import org.seasar.framework.container.annotation.tiger.Binding; import org.seasar.framework.container.annotation.tiger.BindingType; import org.seasar.framework.unit.impl.ConventionTestIntrospector; import org.seasar.framework.util.tiger.ReflectionUtil; /** * テストクラスに定義されたすべてのテストメソッドを扱うランナーです。 * * @author taedium */ public class S2TestClassMethodsRunner extends Runner implements Filterable, Sortable { private static class FailedBefore extends Exception { private static final long serialVersionUID = 1L; } /** {@link S2TestClassMethodsRunner}の振る舞いを提供するプロバイダ */ protected static Provider provider; /** テストメソッドのリスト */ protected final List<Method> testMethods; /** テストクラス */ protected final Class<?> testClass; /** * インスタンスを構築します。 * * @param clazz * テストクラス */ public S2TestClassMethodsRunner(final Class<?> clazz) { testClass = clazz; testMethods = getTestMethods(); } /** * このクラスを破棄します。 */ public static void dispose() { provider = null; } @Override public void run(final RunNotifier notifier) { try { runBefores(notifier); runMethods(notifier); } catch (final FailedBefore e) { } finally { runAfters(notifier); } } /** * テストメソッド群を実行します。 * * @param notifier * ノティファイアー */ protected void runMethods(final RunNotifier notifier) { if (testMethods.isEmpty()) { RunNotifierCompatibility.testAborted(notifier, getDescription(), new Exception("No runnable methods")); } for (final Method method : testMethods) { invokeTestMethod(method, notifier); } } /** * テストクラスの初期化メソッドを実行します。 * * @param notifier * ノティファイアー * @throws FailedBefore * 何らかの例外が発生した場合 */ protected void runBefores(final RunNotifier notifier) throws FailedBefore { try { final List<Method> befores = getBeforeClassMethods(); for (final Method before : befores) before.invoke(null); } catch (final InvocationTargetException e) { addFailure(e.getTargetException(), notifier); throw new FailedBefore(); } catch (final Throwable e) { addFailure(e, notifier); throw new FailedBefore(); } } /** * テストクラスの解放メソッドを実行します。 * * @param notifier * ノティフィアー */ protected void runAfters(final RunNotifier notifier) { final List<Method> afters = getAfterClassMethods(); for (final Method after : afters) try { after.invoke(null); } catch (final InvocationTargetException e) { addFailure(e.getTargetException(), notifier); } catch (final Throwable e) { addFailure(e, notifier); } } /** * テストの失敗を登録します。 * * @param targetException * 例外 * @param notifier * ノティフィアー */ protected void addFailure(final Throwable targetException, final RunNotifier notifier) { final Failure failure = new Failure(getDescription(), targetException); notifier.fireTestFailure(failure); } /** * テストメソッドのリストを返します。 * * @return テストメソッドのリスト */ protected List<Method> getTestMethods() { return getProvider().getTestMethods(testClass); } /** * テストクラスの初期化メソッドのリストを返します。 * * @return 初期化メソッドのリスト */ protected List<Method> getBeforeClassMethods() { return getProvider().getBeforeClassMethods(testClass); } /** * テストクラスの解放メソッドのリストを返します。 * * @return 解放メソッドのリスト */ protected List<Method> getAfterClassMethods() { return getProvider().getAfterClassMethods(testClass); } @Override public Description getDescription() { final Description spec = Description.createSuiteDescription(getName()); for (final Method method : testMethods) spec.addChild(methodDescription(method)); return spec; } /** * テストクラスの名前を返します。 * * @return テストクラスの名前 */ protected String getName() { return getTestClass().getName(); } /** * テストクラスのインスタンスを作成します。 * * @return テストクラスのインスタンス * @throws Exception * 何らかの例外が発生した場合 */ protected Object createTest() throws Exception { return getTestClass().getConstructor().newInstance(); } /** * テストメソッドを実行します。 * * @param method * テストメソッド * @param notifier * ノティファイアー */ protected void invokeTestMethod(final Method method, final RunNotifier notifier) { Object test = null; try { test = createTest(); } catch (final InvocationTargetException e) { RunNotifierCompatibility.testAborted(notifier, methodDescription(method), e.getCause()); return; } catch (final Exception e) { RunNotifierCompatibility.testAborted(notifier, methodDescription(method), e); return; } createMethodRunner(test, method, notifier).run(); } /** * テストメソッドランナーを作成します。 * * @param test * テスト * @param method * テストメソッド * @param notifier * ノティファイアー * @return テストメソッドランナー */ protected S2TestMethodRunner createMethodRunner(final Object test, final Method method, RunNotifier notifier) { return getProvider().createMethodRunner(test, method, notifier, methodDescription(method)); } /** * テストメソッドの名前を返します。 * * @param method * テストメソッド * @return テストメソッドの名前 */ protected String testName(final Method method) { return method.getName(); } /** * テストのディスクリプションを返します。 * * @param method * テストメソッド * @return ディスクリプション */ protected Description methodDescription(final Method method) { return Description.createTestDescription(getTestClass(), testName(method)); } public void filter(final Filter filter) throws NoTestsRemainException { for (final Iterator<Method> iter = testMethods.iterator(); iter .hasNext();) { final Method method = iter.next(); if (!filter.shouldRun(methodDescription(method))) { iter.remove(); } } if (testMethods.isEmpty()) { throw new NoTestsRemainException(); } } public void sort(final Sorter sorter) { Collections.sort(testMethods, new Comparator<Method>() { public int compare(final Method o1, final Method o2) { return sorter.compare(methodDescription(o1), methodDescription(o2)); } }); } /** * テストクラスを返します。 * * @return テストクラス */ protected Class<?> getTestClass() { return testClass; } /** * {@link S2TestClassMethodsRunner}の振る舞いを提供するプロバイダを返します。 * * @return 振る舞いを提供するプロバイダ */ protected static Provider getProvider() { if (provider == null) { provider = new DefaultProvider(); } return provider; } /** * {@link S2TestClassMethodsRunner}の振る舞いを提供するプロバイダを設定します。 * * @param p * 振る舞いを提供するプロバイダ */ protected static void setProvider(final Provider p) { provider = p; } /** * {@link S2TestClassMethodsRunner}の振る舞いを提供します。 * * @author taedium */ public interface Provider { /** * テストメソッドのリストを返します。 * * @param clazz * テストクラス * @return テストメソッドのリスト */ List<Method> getTestMethods(Class<?> clazz); /** * テストクラスの初期化メソッドのリストを返します。 * * @param clazz * テストクラス * @return 初期化メソッド */ List<Method> getBeforeClassMethods(Class<?> clazz); /** * テストクラスの解放メソッドのリストを返します。 * * @param clazz * テストクラス * @return 解放メソッドのリスト */ List<Method> getAfterClassMethods(Class<?> clazz); /** * テストメソッドランナーを作成します。 * * @param test * テストクラスのインスタンス * @param method * テストメソッド * @param notifier * ノティファイアー * @param description * ディスクリプション * @return テストメソッドランナー */ S2TestMethodRunner createMethodRunner(Object test, Method method, RunNotifier notifier, Description description); } /** * {@link S2TestClassMethodsRunner}の振る舞いを提供するデフォルトの実装クラスです。 * * @author taedium */ public static class DefaultProvider implements Provider { /** テストクラスのイントロスペクター */ protected S2TestIntrospector introspector; /** メソッドランナー */ protected Class<? extends S2TestMethodRunner> methodRunnerClass; /** メソッドランナーのコンストラクタ */ protected Constructor<? extends S2TestMethodRunner> constructor; /** * インスタンスを構築します。 */ public DefaultProvider() { final ConventionTestIntrospector conventionIntrospector = new ConventionTestIntrospector(); conventionIntrospector.init(); this.introspector = conventionIntrospector; setTestMethodRunnerClass(S2TestMethodRunner.class); } /** * テストクラスのイントロスペクターを設定します。 * * @param introspector * イントロスペクター */ @Binding(bindingType = BindingType.MAY) public void setTestIntrospector(final S2TestIntrospector introspector) { this.introspector = introspector; } /** * メソッドランナーのクラスを設定します。 * * @param methodRunnerClass * メソッドランナーのクラス */ @Binding(bindingType = BindingType.MAY) public void setTestMethodRunnerClass( final Class<? extends S2TestMethodRunner> methodRunnerClass) { this.methodRunnerClass = methodRunnerClass; this.constructor = ReflectionUtil.getConstructor(methodRunnerClass, Object.class, Method.class, RunNotifier.class, Description.class, S2TestIntrospector.class); } public List<Method> getTestMethods(final Class<?> clazz) { return introspector.getTestMethods(clazz); } public List<Method> getBeforeClassMethods(final Class<?> clazz) { return introspector.getBeforeClassMethods(clazz); } public List<Method> getAfterClassMethods(final Class<?> clazz) { return introspector.getAfterClassMethods(clazz); } public S2TestMethodRunner createMethodRunner(final Object test, final Method method, final RunNotifier notifier, final Description description) { return ReflectionUtil.newInstance(constructor, test, method, notifier, description, introspector); } } }