tw.teddysoft.ezspec.keyword.ScenarioOutline Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ezspec-core Show documentation
Show all versions of ezspec-core Show documentation
ezspec is a framework for adopting Gherkin in Java testing.
The newest version!
package tw.teddysoft.ezspec.keyword;
import org.junit.jupiter.api.DynamicNode;
import tw.teddysoft.ezspec.exception.EzSpecError;
import tw.teddysoft.ezspec.keyword.table.Table;
import tw.teddysoft.ezspec.keyword.visitor.SpecificationElement;
import tw.teddysoft.ezspec.keyword.visitor.SpecificationElementVisitor;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static java.lang.String.format;
import static org.junit.jupiter.api.DynamicContainer.dynamicContainer;
/**
* {@code ScenarioOutline} is a class for representing Gherkin scenario outline.
*
* @author Teddy Chen
* @since 1.0
*/
public class ScenarioOutline extends Scenario implements SpecificationElement {
public static final String KEYWORD = "Scenario Outline";
private final String name;
protected final List allExamples;
private final List runtimeScenarios;
private final String description;
private int currentRuntimeScenarioIndex;
/**
* A static factory to create a scenario outline.
*
* @param rule the rule of this scenario outline
* @return a new scenario outline instance
*/
public static ScenarioOutline New(Rule rule) {
Objects.requireNonNull(rule, "rule");
return rule.newScenarioOutline(getEnclosingMethodName());
}
/**
* A static factory to create a scenario outline.
*
* @param name the name of this scenario outline
* @param rule the rule of this scenario outline
* @return a new scenario outline instance
*/
public static ScenarioOutline New(String name, Rule rule) {
Objects.requireNonNull(name, "name");
return rule.newScenarioOutline(name);
}
/**
* A static factory to create a scenario outline.
*
* @param name the name of this scenario outline
* @param description the description of this scenario outline
* @param rule the rule of this scenario outline
* @return a new scenario outline instance
*/
public static ScenarioOutline New(String name, String description, Rule rule) {
Objects.requireNonNull(name, "name");
return rule.newScenarioOutline(name, description);
}
/**
* Instantiates a new Scenario outline.
*
* @param name the name of this scenario outline
* @param description the description of this scenario outline
* @param background the background of this scenario outline
*/
public ScenarioOutline(String name, String description, Background background, Rule rule) {
super(name, background, rule);
runtimeScenarios = new ArrayList<>();
allExamples = new ArrayList<>();
this.name = name;
this.description = description;
currentRuntimeScenarioIndex = 0;
}
public String getDescription() {
return description;
}
public List getRawSteps() {
return getSteps();
}
@Override
public ScenarioOutline withRule(String ruleName){
Objects.requireNonNull(ruleName);
super.withRule(ruleName);
return this;
}
@Override
public ScenarioOutline withRule(Rule rule){
Objects.requireNonNull(rule);
super.withRule(rule);
return this;
}
/**
* Adds each example into this scenario outline and builds runtime scenarios
* of each example.
*
* @param example the given example
* @param moreExamples the more given examples
* @return the scenario outline
*/
public ScenarioOutline WithExamples(Example example, Example... moreExamples) {
Objects.requireNonNull(example, "example");
ArrayList examples = new ArrayList<>(Arrays.asList(example));
examples.addAll(List.of(moreExamples));
return WithExamples(examples);
}
/**
* Adds list of examples into this scenario outline and builds runtime
* scenarios of each example.
*
* @param argExamples the given list of examples
* @return the scenario outline
*/
public ScenarioOutline WithExamples(List argExamples) {
Objects.requireNonNull(argExamples, "example");
if (argExamples.isEmpty()) throw new RuntimeException("require at least an example");
if (allExamples.size() > 0) return this;
this.allExamples.addAll(argExamples);
buildRuntimeScenarios();
return this;
}
/**
* Adds an example by string containing a table into this scenario outline
* and builds runtime scenarios of the example.
*
* @param tableRawData the given table raw data of example
* @return the scenario outline
*/
public ScenarioOutline WithExamples(String tableRawData) {
Objects.requireNonNull(tableRawData, "table raw data");
if (allExamples.size() > 0) return this;
allExamples.add(0, new Example(tableRawData));
buildRuntimeScenarios();
return this;
}
private void buildRuntimeScenarios() {
runtimeScenarios.clear();
int runtimeScenarioIndex = 0;
for (var example : allExamples) {
for (int i = 0; i < example.getTable().rows().size(); i++) {
runtimeScenarios.add(new RuntimeScenario(name, example.rowAsTable(i), this, runtimeScenarioIndex, runtime, rule));
runtimeScenarioIndex++;
}
}
}
protected Step createRuntimeStep(Scenario runtimeScenario, Step rawStep) {
var desc = replaceScenarioOutlineVariables(rawStep.description());
Step step;
switch (rawStep) {
case Given ignored -> step = new Given(desc, rawStep.isContinuousAfterFailure(), rawStep.getCallback());
case When ignored -> step = new When(desc, rawStep.isContinuousAfterFailure(), rawStep.getCallback());
case Then ignored -> step = new Then(desc, rawStep.isContinuousAfterFailure(), rawStep.getCallback());
case And ignored -> step = new And(desc, rawStep.isContinuousAfterFailure(), rawStep.getCallback());
case But ignored -> step = new But(desc, rawStep.isContinuousAfterFailure(), rawStep.getCallback());
case ThenFailure ignored ->
step = new ThenFailure(desc, rawStep.isContinuousAfterFailure(), rawStep.getCallback());
case ThenSuccess ignored ->
step = new ThenSuccess(desc, rawStep.isContinuousAfterFailure(), rawStep.getCallback());
default -> throw new IllegalStateException("Unsupported step: " + rawStep);
}
return step;
}
@Override
public DynamicNode DynamicExecute() {
List dynamicScenarios = new ArrayList<>();
for (var runtimeScenario : runtimeScenarios) {
buildRuntimeScenarioSteps(runtimeScenario);
dynamicScenarios.add(dynamicContainer(
format("[%d] %s", runtimeScenario.index + 1, runtimeScenario.runtime.getInput().toString()),
runtimeScenario.dynamicExecuteImpl()));
currentRuntimeScenarioIndex++;
}
return dynamicContainer("Scenario: " + this.getReplacedUnderscoresName(), dynamicScenarios);
}
public String getName() {
return name;
}
@Override
public Scenario Given(String description, Consumer callback) {
return this.Given(description, Step.TerminateAfterFailure, callback);
}
@Override
public Scenario Given(String description, boolean continuous, Consumer callback) {
getSteps().add(new Given(description, continuous, callback));
return this;
}
@Override
public Scenario When(String description, Consumer callback) {
return this.When(description, Step.TerminateAfterFailure, callback);
}
@Override
public Scenario When(String description, boolean continuous, Consumer callback) {
getSteps().add(new When(description, continuous, callback));
return this;
}
@Override
public Scenario Then(String description, Consumer callback) {
return this.Then(description, Step.TerminateAfterFailure, callback);
}
@Override
public Scenario ThenSuccess(boolean continuous, Consumer callback) {
return this.ThenSuccess("", continuous, callback);
}
@Override
public Scenario Then(String description, boolean continuous, Consumer callback) {
getSteps().add(new Then(description, continuous, callback));
return this;
}
@Override
public Scenario ThenSuccess(Consumer callback) {
return this.ThenSuccess("", Step.TerminateAfterFailure, callback);
}
@Override
public Scenario ThenSuccess(String description, Consumer callback) {
return this.ThenSuccess(description, Step.TerminateAfterFailure, callback);
}
@Override
public Scenario ThenSuccess(String description, boolean continuous, Consumer callback) {
getSteps().add(new ThenSuccess(description, continuous, callback));
return this;
}
@Override
public Scenario ThenFailure(Consumer callback) {
return this.ThenFailure("", Step.TerminateAfterFailure, callback);
}
@Override
public Scenario ThenFailure(boolean continuous, Consumer callback) {
return this.ThenFailure("", continuous, callback);
}
@Override
public Scenario ThenFailure(String description, boolean continuous, Consumer callback) {
getSteps().add(new ThenFailure(description, continuous, callback));
return this;
}
@Override
public Scenario ThenFailure(String description, Consumer callback) {
return this.ThenFailure("", Step.TerminateAfterFailure, callback);
}
@Override
public Scenario And(String description, Consumer callback) {
return this.And(description, Step.TerminateAfterFailure, callback);
}
@Override
public Scenario And(String description, boolean continuous, Consumer callback) {
getSteps().add(new And(description, continuous, callback));
return this;
}
@Override
public Scenario But(String description, Consumer callback) {
return this.But(description, Step.TerminateAfterFailure, callback);
}
@Override
public Scenario But(String description, boolean continuous, Consumer callback) {
getSteps().add(new But(description, continuous, callback));
return this;
}
@Override
public void accept(SpecificationElementVisitor visitor) {
visitor.visit(this);
runtimeScenarios.forEach(scenario -> {
scenario.accept(visitor);
});
}
public String getReplacedUnderscoresName() {
return getName().replace("_", " ");
}
/**
* Replaces variables in description of scenario outline with example value
*
* @param description the description
* @return the string
*/
protected String replaceScenarioOutlineVariables(String description) {
StringBuilder sb = new StringBuilder(description);
Table inputTable = getCurrentExampleTable();
var args = parseArgumentsInOutline(description);
args.forEach(arg -> doReplaceArguments(sb, arg, inputTable.get(arg.key())));
return sb.toString();
}
private Table getCurrentExampleTable() {
Table inputTable = null;
int index = currentRuntimeScenarioIndex;
for (int i = 0; i < allExamples.size(); i++) {
if (index >= allExamples.get(i).getTable().rows().size()) {
index -= allExamples.get(i).getTable().rows().size();
} else {
inputTable = allExamples.get(i).rowAsTable(index);
break;
}
}
return inputTable;
}
private List parseArgumentsInOutline(String description) {
List arguments = new LinkedList<>();
String regx = "<(.*?)>";
Pattern pattern = Pattern.compile(regx);
Matcher matcher = pattern.matcher(description);
while (matcher.find()) {
arguments.add(Argument.fromKey(matcher.group(1)));
}
return arguments;
}
private void doReplaceArguments(StringBuilder sb, Argument arg, String value) {
arg.value(value);
var temp = sb.toString();
sb.setLength(0);
sb.append(temp.replace(format("<%s>", arg.key()), format("<%s>", arg.value())));
}
private static List getParameterNames() {
StackWalker walker = StackWalker.getInstance();
Optional enclosingFrame = walker.walk(frames -> frames
.skip(2)
.findFirst());
String methodName = enclosingFrame.get().getMethodName();
String className = enclosingFrame.get().getClassName();
try {
Class> cls = Class.forName(className);
Method method = Arrays.stream(cls.getMethods()).filter(x -> x.getName().equals(methodName)).findFirst().get();
return getParameterNames(method);
} catch (ClassNotFoundException e) {
throw new RuntimeException("Cannot find parameter names", e);
}
}
private static List getParameterNames(Method method) {
Parameter[] parameters = method.getParameters();
List parameterNames = new ArrayList<>();
for (Parameter parameter : parameters) {
if (!parameter.isNamePresent()) {
throw new IllegalArgumentException("Parameter names are not present!");
}
String parameterName = parameter.getName();
parameterNames.add(parameterName);
}
return parameterNames;
}
public List getAllExamples() {
return Collections.unmodifiableList(allExamples);
}
public List RuntimeScenarios() {
return Collections.unmodifiableList(runtimeScenarios);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(KEYWORD).append(": ").append(replaceName(name)).append("\n\n");
if (!description.isEmpty())
sb.append(description).append("\n");
for (var each : getSteps()) {
sb.append(each.getName());
if (!each.description().isEmpty()) {
sb.append(" ").append(each.description());
}
sb.append("\n");
}
allExamples.forEach(example -> {
sb.append(example.toString());
});
return sb.toString();
}
private void buildRuntimeScenarioSteps(Scenario runtimeScenario) {
for (var step : this.getSteps()) {
runtimeScenario.getSteps().add(createRuntimeStep(runtimeScenario, step));
setRuntimeScenarioEnvironment(runtimeScenario, step);
}
}
private void setRuntimeScenarioEnvironment(Scenario runtimeScenario, Step step) {
Table inputTable = getCurrentExampleTable();
var args = parseArgumentsInOutline(step.description());
args.forEach(arg -> {
String value = inputTable.get(arg.key());
if (Table.containsTable(value)) {
runtimeScenario.getEnvironment().put(arg.key(), new Table(value));
} else {
runtimeScenario.getEnvironment().put(arg.key(), value);
}
});
}
@Override
public void Execute() {
for (var runtimeScenario : runtimeScenarios) {
buildRuntimeScenarioSteps(runtimeScenario);
runtimeScenario.doExecute(false);
currentRuntimeScenarioIndex++;
}
List allSteps = new ArrayList<>();
for (var runtimeScenario : runtimeScenarios) {
allSteps.addAll(runtimeScenario.getSteps());
}
Optional ezSpecError = buildSpecError(allSteps);
if (ezSpecError.isPresent())
throw ezSpecError.get();
}
@Override
public void ExecuteConcurrently() {
for (var runtimeScenario : runtimeScenarios) {
buildRuntimeScenarioSteps(runtimeScenario);
runtimeScenario.doExecuteConcurrently();
currentRuntimeScenarioIndex++;
}
List allSteps = new ArrayList<>();
for (var runtimeScenario : runtimeScenarios) {
allSteps.addAll(runtimeScenario.getSteps());
}
Optional ezSpecError = buildSpecError(allSteps);
if (ezSpecError.isPresent())
throw ezSpecError.get();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy