JUnit4DescriptionGenerator.java

  1. package org.jbehave.core.junit;

  2. import java.lang.annotation.Annotation;
  3. import java.lang.reflect.Method;
  4. import java.util.ArrayList;
  5. import java.util.Arrays;
  6. import java.util.List;
  7. import java.util.function.Function;

  8. import org.jbehave.core.annotations.AfterScenario.Outcome;
  9. import org.jbehave.core.annotations.ScenarioType;
  10. import org.jbehave.core.annotations.Scope;
  11. import org.jbehave.core.configuration.Configuration;
  12. import org.jbehave.core.configuration.Keywords;
  13. import org.jbehave.core.embedder.AllStepCandidates;
  14. import org.jbehave.core.embedder.PerformableTree;
  15. import org.jbehave.core.embedder.PerformableTree.ExamplePerformableScenario;
  16. import org.jbehave.core.embedder.PerformableTree.PerformableScenario;
  17. import org.jbehave.core.embedder.PerformableTree.PerformableStory;
  18. import org.jbehave.core.model.GivenStories;
  19. import org.jbehave.core.model.Lifecycle;
  20. import org.jbehave.core.model.Scenario;
  21. import org.jbehave.core.model.Story;
  22. import org.jbehave.core.steps.BeforeOrAfterStep;
  23. import org.jbehave.core.steps.ConditionalStepCandidate;
  24. import org.jbehave.core.steps.StepCandidate;
  25. import org.jbehave.core.steps.StepType;
  26. import org.junit.runner.Description;

  27. public class JUnit4DescriptionGenerator {

  28.     private final TextManipulator textManipulator = new TextManipulator();

  29.     private final Configuration configuration;
  30.     private final AllStepCandidates allStepCandidates;
  31.     private String previousNonAndStep;
  32.     private int testCases;

  33.     private final JUnit4Test junitTestMeta;

  34.     public JUnit4DescriptionGenerator(AllStepCandidates allStepCandidates, Configuration configuration) {
  35.         this.configuration = configuration;
  36.         this.allStepCandidates = allStepCandidates;
  37.         this.junitTestMeta = new JUnit4Test() {
  38.             @Override
  39.             public Class<? extends Annotation> annotationType() {
  40.                 return JUnit4Test.class;
  41.             }
  42.         };
  43.     }

  44.     public List<Description> createDescriptionsFrom(PerformableTree performableTree) {
  45.         List<Description> storyDescriptions = new ArrayList<>();
  46.         for (PerformableStory performableStory : performableTree.getRoot().getStories()) {
  47.             if (!performableStory.isExcluded()) {
  48.                 Story story = performableStory.getStory();
  49.                 Lifecycle lifecycle = story.getLifecycle();
  50.                 Description storyDescription = createDescriptionForStory(story);
  51.                 addBeforeOrAfterStep(allStepCandidates.getBeforeStorySteps(false), storyDescription,
  52.                         "@BeforeStory: ");
  53.                 addSteps(storyDescription, lifecycle.getBeforeSteps(Scope.STORY));
  54.                 if (story.hasGivenStories()) {
  55.                     insertGivenStories(story.getGivenStories(), storyDescription);
  56.                 }
  57.                 List<PerformableScenario> scenarios = performableStory.getScenarios();
  58.                 for (Description scenarioDescription : getScenarioDescriptions(lifecycle, scenarios)) {
  59.                     storyDescription.addChild(scenarioDescription);
  60.                 }
  61.                 addSteps(storyDescription, lifecycle.getAfterSteps(Scope.STORY, Outcome.ANY));
  62.                 addBeforeOrAfterStep(allStepCandidates.getAfterStorySteps(false), storyDescription,
  63.                         "@AfterStory: ");
  64.                 storyDescriptions.add(storyDescription);
  65.             }
  66.         }
  67.         return storyDescriptions;
  68.     }

  69.     public Description createDescriptionsFrom(Lifecycle lifecycle, PerformableScenario performableScenario) {
  70.         Scenario scenario = performableScenario.getScenario();
  71.         Description scenarioDescription = createDescriptionForScenario(scenario);
  72.         if (performableScenario.hasExamples() && !scenario.getGivenStories().requireParameters()) {
  73.             insertDescriptionForExamples(lifecycle, performableScenario, scenarioDescription);
  74.         } else {
  75.             addScenarioSteps(lifecycle, ScenarioType.NORMAL, scenario, scenarioDescription);
  76.         }
  77.         return scenarioDescription;
  78.     }

  79.     private void addScenarioSteps(Lifecycle lifecycle, ScenarioType scenarioType, Scenario scenario,
  80.             Description scenarioDescription) {
  81.         addBeforeOrAfterScenarioStep(allStepCandidates::getBeforeScenarioSteps, scenarioType, scenarioDescription,
  82.                 "@BeforeScenario: ");
  83.         addSteps(scenarioDescription, lifecycle.getBeforeSteps(Scope.SCENARIO));
  84.         if (scenario.hasGivenStories()) {
  85.             insertGivenStories(scenario.getGivenStories(), scenarioDescription);
  86.         }
  87.         addScenarioSteps(lifecycle, scenarioDescription, scenario);
  88.         addSteps(scenarioDescription, lifecycle.getAfterSteps(Scope.SCENARIO, Outcome.ANY));
  89.         addBeforeOrAfterScenarioStep(allStepCandidates::getAfterScenarioSteps, scenarioType, scenarioDescription,
  90.                 "@AfterScenario: ");
  91.     }

  92.     private void addScenarioSteps(Lifecycle lifecycle, Description scenarioDescription, Scenario scenario) {
  93.         List<String> beforeSteps = lifecycle.getBeforeSteps(Scope.STEP);
  94.         List<String> afterSteps = lifecycle.getAfterSteps(Scope.STEP);
  95.         previousNonAndStep = null;
  96.         String tempPreviousNonAndStep = null;
  97.         for (String scenarioStep : scenario.getSteps()) {
  98.             addSteps(scenarioDescription, beforeSteps);
  99.             previousNonAndStep = tempPreviousNonAndStep;
  100.             addStep(scenarioDescription, scenarioStep);
  101.             tempPreviousNonAndStep = previousNonAndStep;
  102.             addSteps(scenarioDescription, afterSteps);
  103.         }
  104.     }

  105.     private void addBeforeOrAfterScenarioStep(Function<ScenarioType, List<BeforeOrAfterStep>> stepsProvider,
  106.             ScenarioType scenarioType, Description description, String stepName) {
  107.         List<BeforeOrAfterStep> beforeOrAfterSteps = new ArrayList<>();
  108.         beforeOrAfterSteps.addAll(stepsProvider.apply(scenarioType));
  109.         beforeOrAfterSteps.addAll(stepsProvider.apply(ScenarioType.ANY));
  110.         addBeforeOrAfterStep(beforeOrAfterSteps, description, stepName);
  111.     }

  112.     private void addBeforeOrAfterStep(List<BeforeOrAfterStep> beforeOrAfterSteps, Description description,
  113.             String stepPrefix) {
  114.         beforeOrAfterSteps.forEach(steps -> {
  115.             testCases++;
  116.             Method method = steps.getMethod();
  117.             String stepName = uniquify(stepPrefix + steps.getMethod().getName());
  118.             Description testDescription = Description.createTestDescription(method.getDeclaringClass(), stepName,
  119.                     junitTestMeta);
  120.             description.addChild(testDescription);
  121.         });
  122.     }

  123.     public String uniquify(String string) {
  124.         return textManipulator.uniquify(string);
  125.     }

  126.     public int getTestCases() {
  127.         return testCases;
  128.     }

  129.     private void insertGivenStories(GivenStories givenStories, Description parentDescription) {
  130.         for (String path : givenStories.getPaths()) {
  131.             addGivenStory(parentDescription, path);
  132.         }
  133.     }

  134.     private void addGivenStory(Description parentDescription, String path) {
  135.         parentDescription.addChild(Description.createSuiteDescription(uniquify(getFilename(path)), junitTestMeta));
  136.         testCases++;
  137.     }

  138.     private String getFilename(String path) {
  139.         return path.substring(path.lastIndexOf('/') + 1).split("#")[0];
  140.     }

  141.     private void insertDescriptionForExamples(Lifecycle lifecycle, PerformableScenario performableScenario,
  142.             Description scenarioDescription) {
  143.         Scenario scenario = performableScenario.getScenario();
  144.         for (ExamplePerformableScenario examplePerformableScenario : performableScenario.getExamples()) {
  145.             Description exampleRowDescription = Description.createSuiteDescription(
  146.                     configuration.keywords().examplesTableRow() + " " + examplePerformableScenario.getParameters());
  147.             scenarioDescription.addChild(exampleRowDescription);
  148.             addScenarioSteps(lifecycle, ScenarioType.EXAMPLE, scenario, exampleRowDescription);
  149.         }
  150.     }

  151.     private void addSteps(Description description, List<String> steps) {
  152.         previousNonAndStep = null;
  153.         steps.forEach(step -> addStep(description, step));
  154.     }

  155.     private void addStep(Description description, String step) {
  156.         String stringStepOneLine = stripLinebreaks(step);
  157.         StepCandidate matchingStep = findMatchingStep(step);
  158.         if (matchingStep == null) {
  159.             addNonExistingStep(description, stringStepOneLine, step);
  160.         } else {
  161.             addExistingStep(description, stringStepOneLine, matchingStep);
  162.         }
  163.     }

  164.     private void addExistingStep(Description description, String stringStepOneLine, StepCandidate matchingStep) {
  165.         if (matchingStep.isComposite()) {
  166.             addCompositeSteps(description, stringStepOneLine, matchingStep);
  167.         } else if (matchingStep instanceof ConditionalStepCandidate) {
  168.             addConditionalStep(description, stringStepOneLine);
  169.         } else {
  170.             addRegularStep(description, stringStepOneLine, matchingStep);
  171.         }
  172.     }

  173.     private void addNonExistingStep(Description description, String stringStepOneLine, String stepAsString) {
  174.         Keywords keywords = configuration.keywords();
  175.         if (keywords.isIgnorableStep(stepAsString)) {
  176.             if (isStep(keywords.stepWithoutStartingWord(stepAsString, StepType.IGNORABLE))) {
  177.                 addIgnorableStep(description, stringStepOneLine);
  178.             }
  179.         } else {
  180.             addPendingStep(description, stringStepOneLine);
  181.         }
  182.     }

  183.     private boolean isStep(String stepAsString) {
  184.         Keywords keywords = configuration.keywords();
  185.         for (String stepStartingWord : keywords.startingWordsByType().values()) {
  186.             if (keywords.stepStartsWithWord(stepAsString, stepStartingWord)) {
  187.                 return true;
  188.             }
  189.         }
  190.         return false;
  191.     }

  192.     private void addIgnorableStep(Description description, String stringStep) {
  193.         testCases++;
  194.         description.addChild(Description.createSuiteDescription(stringStep));
  195.     }

  196.     private void addPendingStep(Description description, String stringStep) {
  197.         testCases++;
  198.         description.addChild(Description.createSuiteDescription(uniquify("[PENDING] " + stringStep)));
  199.     }

  200.     private void addConditionalStep(Description description, String stringStep) {
  201.         testCases++;
  202.         description.addChild(Description.createSuiteDescription(uniquify(stringStep)));
  203.     }

  204.     private void addRegularStep(Description description, String stringStep, StepCandidate step) {
  205.         testCases++;
  206.         // JUnit and the Eclipse JUnit view needs to be touched/fixed in order to make the JUnit view jump to the
  207.         // corresponding test method accordingly. For now we have to live, that we end up in the correct class.
  208.         description.addChild(Description.createTestDescription(step.getStepsType(), uniquify(stringStep),
  209.                 junitTestMeta));
  210.     }

  211.     private void addCompositeSteps(Description description, String stringStep, StepCandidate step) {
  212.         Description testDescription = Description.createSuiteDescription(uniquify(stringStep), junitTestMeta);
  213.         addSteps(testDescription, Arrays.asList(step.composedSteps()));
  214.         description.addChild(testDescription);
  215.     }

  216.     private List<Description> getScenarioDescriptions(Lifecycle lifecycle,
  217.             List<PerformableScenario> performableScenarios) {
  218.         List<Description> scenarioDescriptions = new ArrayList<>();
  219.         for (PerformableScenario scenario : performableScenarios) {
  220.             if (!scenario.isExcluded()) {
  221.                 scenarioDescriptions.add(createDescriptionsFrom(lifecycle, scenario));
  222.             }
  223.         }
  224.         return scenarioDescriptions;
  225.     }

  226.     private StepCandidate findMatchingStep(String stringStep) {
  227.         for (StepCandidate step : allStepCandidates.getRegularSteps()) {
  228.             if (step.matches(stringStep, previousNonAndStep)) {
  229.                 if (step.getStepType() != StepType.AND) {
  230.                     previousNonAndStep = step.getStartingWord() + " ";
  231.                 }
  232.                 return step;
  233.             }
  234.         }
  235.         return null;
  236.     }

  237.     private String stripLinebreaks(String stringStep) {
  238.         if (stringStep.indexOf('\n') != -1) {
  239.             return stringStep.substring(0, stringStep.indexOf('\n'));
  240.         }
  241.         return stringStep;
  242.     }

  243.     private Description createDescriptionForStory(Story story) {
  244.         return Description.createSuiteDescription(uniquify(story.getName()));
  245.     }

  246.     private Description createDescriptionForScenario(Scenario scenario) {
  247.         return Description.createSuiteDescription(
  248.                 configuration.keywords().scenario() + " " + uniquify(scenario.getTitle()));
  249.     }

  250.     public @interface JUnit4Test {
  251.     }
  252. }