/* * 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.impl; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.Collections; import java.util.List; import java.util.Map; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Ignore; import org.junit.Test; import org.junit.Test.None; import org.seasar.framework.aop.Pointcut; import org.seasar.framework.aop.interceptors.MockInterceptor; import org.seasar.framework.container.AspectDef; import org.seasar.framework.container.factory.AspectDefFactory; import org.seasar.framework.env.Env; import org.seasar.framework.unit.Expression; import org.seasar.framework.unit.InternalTestContext; import org.seasar.framework.unit.S2TestIntrospector; import org.seasar.framework.unit.annotation.Mock; import org.seasar.framework.unit.annotation.Mocks; import org.seasar.framework.unit.annotation.PostBindFields; import org.seasar.framework.unit.annotation.PreUnbindFields; import org.seasar.framework.unit.annotation.Prerequisite; import org.seasar.framework.unit.annotation.RegisterNamingConvention; import org.seasar.framework.unit.annotation.RootDicon; import org.seasar.framework.unit.annotation.TxBehavior; import org.seasar.framework.unit.annotation.TxBehaviorType; import org.seasar.framework.unit.annotation.WarmDeploy; import org.seasar.framework.util.StringUtil; import org.seasar.framework.util.tiger.CollectionsUtil; /** * アノテーションを解釈してテストクラスを分析するイントロスペクターです。 * * @author taedium */ public class AnnotationTestIntrospector implements S2TestIntrospector { /** テストクラスの初期化メソッドに注釈可能なアノテーションクラス */ protected Class<? extends Annotation> beforeClassAnnotation = BeforeClass.class; /** テストクラスの解放メソッドに注釈可能なアノテーションクラス */ protected Class<? extends Annotation> afterClassAnnotation = AfterClass.class; /** 全テストケースに共通の初期化メソッドに注釈可能なアノテーションクラス */ protected Class<? extends Annotation> beforeAnnotation = Before.class; /** 全テストケースに共通の解放メソッドに注釈可能なアノテーションクラス */ protected Class<? extends Annotation> afterAnnotation = After.class; /** テストクラスのバインドフィールド直後のメソッドに注釈可能なアノテーションクラス */ protected Class<? extends Annotation> postBindFieldsAnnotation = PostBindFields.class; /** テストクラスのアンバインドフィールド直前のメソッドメソッドに注釈可能なアノテーションクラス */ protected Class<? extends Annotation> preUnbindFieldsAnnotation = PreUnbindFields.class; /** テストケースを無視する処理が有効かどうかを表すフラグ。デフォルトは<code>true</code> */ protected boolean enableIgnore = true; /** テストケースの事前条件チェック処理が有効かどうかを表すフラグ。デフォルトは<code>true</code> */ protected boolean enablePrerequisite = true; /** * テストクラスの初期化メソッドに注釈可能なアノテーションクラスを設定します。 * * @param beforeClassAnnotation * アノテーションクラス */ public void setBeforeClassAnnotation( final Class<? extends Annotation> beforeClassAnnotation) { this.beforeClassAnnotation = beforeClassAnnotation; } /** * テストクラスの解放メソッドに注釈可能なアノテーションクラスを設定します。 * * @param afterClassAnnotation * アノテーションクラス */ public void setAfterClassAnnotation( final Class<? extends Annotation> afterClassAnnotation) { this.afterClassAnnotation = afterClassAnnotation; } /** * 全テストケースに共通の初期化メソッドに注釈可能なアノテーションクラスを設定します。 * * @param beforeAnnotation * アノテーションクラス */ public void setBeforeAnnotation( final Class<? extends Annotation> beforeAnnotation) { this.beforeAnnotation = beforeAnnotation; } /** * 全テストケースに共通の初期化メソッドに注釈可能なアノテーションクラスを設定します。 * * @param afterAnnotation * アノテーションクラス */ public void setAfterAnnotation( final Class<? extends Annotation> afterAnnotation) { this.afterAnnotation = afterAnnotation; } /** * テストクラスのバインドフィールド直後のメソッドに注釈可能なアノテーションクラスを設定します。 * * @param postBindFieldsAnnotation * アノテーションクラス */ public void setPostBindFieldsAnnotation( Class<? extends Annotation> postBindFieldsAnnotation) { this.postBindFieldsAnnotation = postBindFieldsAnnotation; } /** * テストクラスのアンバインドフィールド直前のメソッドに注釈可能なアノテーションクラスを設定します。 * * @param preUnbindFieldsAnnotation * アノテーションクラス */ public void setPreUnbindFieldsAnnotation( Class<? extends Annotation> preUnbindFieldsAnnotation) { this.preUnbindFieldsAnnotation = preUnbindFieldsAnnotation; } /** * テストケースを無視する処理を有効とするかどうかを設定します。 * * @param enableIgnore * 無視する処理が有効の場合<code>true</code>、そうでない場合<code>false</code> */ public void setEnableIgnore(boolean enableIgnore) { this.enableIgnore = enableIgnore; } /** * テストケースの事前条件チェックの処理を有効とするかどうかを設定します。 * * @param enablePrerequisite * 事前条件チェックの処理が有効の場合<code>true</code>、そうでない場合<code>false</code> */ public void setEnablePrerequisite(boolean enablePrerequisite) { this.enablePrerequisite = enablePrerequisite; } public List<Method> getBeforeClassMethods(final Class<?> clazz) { return getAnnotatedMethods(clazz, beforeClassAnnotation); } public List<Method> getAfterClassMethods(final Class<?> clazz) { return getAnnotatedMethods(clazz, afterClassAnnotation); } public List<Method> getPostBindFieldsMethods(final Class<?> clazz) { return getAnnotatedMethods(clazz, postBindFieldsAnnotation); } public List<Method> getPreUnbindFieldsMethods(final Class<?> clazz) { return getAnnotatedMethods(clazz, preUnbindFieldsAnnotation); } public List<Method> getBeforeMethods(final Class<?> clazz) { return getAnnotatedMethods(clazz, beforeAnnotation); } public List<Method> getAfterMethods(final Class<?> clazz) { return getAnnotatedMethods(clazz, afterAnnotation); } public List<Method> getTestMethods(final Class<?> clazz) { return getAnnotatedMethods(clazz, Test.class); } /** * アノテーションが付与されたメソッドのリストを返します。 * * @param clazz * テストクラス * @param annotationClass * アノテーションクラス * @return アノテーションが付与されたメソッドのリスト */ protected List<Method> getAnnotatedMethods(Class<?> clazz, Class<? extends Annotation> annotationClass) { List<Method> results = IntrospectorUtil.getAnnotatedMethods(clazz, annotationClass); if (runsTopToBottom(annotationClass)) Collections.reverse(results); return results; } /** * クラスの階層構造の上位から実行するならば<code>true</code>を返します。 * * @param annotation * アノテーション * @return クラスの階層構造の上位から実行するならば<code>true</code> */ protected boolean runsTopToBottom(Class<? extends Annotation> annotation) { return annotation.equals(Before.class) || annotation.equals(BeforeClass.class); } public Method getEachBeforeMethod(final Class<?> clazz, final Method method) { return null; } public Method getEachAfterMethod(final Class<?> clazz, final Method method) { return null; } public Method getEachRecordMethod(final Class<?> clazz, final Method method) { return null; } public Class<? extends Throwable> expectedException(final Method method) { final Test annotation = method.getAnnotation(Test.class); if (annotation == null || annotation.expected() == None.class) { return null; } return annotation.expected(); } public long getTimeout(final Method method) { final Test annotation = method.getAnnotation(Test.class); if (annotation != null) { return annotation.timeout(); } return 0; } public boolean isIgnored(final Method method) { if (enableIgnore) { return method.isAnnotationPresent(Ignore.class); } return false; } public boolean isFulfilled(final Class<?> clazz, final Method method, final Object test) { if (!enablePrerequisite) { return true; } if (clazz.isAnnotationPresent(Prerequisite.class)) { final String source = clazz.getAnnotation(Prerequisite.class) .value(); final Expression exp = createExpression(source, method, test); if (!isFulfilled(exp)) { return false; } } if (method.isAnnotationPresent(Prerequisite.class)) { final String source = method.getAnnotation(Prerequisite.class) .value(); final Expression exp = createExpression(source, method, test); if (!isFulfilled(exp)) { return false; } } return true; } /** * 事前条件が満たされた場合<code>true</code>を返します。 * * @param expression * 事前条件を表す式 * @return 事前条件が満たされた場合<code>true</code>、満たされない場合<code>false</code> */ protected boolean isFulfilled(final Expression expression) { final Object result = expression.evaluateNoException(); if (expression.isMethodFailed()) { System.err.println(expression.getException()); return false; } expression.throwExceptionIfNecessary(); if (result instanceof Boolean && Boolean.class.cast(result)) { return true; } return false; } public boolean needsTransaction(final Class<?> clazz, final Method method) { final TxBehaviorType type = getTxBehaviorType(clazz, method); return type == null || type != TxBehaviorType.NONE; } public boolean requiresTransactionCommitment(final Class<?> clazz, final Method method) { final TxBehaviorType type = getTxBehaviorType(clazz, method); return type != null && type == TxBehaviorType.COMMIT; } /** * トランザクションの振る舞いを返します。 * * @param clazz * テストクラス * @param method * テストメソッド * @return トランザクションの振る舞い */ protected TxBehaviorType getTxBehaviorType(final Class<?> clazz, final Method method) { if (method.isAnnotationPresent(TxBehavior.class)) { return method.getAnnotation(TxBehavior.class).value(); } if (clazz.isAnnotationPresent(TxBehavior.class)) { return clazz.getAnnotation(TxBehavior.class).value(); } return null; } public boolean needsWarmDeploy(final Class<?> clazz, final Method method) { if (method.isAnnotationPresent(WarmDeploy.class)) { return method.getAnnotation(WarmDeploy.class).value(); } if (clazz.isAnnotationPresent(WarmDeploy.class)) { return clazz.getAnnotation(WarmDeploy.class).value(); } return true; } public boolean isRegisterNamingConvention(final Class<?> clazz, final Method method) { if (method.isAnnotationPresent(RegisterNamingConvention.class)) { return method.getAnnotation(RegisterNamingConvention.class).value(); } if (clazz.isAnnotationPresent(RegisterNamingConvention.class)) { return clazz.getAnnotation(RegisterNamingConvention.class).value(); } return true; } public void createMock(final Method method, final Object test, final InternalTestContext context) { final Mock mock = method.getAnnotation(Mock.class); if (mock != null) { createMock(mock, method, test, context); } else { final Mocks mocks = method.getAnnotation(Mocks.class); if (mocks != null) { for (final Mock each : mocks.value()) { createMock(each, method, test, context); } } } } /** * <code>mock</code>から{@link MockInterceptor モックインターセプター}を作成し、<code>context</code>に登録します。 * * @param mock * モックインターセプターの定義 * @param method * テストメソッド * @param test * テストクラスのインスタンス * @param context * S2JUnit4の内部的なテストコンテキスト */ protected void createMock(final Mock mock, final Method method, final Object test, final InternalTestContext context) { final MockInterceptor mi = new MockInterceptor(); if (!StringUtil.isEmpty(mock.returnValue())) { final Expression exp = createExpression(mock.returnValue(), method, test); mi.setReturnValue(exp.evaluate()); } if (!StringUtil.isEmpty(mock.throwable())) { final Expression exp = createExpression(mock.throwable(), method, test); final Object result = exp.evaluate(); mi.setThrowable(Throwable.class.cast(result)); } Pointcut pc = null; if (StringUtil.isEmpty(mock.pointcut())) { pc = AspectDefFactory.createPointcut(mock.target()); } else { pc = AspectDefFactory.createPointcut(mock.pointcut()); } final Object componentKey = StringUtil.isEmpty(mock.targetName()) ? mock .target() : mock.targetName(); final AspectDef aspectDef = AspectDefFactory.createAspectDef(mi, pc); context.addAspecDef(componentKey, aspectDef); context.addMockInterceptor(mi); } /** * 式を作成します。 * * @param source * 式の文字列表現 * @param method * テストメソッド * @param test * テストクラスのインスタンス * @return 式 */ protected Expression createExpression(final String source, final Method method, final Object test) { final Map<String, Object> ctx = CollectionsUtil.newHashMap(); ctx.put("ENV", Env.getValue()); ctx.put("method", method); return new OgnlExpression(source, test, ctx); } public String getRootDicon(final Class<?> clazz, final Method method) { if (method.isAnnotationPresent(RootDicon.class)) { return method.getAnnotation(RootDicon.class).value(); } if (clazz.isAnnotationPresent(RootDicon.class)) { return clazz.getAnnotation(RootDicon.class).value(); } return null; } }