package org.test4j.spec; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.test4j.module.core.utility.MessageHelper; import org.test4j.module.spring.utility.SpringModuleHelper; import org.test4j.spec.annotations.Mix; import org.test4j.spec.annotations.StoryFile; import org.test4j.spec.exceptions.ScenarioAssertError; import org.test4j.spec.exceptions.SkipStepException; import org.test4j.spec.inner.IScenario; import org.test4j.spec.inner.IScenarioStep; import org.test4j.spec.inner.ISpecMethod; import org.test4j.spec.inner.ISpecMethod.SpecMethodID; import org.test4j.spec.inner.ISpecPrinter; import org.test4j.spec.inner.StepType; import org.test4j.spec.printer.MoreSpecPrinter; import org.test4j.spec.scenario.ExceptionJSpecScenario; import org.test4j.spec.scenario.Story; import org.test4j.spec.scenario.TestScenario; import org.test4j.spec.scenario.step.SpecMethod; import org.test4j.spec.storypath.StoryPath; import org.test4j.tools.commons.AnnotationHelper; import org.test4j.tools.commons.ConfigHelper; import org.test4j.tools.commons.StringHelper; import org.test4j.tools.datagen.DataProviderIterator; /** * @author darui.wudr 2013-1-10 下午8:33:04 */ @SuppressWarnings({ "rawtypes", "unchecked" }) public class JSpecExecutorFactory implements ISpecExecutorFactory { @Override public Map<SpecMethodID, ISpecMethod> findMethodsInSpec(Class<? extends ISpec> clazz) { Map<SpecMethodID, ISpecMethod> allMethods = SpecMethod.findMethods(clazz); Mix mix = AnnotationHelper.getClassLevelAnnotation(Mix.class, clazz); if (mix == null) { return allMethods; } for (Class claz : mix.value()) { Map<SpecMethodID, ISpecMethod> stepMethods = SpecMethod.findMethods(claz); addMethods(allMethods, stepMethods); } return allMethods; } @Override public Map<String, Object> newSteps(ISpec spec) { Map<String, Object> stepsInstances = new HashMap<String, Object>(); Mix mix = AnnotationHelper.getClassLevelAnnotation(Mix.class, spec.getClass()); if (mix == null) { return stepsInstances; } for (Class<? extends Steps> claz : mix.value()) { try { Object instance = claz.newInstance(); SpringModuleHelper.setSpringBean(instance); if (instance instanceof Steps) { ((Steps) instance).setSharedData(spec.getSharedData()); } stepsInstances.put(claz.getName(), instance); } catch (Exception e) { String error = "new " + claz.getName() + " error, the Steps must have default Constructor. error:" + e.getMessage(); throw new RuntimeException(error, e); } } return stepsInstances; } private void addMethods(Map<SpecMethodID, ISpecMethod> allMethods, Map<SpecMethodID, ISpecMethod> stepMethods) { for (Map.Entry<SpecMethodID, ISpecMethod> entry : stepMethods.entrySet()) { if (allMethods.containsKey(entry.getKey())) { ISpecMethod existed = allMethods.get(entry.getKey()); ISpecMethod method = entry.getValue(); String info = String.format("duplicated muthod[%s] declared by class[%s] and class[%s].", existed.getMethodName(), existed.getClazzName(), method.getClazzName()); throw new RuntimeException(info); } allMethods.put(entry.getKey(), entry.getValue()); } } @Override public ISpecPrinter newSpecPrinter() { return new MoreSpecPrinter(); } @Override public DataProviderIterator<IScenario> findScenario(Class<? extends ISpec> clazz) { try { Set<Integer> indexs = findScenarioRunIndex(); StoryFile storyFile = AnnotationHelper.getClassLevelAnnotation(StoryFile.class, clazz); StoryPath storyPath = StoryPath.factory(clazz, storyFile); String encoding = this.getEncoding(storyFile); Story story = storyPath.getStory(storyFile, encoding); if (story == null || story.getScenarios() == null || story.getScenarios().size() == 0) { Exception e = new RuntimeException("no scenario defined in spec " + clazz.getName()); return ExceptionJSpecScenario.iterator(e); } DataProviderIterator<IScenario> it = new DataProviderIterator<IScenario>(); int index = 1; for (IScenario scenario : story.getScenarios()) { if (indexs == null || indexs.contains(index)) { TestScenario testScenario = new TestScenario(story.getBeforeScenario(), scenario, story.getAfterScenario()); it.data(testScenario); } index++; } return it; } catch (Throwable e) { return ExceptionJSpecScenario.iterator(e); } } public String getEncoding(StoryFile storyFile) { String default_encoding = ConfigHelper.getString("jspec.file.encoding", "utf8"); if (storyFile == null) { return default_encoding; } else { String encoding = storyFile.encoding(); return StringHelper.isBlankOrNull(encoding) ? default_encoding : encoding; } } /** * 需要被执行的场景序号,序号从1开始 */ public static final String SCENARIO_RUN_INDEX1 = "SIndex"; public static final String SCENARIO_RUN_INDEX2 = "si"; public static Set<Integer> findScenarioRunIndex() { String sIndexes = System.getProperty(SCENARIO_RUN_INDEX1); if (StringHelper.isBlankOrNull(sIndexes)) { sIndexes = System.getProperty(SCENARIO_RUN_INDEX2); } if (StringHelper.isBlankOrNull(sIndexes)) { return null; } if ("0".equals(sIndexes)) { return null; } String[] items = sIndexes.split(","); Set<Integer> set = new HashSet<Integer>(); for (String item : items) { try { Integer no = Integer.parseInt(item); set.add(no); } catch (Exception e) { // do nothing } } return set; } @Override public void runScenario(ISpec spec, IScenario scenario, Map<SpecMethodID, ISpecMethod> methods, ISpecPrinter printer) throws Throwable { scenario.validate(); List<IScenarioStep> steps = scenario.getSteps(); List<Throwable> assertErrors = new ArrayList<Throwable>(); Throwable suspend = null; for (IScenarioStep step : steps) { if (step.isSuspend() || suspend != null) { step.setError(SkipStepException.instance); continue; } Throwable error = this.runSingleStep(spec, step, methods); if (error != null) { assertErrors.add(error); step.setError(error); } if (step.getType() != StepType.Then) { suspend = error; } } printer.printScenario(spec, scenario); if (suspend != null) { throw suspend; } else if (assertErrors.size() != 0) { throw new ScenarioAssertError(scenario, assertErrors); } } /** * 执行测试场景的单个方法 * * @param spec * @param step * @param methods * @return */ private Throwable runSingleStep(ISpec spec, IScenarioStep step, Map<SpecMethodID, ISpecMethod> methods) { SpecMethodID id = step.getSpecMethodID(); if (methods.containsKey(id) == false) { throw new RuntimeException(String.format("can't find %s in class[%s]", id, spec.getClass().getName())); } ISpecMethod specMethod = methods.get(id); try { specMethod.execute(spec, step); return null; } catch (Throwable e) { String error = String.format("Invoke class (%s.java:1) method[%s] error:\n%s!", specMethod.getClazzName(), specMethod.getMethodName(), e.getMessage()); MessageHelper.error(error, e); return new RuntimeException(error, e); } } }