From 82ad3be2cbf53d8bc788048cefb9dc0159b09600 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Sch=C3=A4fer?= Date: Thu, 18 Sep 2014 11:32:07 +0200 Subject: [PATCH] Implement derived parameters (#15) --- CHANGELOG.md | 15 ++-- .../tngtech/jgiven/impl/ScenarioExecutor.java | 4 +- .../impl/intercept/NoOpScenarioListener.java | 2 +- .../impl/intercept/ScenarioListener.java | 3 +- .../jgiven/impl/util/ScenarioUtil.java | 2 +- .../html/DataTableScenarioHtmlWriter.java | 7 +- .../report/html/ScenarioHtmlWriter.java | 4 +- .../report/impl/CaseArgumentAnalyser.java | 87 +++++++++++++++---- .../jgiven/report/model/ArgumentInfo.java | 69 +++++++++++---- .../report/model/ReportModelBuilder.java | 4 +- .../jgiven/report/model/ScenarioModel.java | 4 + .../jgiven/report/model/StepFormatter.java | 14 +-- .../com/tngtech/jgiven/report/model/Word.java | 3 +- .../DataTablePlainTextScenarioWriter.java | 8 +- .../jgiven/format/StepFormatterTest.java | 8 +- .../jgiven/impl/util/ScenarioUtilTest.java | 2 +- jgiven-examples/pom.xml | 7 ++ .../coffeemachine/ServeCoffeeTest.java | 16 ++++ .../coffeemachine/steps/ThenCoffee.java | 21 +++-- .../NotImplementYetExampleTest.java | 2 + .../jgiven/junit/ScenarioExecutionRule.java | 2 +- .../jgiven/junit/DataProviderTest.java | 24 ++++- .../jgiven/testng/ScenarioTestListener.java | 2 +- jgiven-tests/pom.xml | 8 -- .../report/html/HtmlWriterScenarioTest.java | 22 +++++ .../jgiven/report/model/GivenReportModel.java | 9 +- .../jgiven/tags/FeatureDataTables.java | 5 +- .../jgiven/tags/FeatureDerivedParameters.java | 15 ++++ .../jgiven/tags/FeatureNotImplementedYet.java | 4 +- 29 files changed, 280 insertions(+), 93 deletions(-) create mode 100644 jgiven-tests/src/test/java/com/tngtech/jgiven/tags/FeatureDerivedParameters.java diff --git a/CHANGELOG.md b/CHANGELOG.md index c6e99472da..ce17ed55b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,19 +1,22 @@ +v0.5.0 +* implement derived parameters (#15) + v0.4.0 -* made scenarios and cases in HTML reports collapsible (Issue-#18) +* made scenarios and cases in HTML reports collapsible (#18) * slightly changed layout and appearance of HTML reports -* measure duration of each step and include in reports (Issue-#13) +* measure duration of each step and include in reports (#13) * scenarios are sorted by name in HTML reports * fix issue with intercepting methods of stages during construction * fix issue when multiple exceptions are thrown to throw the first one instead of the last one -* @ScenarioDescription is now deprecated, instead just use @Description (Issue-#16) +* @ScenarioDescription is now deprecated, instead just use @Description (#16) * @Description now also works on test classes -* fixed case generation for parameterized JUnit runner (Issue-#21) +* fixed case generation for parameterized JUnit runner (#21) v0.3.0 * Arguments from JUnit Parameterized and JUnitParams runner can now be read * Arguments from JUnit Dataprovider runner are now read directly instead of parsing them * Schritte-class is deprecated and has been replaced with Stufe-class (only relevant for german scenarios) -* the HTML report now escapes HTML in step arguments (Issue-#9) +* the HTML report now escapes HTML in step arguments (#9) * print style of HTML report is nicer * steps following failed steps are now reported as skipped -* @NotImplementedYet annotation has new attributes failIfPass and executeSteps (Issue-#4) +* @NotImplementedYet annotation has new attributes failIfPass and executeSteps (#4) diff --git a/jgiven-core/src/main/java/com/tngtech/jgiven/impl/ScenarioExecutor.java b/jgiven-core/src/main/java/com/tngtech/jgiven/impl/ScenarioExecutor.java index 005b2c76d9..d7e694aede 100644 --- a/jgiven-core/src/main/java/com/tngtech/jgiven/impl/ScenarioExecutor.java +++ b/jgiven-core/src/main/java/com/tngtech/jgiven/impl/ScenarioExecutor.java @@ -42,6 +42,7 @@ import com.tngtech.jgiven.impl.util.ReflectionUtil; import com.tngtech.jgiven.impl.util.ReflectionUtil.FieldAction; import com.tngtech.jgiven.impl.util.ReflectionUtil.MethodAction; +import com.tngtech.jgiven.impl.util.ScenarioUtil; import com.tngtech.jgiven.integration.CanWire; /** @@ -113,7 +114,8 @@ public void handleMethod( Object stageInstance, Method paramMethod, Object[] arg if( paramMethod.isAnnotationPresent( IntroWord.class ) ) { listener.introWordAdded( paramMethod.getName() ); } else { - listener.stepMethodInvoked( paramMethod, Arrays.asList( arguments ), mode ); + List namedArguments = ScenarioUtil.mapArgumentsWithParameterNames( paramMethod, Arrays.asList( arguments ) ); + listener.stepMethodInvoked( paramMethod, namedArguments, mode ); } } diff --git a/jgiven-core/src/main/java/com/tngtech/jgiven/impl/intercept/NoOpScenarioListener.java b/jgiven-core/src/main/java/com/tngtech/jgiven/impl/intercept/NoOpScenarioListener.java index fd5250b5d5..74ff461ddf 100644 --- a/jgiven-core/src/main/java/com/tngtech/jgiven/impl/intercept/NoOpScenarioListener.java +++ b/jgiven-core/src/main/java/com/tngtech/jgiven/impl/intercept/NoOpScenarioListener.java @@ -17,7 +17,7 @@ public void scenarioStarted( String string ) {} public void scenarioStarted( Method method, List arguments ) {} @Override - public void stepMethodInvoked( Method paramMethod, List arguments, InvocationMode mode ) {} + public void stepMethodInvoked( Method method, List arguments, InvocationMode mode ) {} @Override public void introWordAdded( String word ) {} diff --git a/jgiven-core/src/main/java/com/tngtech/jgiven/impl/intercept/ScenarioListener.java b/jgiven-core/src/main/java/com/tngtech/jgiven/impl/intercept/ScenarioListener.java index b083652ccd..a09967cc40 100644 --- a/jgiven-core/src/main/java/com/tngtech/jgiven/impl/intercept/ScenarioListener.java +++ b/jgiven-core/src/main/java/com/tngtech/jgiven/impl/intercept/ScenarioListener.java @@ -13,7 +13,7 @@ public interface ScenarioListener { void scenarioStarted( Method method, List arguments ); - void stepMethodInvoked( Method paramMethod, List arguments, InvocationMode mode ); + void stepMethodInvoked( Method method, List arguments, InvocationMode mode ); void introWordAdded( String word ); @@ -22,5 +22,4 @@ public interface ScenarioListener { void stepMethodFinished( long durationInNanos ); void scenarioFinished(); - } diff --git a/jgiven-core/src/main/java/com/tngtech/jgiven/impl/util/ScenarioUtil.java b/jgiven-core/src/main/java/com/tngtech/jgiven/impl/util/ScenarioUtil.java index 7e1f093f4d..f57df13eec 100644 --- a/jgiven-core/src/main/java/com/tngtech/jgiven/impl/util/ScenarioUtil.java +++ b/jgiven-core/src/main/java/com/tngtech/jgiven/impl/util/ScenarioUtil.java @@ -25,7 +25,7 @@ public class ScenarioUtil { /** * @throws NullPointerException iif {@code constructorOrMethod} is {@code null} */ - public static List mapArgumentsWithParameterNamesOf( AccessibleObject contructorOrMethod, List arguments ) { + public static List mapArgumentsWithParameterNames( AccessibleObject contructorOrMethod, List arguments ) { Preconditions.checkNotNull( contructorOrMethod, "constructorOrMethod must not be null." ); Preconditions.checkNotNull( arguments, "arguments must not be null" ); diff --git a/jgiven-core/src/main/java/com/tngtech/jgiven/report/html/DataTableScenarioHtmlWriter.java b/jgiven-core/src/main/java/com/tngtech/jgiven/report/html/DataTableScenarioHtmlWriter.java index fd819336e2..9a5e18fb3f 100644 --- a/jgiven-core/src/main/java/com/tngtech/jgiven/report/html/DataTableScenarioHtmlWriter.java +++ b/jgiven-core/src/main/java/com/tngtech/jgiven/report/html/DataTableScenarioHtmlWriter.java @@ -80,12 +80,7 @@ String formatCaseArgument( Word value ) { } private String findParameterName( Word word ) { - int index = word.getArgumentInfo().getParameterIndex(); - String paramName = index + ""; - if( index < scenarioModel.parameterNames.size() ) { - paramName = scenarioModel.parameterNames.get( index ); - } - return paramName; + return word.getArgumentInfo().getParameterName(); } } diff --git a/jgiven-core/src/main/java/com/tngtech/jgiven/report/html/ScenarioHtmlWriter.java b/jgiven-core/src/main/java/com/tngtech/jgiven/report/html/ScenarioHtmlWriter.java index 13b34ae309..1b9830fbc7 100644 --- a/jgiven-core/src/main/java/com/tngtech/jgiven/report/html/ScenarioHtmlWriter.java +++ b/jgiven-core/src/main/java/com/tngtech/jgiven/report/html/ScenarioHtmlWriter.java @@ -171,10 +171,10 @@ public void visit( StepModel stepModel ) { } private void printArg( Word word ) { - String value = word.getArgumentInfo().isCaseArg() ? formatCaseArgument( word ) : HtmlEscapers.htmlEscaper().escape( word.value ); + String value = word.getArgumentInfo().isParameter() ? formatCaseArgument( word ) : HtmlEscapers.htmlEscaper().escape( word.value ); value = escapeToHtml( value ); String multiLine = value.contains( "
" ) ? "multiline" : ""; - String caseClass = word.getArgumentInfo().isCaseArg() ? "caseArgument" : "argument"; + String caseClass = word.getArgumentInfo().isParameter() ? "caseArgument" : "argument"; writer.print( format( "%s", caseClass, multiLine, value ) ); } diff --git a/jgiven-core/src/main/java/com/tngtech/jgiven/report/impl/CaseArgumentAnalyser.java b/jgiven-core/src/main/java/com/tngtech/jgiven/report/impl/CaseArgumentAnalyser.java index d5789328b4..b95c4fffb0 100644 --- a/jgiven-core/src/main/java/com/tngtech/jgiven/report/impl/CaseArgumentAnalyser.java +++ b/jgiven-core/src/main/java/com/tngtech/jgiven/report/impl/CaseArgumentAnalyser.java @@ -9,6 +9,7 @@ import com.google.common.base.Objects; import com.google.common.collect.Lists; import com.google.common.collect.Sets; +import com.tngtech.jgiven.report.model.ArgumentInfo; import com.tngtech.jgiven.report.model.ReportModel; import com.tngtech.jgiven.report.model.ReportModelVisitor; import com.tngtech.jgiven.report.model.ScenarioCaseModel; @@ -17,8 +18,10 @@ import com.tngtech.jgiven.report.model.Word; /** - * Analyzes a report model and tries to infer which step method. - * arguments match to which case argument + * Analyzes a report model and tries to infer which step method arguments match to which case argument. + * + * This is done by comparing all cases of a scenario and find out which method arguments + * match in all cases to the same parameter * */ public class CaseArgumentAnalyser { @@ -34,11 +37,11 @@ public void analyze( ScenarioModel scenarioModel ) { if( scenarioModel.getScenarioCases().size() < 2 ) { return; } - CollectPhase collectPhase = new CollectPhase(); + CollectPhase collectPhase = new CollectPhase( scenarioModel ); scenarioModel.accept( collectPhase ); try { - reduceMatrix( collectPhase.argumentMatrix ); + reduceMatrix( scenarioModel, collectPhase.argumentMatrix ); scenarioModel.setCasesAsTable( allStepsEqual( collectPhase.allWords ) ); } catch( IndexOutOfBoundsException e ) { log.info( "Scenario model " + scenarioModel.className + "." + scenarioModel.testMethodName + " has no homogene cases." @@ -63,7 +66,7 @@ private boolean wordsAreEqual( List firstWords, List words ) { return false; } for( int j = 0; j < words.size(); j++ ) { - if( firstWords.get( j ).isArg() && firstWords.get( j ).getArgumentInfo().isCaseArg() ) { + if( firstWords.get( j ).isArg() && firstWords.get( j ).getArgumentInfo().isParameter() ) { continue; } if( !firstWords.get( j ).equals( words.get( j ) ) ) { @@ -73,10 +76,25 @@ private boolean wordsAreEqual( List firstWords, List words ) { return true; } - private void reduceMatrix( List> argumentMatrix ) { - int nArguments = argumentMatrix.get( 0 ).size(); + private static final class CaseArguments { + final ScenarioCaseModel caseModel; + final List arguments; + + private CaseArguments( ScenarioCaseModel model, List arguments ) { + this.caseModel = model; + this.arguments = arguments; + } + + public ArgumentHolder get( int i ) { + return arguments.get( i ); + } + } + + private void reduceMatrix( ScenarioModel scenarioModel, List argumentMatrix ) { + int nArguments = argumentMatrix.get( 0 ).arguments.size(); + List derivedParams = Lists.newArrayList(); for( int iArg = 0; iArg < nArguments; iArg++ ) { - Set currentSet = Sets.newLinkedHashSet(); + Set currentSet = Sets.newLinkedHashSet(); currentSet.addAll( argumentMatrix.get( 0 ).get( iArg ).params ); for( int iCase = 1; iCase < argumentMatrix.size(); iCase++ ) { currentSet.retainAll( argumentMatrix.get( iCase ).get( iArg ).params ); @@ -85,19 +103,47 @@ private void reduceMatrix( List> argumentMatrix ) { log.warn( "Could not disambiguate case arguments for argument " + iArg + ". Values: " + currentSet ); } else if( currentSet.isEmpty() ) { log.warn( "Could not identify parameter index for argument " + iArg ); + + if( !allArgumentsAreEqual( argumentMatrix, iArg ) ) { + String parameterName = argumentMatrix.get( 0 ).get( iArg ).word.getArgumentInfo().getArgumentName(); + derivedParams.add( parameterName ); + for( int iCase = 0; iCase < argumentMatrix.size(); iCase++ ) { + CaseArguments caseArguments = argumentMatrix.get( iCase ); + Word word = caseArguments.get( iArg ).word; + ArgumentInfo argumentInfo = word.getArgumentInfo(); + argumentInfo.setParameterName( parameterName ); + argumentInfo.setDerivedParameter( true ); + caseArguments.caseModel.addArguments( word.value ); + } + scenarioModel.addDerivedParameter( parameterName ); + } + continue; } for( int iCase = 0; iCase < argumentMatrix.size(); iCase++ ) { Word word = argumentMatrix.get( iCase ).get( iArg ).word; - word.getArgumentInfo().setParameterIndex( currentSet.iterator().next() ); + word.getArgumentInfo().setParameterName( currentSet.iterator().next() ); } } } + private boolean allArgumentsAreEqual( List argumentMatrix, int iArg ) { + Word word = argumentMatrix.get( 0 ).get( iArg ).word; + boolean allWordsAreEqual = true; + for( int iCase = 1; iCase < argumentMatrix.size(); iCase++ ) { + Word word2 = argumentMatrix.get( iCase ).get( iArg ).word; + if( !word.equals( word2 ) ) { + allWordsAreEqual = false; + break; + } + } + return allWordsAreEqual; + } + static class ArgumentHolder { Word word; - Set params; + Set params; } /** @@ -105,17 +151,22 @@ static class ArgumentHolder { * This results in a set of possible case arguments for each step argument */ static class CollectPhase extends ReportModelVisitor { - List> argumentMatrix = Lists.newArrayList(); + List argumentMatrix = Lists.newArrayList(); List argumentsOfCurrentCase; List> allWords = Lists.newArrayList(); List allWordsOfCurrentCase; ScenarioCaseModel currentCase; + final ScenarioModel scenarioModel; + + public CollectPhase( ScenarioModel model ) { + this.scenarioModel = model; + } @Override public void visit( ScenarioCaseModel scenarioCase ) { currentCase = scenarioCase; argumentsOfCurrentCase = Lists.newArrayList(); - argumentMatrix.add( argumentsOfCurrentCase ); + argumentMatrix.add( new CaseArguments( currentCase, argumentsOfCurrentCase ) ); allWordsOfCurrentCase = Lists.newArrayList(); allWords.add( allWordsOfCurrentCase ); } @@ -126,21 +177,23 @@ public void visit( StepModel methodModel ) { if( word.isArg() ) { ArgumentHolder holder = new ArgumentHolder(); holder.word = word; - holder.params = getMatchingIndices( word ); + holder.params = getMatchingParameters( word ); argumentsOfCurrentCase.add( holder ); } allWordsOfCurrentCase.add( word ); } } - private Set getMatchingIndices( Word word ) { - Set matchingIndices = Sets.newLinkedHashSet(); + private Set getMatchingParameters( Word word ) { + Set matchingParameters = Sets.newLinkedHashSet(); for( int i = 0; i < currentCase.arguments.size(); i++ ) { if( Objects.equal( word.value, currentCase.arguments.get( i ) ) ) { - matchingIndices.add( i ); + if( i < scenarioModel.parameterNames.size() ) { + matchingParameters.add( scenarioModel.parameterNames.get( i ) ); + } } } - return matchingIndices; + return matchingParameters; } } diff --git a/jgiven-core/src/main/java/com/tngtech/jgiven/report/model/ArgumentInfo.java b/jgiven-core/src/main/java/com/tngtech/jgiven/report/model/ArgumentInfo.java index dd73b45c84..01ee2726f8 100644 --- a/jgiven-core/src/main/java/com/tngtech/jgiven/report/model/ArgumentInfo.java +++ b/jgiven-core/src/main/java/com/tngtech/jgiven/report/model/ArgumentInfo.java @@ -4,33 +4,68 @@ public class ArgumentInfo { /** - * If this word is an argument, whether it is a case - * argument or not. + * In case this word can be replaced by a parameter name, + * e.g. for data tables, this value is set, otherwise it is {@code null}. + * The parameter name is in general taken from scenario parameters. + * In case of a derived parameter the parameter name is actually equal to the + * argumentName. + * */ - private boolean isCaseArg; + private String parameterName; /** - * In case this word is a case argument (isCaseArg == true) - * this field is set to the corresponding parameter index. + * Whether this argument is actually a derived parameter. + * Note that in that case parameterName is equal to argumentName */ - private int parameterIndex; + private boolean isDerivedParameter; - public void setParameterIndex( int i ) { - isCaseArg = true; - parameterIndex = i; + /** + * The name of the argument as declared in the step method. + * Should never be {@code null}. + */ + private String argumentName; + + public void setParameterName( String parameterName ) { + this.parameterName = parameterName; } - public boolean isCaseArg() { - return isCaseArg; + /** + * @throws NullPointerException in case their is no parameter name + * @return the parameter name if there is one + */ + public String getParameterName() { + if( parameterName == null ) { + throw new NullPointerException( "Argument has no parameter name" ); + } + return parameterName; } - public int getParameterIndex() { - return parameterIndex; + /** + * @return whether this is argument is a parameter or not + */ + public boolean isParameter() { + return parameterName != null; + } + + public void setArgumentName( String argumentName ) { + this.argumentName = argumentName; + } + + public String getArgumentName() { + return argumentName; + } + + public void setDerivedParameter( boolean isDerivedParameter ) { + this.isDerivedParameter = isDerivedParameter; + } + + public boolean isDerivedParameter() { + return isDerivedParameter; } @Override public int hashCode() { - return Objects.hashCode( isCaseArg, parameterIndex ); + return Objects.hashCode( parameterName, argumentName, isDerivedParameter ); } @Override @@ -45,7 +80,9 @@ public boolean equals( Object obj ) { return false; } ArgumentInfo other = (ArgumentInfo) obj; - return Objects.equal( isCaseArg, other.isCaseArg ) && - Objects.equal( parameterIndex, other.parameterIndex ); + return Objects.equal( parameterName, other.parameterName ) + && Objects.equal( argumentName, other.argumentName ) + && ( isDerivedParameter == other.isDerivedParameter ); } + } diff --git a/jgiven-core/src/main/java/com/tngtech/jgiven/report/model/ReportModelBuilder.java b/jgiven-core/src/main/java/com/tngtech/jgiven/report/model/ReportModelBuilder.java index a594cb957e..f44a14bae1 100644 --- a/jgiven-core/src/main/java/com/tngtech/jgiven/report/model/ReportModelBuilder.java +++ b/jgiven-core/src/main/java/com/tngtech/jgiven/report/model/ReportModelBuilder.java @@ -93,7 +93,7 @@ private String camelCaseToReadableText( String camelCase ) { return WordUtil.capitalize( scenarioDescription ); } - public void addStepMethod( Method paramMethod, List arguments, InvocationMode mode ) { + public void addStepMethod( Method paramMethod, List arguments, InvocationMode mode ) { String name; Description description = paramMethod.getAnnotation( Description.class ); if( description != null ) { @@ -158,7 +158,7 @@ private ScenarioCaseModel getCurrentScenarioCase() { } @Override - public void stepMethodInvoked( Method paramMethod, List arguments, InvocationMode mode ) { + public void stepMethodInvoked( Method paramMethod, List arguments, InvocationMode mode ) { if( !isStepMethod( paramMethod ) ) { return; } diff --git a/jgiven-core/src/main/java/com/tngtech/jgiven/report/model/ScenarioModel.java b/jgiven-core/src/main/java/com/tngtech/jgiven/report/model/ScenarioModel.java index 607b88c01a..9feebc2981 100644 --- a/jgiven-core/src/main/java/com/tngtech/jgiven/report/model/ScenarioModel.java +++ b/jgiven-core/src/main/java/com/tngtech/jgiven/report/model/ScenarioModel.java @@ -80,4 +80,8 @@ public void setDurationInNanos( long durationInNanos ) { public void addDurationInNanos( long durationInNanosDelta ) { this.durationInNanos += durationInNanosDelta; } + + public void addDerivedParameter( String parameterName ) { + this.parameterNames.add( parameterName ); + } } \ No newline at end of file diff --git a/jgiven-core/src/main/java/com/tngtech/jgiven/report/model/StepFormatter.java b/jgiven-core/src/main/java/com/tngtech/jgiven/report/model/StepFormatter.java index beed2f286d..714897df8d 100644 --- a/jgiven-core/src/main/java/com/tngtech/jgiven/report/model/StepFormatter.java +++ b/jgiven-core/src/main/java/com/tngtech/jgiven/report/model/StepFormatter.java @@ -8,10 +8,11 @@ import com.google.common.collect.Lists; import com.tngtech.jgiven.format.ArgumentFormatter; import com.tngtech.jgiven.format.DefaultFormatter; +import com.tngtech.jgiven.impl.NamedArgument; public class StepFormatter { private final String stepDescription; - private final List arguments; + private final List arguments; private final List> formatters; public static class Formatting { @@ -28,7 +29,7 @@ public String format( T o ) { } } - public StepFormatter( String stepDescription, List arguments, List> formatters ) { + public StepFormatter( String stepDescription, List arguments, List> formatters ) { this.stepDescription = stepDescription; this.arguments = arguments; this.formatters = formatters; @@ -62,8 +63,8 @@ public List buildFormattedWords() { } } for( int i = argCount; i < arguments.size(); i++ ) { - String value = formatUsingFormatter( formatters.get( i ), arguments.get( i ) ); - formattedWords.add( Word.argWord( value ) ); + String value = formatUsingFormatter( formatters.get( i ), arguments.get( i ).value ); + formattedWords.add( Word.argWord( arguments.get( i ).name, value ) ); } return formattedWords; } @@ -87,10 +88,11 @@ private void formatArgument( List formattedWords, int argCount, String wor index = argIndex - 1; } - String value = formatUsingFormatter( formatters.get( index ), arguments.get( index ) ); + String value = formatUsingFormatter( formatters.get( index ), arguments.get( index ).value ); + String argumentName = arguments.get( index ).name; if( value != null && !value.isEmpty() ) { - formattedWords.add( Word.argWord( value ) ); + formattedWords.add( Word.argWord( argumentName, value ) ); } } diff --git a/jgiven-core/src/main/java/com/tngtech/jgiven/report/model/Word.java b/jgiven-core/src/main/java/com/tngtech/jgiven/report/model/Word.java index 653fde544e..36cc9f011b 100644 --- a/jgiven-core/src/main/java/com/tngtech/jgiven/report/model/Word.java +++ b/jgiven-core/src/main/java/com/tngtech/jgiven/report/model/Word.java @@ -22,9 +22,10 @@ public Word( String value, boolean isIntroWord ) { this.isIntroWord = isIntroWord; } - public static Word argWord( String value ) { + public static Word argWord( String argumentName, String value ) { Word word = new Word( value ); word.argumentInfo = new ArgumentInfo(); + word.argumentInfo.setArgumentName( argumentName ); return word; } diff --git a/jgiven-core/src/main/java/com/tngtech/jgiven/report/text/DataTablePlainTextScenarioWriter.java b/jgiven-core/src/main/java/com/tngtech/jgiven/report/text/DataTablePlainTextScenarioWriter.java index fae40dcc06..c86874720f 100644 --- a/jgiven-core/src/main/java/com/tngtech/jgiven/report/text/DataTablePlainTextScenarioWriter.java +++ b/jgiven-core/src/main/java/com/tngtech/jgiven/report/text/DataTablePlainTextScenarioWriter.java @@ -26,11 +26,9 @@ public void visit( StepModel stepModel ) { @Override protected String wordToString( Word word ) { - if( word.isArg() && word.getArgumentInfo().isCaseArg() ) { - int argIndex = word.getArgumentInfo().getParameterIndex(); - if( argIndex < currentScenarioModel.parameterNames.size() ) { - return "<" + currentScenarioModel.parameterNames.get( argIndex ) + ">"; - } + if( word.isArg() && word.getArgumentInfo().isParameter() ) { + String parameterName = word.getArgumentInfo().getParameterName(); + return "<" + parameterName + ">"; } return super.wordToString( word ); } diff --git a/jgiven-core/src/test/java/com/tngtech/jgiven/format/StepFormatterTest.java b/jgiven-core/src/test/java/com/tngtech/jgiven/format/StepFormatterTest.java index b41d1bfcac..faf381960b 100644 --- a/jgiven-core/src/test/java/com/tngtech/jgiven/format/StepFormatterTest.java +++ b/jgiven-core/src/test/java/com/tngtech/jgiven/format/StepFormatterTest.java @@ -15,6 +15,7 @@ import com.tngtech.java.junit.dataprovider.DataProvider; import com.tngtech.java.junit.dataprovider.DataProviderRunner; import com.tngtech.java.junit.dataprovider.UseDataProvider; +import com.tngtech.jgiven.impl.NamedArgument; import com.tngtech.jgiven.report.model.StepFormatter; import com.tngtech.jgiven.report.model.StepFormatter.Formatting; import com.tngtech.jgiven.report.model.Word; @@ -72,7 +73,12 @@ public void testFormatter( String source, List arguments, ArgumentFormat asList.add( null ); } } - List formattedWords = new StepFormatter( source, arguments, asList ) + List namedArguments = Lists.newArrayList(); + for( Object o : arguments ) { + namedArguments.add( new NamedArgument( "foo", o ) ); + } + + List formattedWords = new StepFormatter( source, namedArguments, asList ) .buildFormattedWords(); String actualResult = Joiner.on( ' ' ).join( formattedWords ); assertThat( actualResult ).matches( expectedResult ); diff --git a/jgiven-core/src/test/java/com/tngtech/jgiven/impl/util/ScenarioUtilTest.java b/jgiven-core/src/test/java/com/tngtech/jgiven/impl/util/ScenarioUtilTest.java index 3916436cc0..6c9815f0e8 100644 --- a/jgiven-core/src/test/java/com/tngtech/jgiven/impl/util/ScenarioUtilTest.java +++ b/jgiven-core/src/test/java/com/tngtech/jgiven/impl/util/ScenarioUtilTest.java @@ -37,7 +37,7 @@ public static Object[][] dataProviderMapArgumentsWithParameterNamesOf() throws E public void testMapArgumentsWithParameterNamesOf( AccessibleObject contructorOrMethod, List arguments, List expected ) { // When: - List result = ScenarioUtil.mapArgumentsWithParameterNamesOf( contructorOrMethod, arguments ); + List result = ScenarioUtil.mapArgumentsWithParameterNames( contructorOrMethod, arguments ); // Then: assertThat( result ).containsExactly( expected.toArray( new NamedArgument[0] ) ); diff --git a/jgiven-examples/pom.xml b/jgiven-examples/pom.xml index 11eaa31daa..db18a3da82 100644 --- a/jgiven-examples/pom.xml +++ b/jgiven-examples/pom.xml @@ -21,6 +21,13 @@ test-jar test + + ${project.groupId} + jgiven-tests + ${project.version} + test-jar + test + ${project.groupId} jgiven-spring diff --git a/jgiven-examples/src/test/java/com/tngtech/jgiven/examples/coffeemachine/ServeCoffeeTest.java b/jgiven-examples/src/test/java/com/tngtech/jgiven/examples/coffeemachine/ServeCoffeeTest.java index 2468f1c5c8..386abc8e6b 100644 --- a/jgiven-examples/src/test/java/com/tngtech/jgiven/examples/coffeemachine/ServeCoffeeTest.java +++ b/jgiven-examples/src/test/java/com/tngtech/jgiven/examples/coffeemachine/ServeCoffeeTest.java @@ -9,6 +9,8 @@ import com.tngtech.jgiven.examples.coffeemachine.steps.ThenCoffee; import com.tngtech.jgiven.examples.coffeemachine.steps.WhenCoffee; import com.tngtech.jgiven.junit.ScenarioTest; +import com.tngtech.jgiven.tags.FeatureDataTables; +import com.tngtech.jgiven.tags.Issue; /** * Feature: Serve coffee @@ -52,6 +54,7 @@ public void not_enough_money_message_is_shown_when_insufficient_money_was_given( } @Test + @FeatureDataTables @DataProvider( { "0, 0, Error: No coffees left", "0, 1, Error: No coffees left", @@ -67,6 +70,19 @@ public void correct_messages_are_shown( int coffeesLeft, int numberOfCoins, Stri then().the_message_$_is_shown( message ); } + @Test + @FeatureDataTables + @Issue( "#15" ) + @DataProvider( { "1", "3", "10" } ) + public void serving_a_coffee_reduces_the_number_of_available_coffees_by_one( int initialCoffees ) { + given().a_coffee_machine() + .and().there_are_$_coffees_left_in_the_machine( initialCoffees ); + when().I_insert_$_one_euro_coins( 2 ) + .and().I_press_the_coffee_button(); + then().a_coffee_should_be_served() + .and().there_are_$_coffees_left_in_the_machine( initialCoffees - 1 ); + } + @Test public void a_turned_off_coffee_machine_cannot_serve_coffee() throws Exception { diff --git a/jgiven-examples/src/test/java/com/tngtech/jgiven/examples/coffeemachine/steps/ThenCoffee.java b/jgiven-examples/src/test/java/com/tngtech/jgiven/examples/coffeemachine/steps/ThenCoffee.java index 1e786e9ca4..5d5198ebbb 100644 --- a/jgiven-examples/src/test/java/com/tngtech/jgiven/examples/coffeemachine/steps/ThenCoffee.java +++ b/jgiven-examples/src/test/java/com/tngtech/jgiven/examples/coffeemachine/steps/ThenCoffee.java @@ -24,26 +24,31 @@ public void I_should_not_be_served_a_coffee() { I_should_be_served_a_coffee( false ); } - private void I_should_be_served_a_coffee( boolean b ) { + private ThenCoffee I_should_be_served_a_coffee( boolean b ) { assertThat( coffeeServed ).isEqualTo( b ); + return self(); } - public void a_coffee_should_be_served() { - I_should_be_served_a_coffee( true ); + public ThenCoffee a_coffee_should_be_served() { + return I_should_be_served_a_coffee( true ); } public ThenCoffee no_coffee_should_be_served() { - - return this; + return self(); } public ThenCoffee an_error_should_be_shown() { assertThat( coffeeMachine.message ).startsWith( "Error" ); - return this; + return self(); } - public ThenCoffee the_message_$_is_shown(String message) { + public ThenCoffee the_message_$_is_shown( String message ) { assertThat( coffeeMachine.message ).isEqualTo( message ); - return this; + return self(); + } + + public ThenCoffee there_are_$_coffees_left_in_the_machine( int coffeesLeft ) { + assertThat( coffeeMachine.coffees ).isEqualTo( coffeesLeft ); + return self(); } } diff --git a/jgiven-examples/src/test/java/com/tngtech/jgiven/examples/notimplementedyet/NotImplementYetExampleTest.java b/jgiven-examples/src/test/java/com/tngtech/jgiven/examples/notimplementedyet/NotImplementYetExampleTest.java index 8b1eef3e1f..ec64a9e756 100644 --- a/jgiven-examples/src/test/java/com/tngtech/jgiven/examples/notimplementedyet/NotImplementYetExampleTest.java +++ b/jgiven-examples/src/test/java/com/tngtech/jgiven/examples/notimplementedyet/NotImplementYetExampleTest.java @@ -5,6 +5,7 @@ import com.tngtech.jgiven.annotation.Description; import com.tngtech.jgiven.annotation.NotImplementedYet; import com.tngtech.jgiven.junit.SimpleScenarioTest; +import com.tngtech.jgiven.tags.FeatureNotImplementedYet; @Description( "As a good BDD practitioner,
" + "I want to write my scenarios before I start coding
" @@ -12,6 +13,7 @@ public class NotImplementYetExampleTest extends SimpleScenarioTest { @Test + @FeatureNotImplementedYet @NotImplementedYet public void tests_that_are_not_implemented_yet_can_be_annotated_with_the_NotImplementedYet_annotation() { given().some_state(); diff --git a/jgiven-junit/src/main/java/com/tngtech/jgiven/junit/ScenarioExecutionRule.java b/jgiven-junit/src/main/java/com/tngtech/jgiven/junit/ScenarioExecutionRule.java index 2e95ae5556..c9c909fdd3 100644 --- a/jgiven-junit/src/main/java/com/tngtech/jgiven/junit/ScenarioExecutionRule.java +++ b/jgiven-junit/src/main/java/com/tngtech/jgiven/junit/ScenarioExecutionRule.java @@ -113,7 +113,7 @@ static List getNamedArguments( Statement base, FrameworkMethod me arguments = getArgumentsFrom( constructor, target ); } - return ScenarioUtil.mapArgumentsWithParameterNamesOf( constructorOrMethod, arguments ); + return ScenarioUtil.mapArgumentsWithParameterNames( constructorOrMethod, arguments ); } private static List getArgumentsFrom( Object object, String fieldName ) { diff --git a/jgiven-junit/src/test/java/com/tngtech/jgiven/junit/DataProviderTest.java b/jgiven-junit/src/test/java/com/tngtech/jgiven/junit/DataProviderTest.java index d07a8cd5ca..c819fc0867 100644 --- a/jgiven-junit/src/test/java/com/tngtech/jgiven/junit/DataProviderTest.java +++ b/jgiven-junit/src/test/java/com/tngtech/jgiven/junit/DataProviderTest.java @@ -64,13 +64,31 @@ public void DataProviderRunner_with_tricky_data( int firstArg, int secondArg, in CaseArgumentAnalyser analyser = new CaseArgumentAnalyser(); analyser.analyze( scenarioModel ); ScenarioCaseModel case0 = scenarioModel.getCase( 0 ); - assertParamIndex( case0, 0, 0 ); + assertParameter( case0, 0, scenarioModel.parameterNames.get( 0 ) ); } } - private void assertParamIndex( ScenarioCaseModel case0, int step, int parameterIndex ) { + private void assertParameter( ScenarioCaseModel case0, int step, String parameter ) { Word word = case0.getStep( step ).words.get( 2 ); - assertThat( word.getArgumentInfo().getParameterIndex() ).isEqualTo( parameterIndex ); + assertThat( word.getArgumentInfo().getParameterName() ).isEqualTo( parameter ); + } + + @Test + @DataProvider( { "1", "2", "3" } ) + public void derived_parameters_work( Integer arg ) { + given().some_integer_value( arg ) + .and().another_integer_value( arg * 10 ); + + when().multiply_with_two(); + + ScenarioModel scenarioModel = getScenario().getModel().getScenarios().get( 0 ); + if( scenarioModel.getScenarioCases().size() == 3 ) { + CaseArgumentAnalyser analyser = new CaseArgumentAnalyser(); + analyser.analyze( scenarioModel ); + ScenarioCaseModel case0 = scenarioModel.getCase( 0 ); + assertParameter( case0, 0, scenarioModel.parameterNames.get( 0 ) ); + assertParameter( case0, 1, "param0" ); + } } } diff --git a/jgiven-testng/src/main/java/com/tngtech/jgiven/testng/ScenarioTestListener.java b/jgiven-testng/src/main/java/com/tngtech/jgiven/testng/ScenarioTestListener.java index b77a4b9a66..b66a64f71a 100644 --- a/jgiven-testng/src/main/java/com/tngtech/jgiven/testng/ScenarioTestListener.java +++ b/jgiven-testng/src/main/java/com/tngtech/jgiven/testng/ScenarioTestListener.java @@ -75,7 +75,7 @@ public void onFinish( ITestContext paramITestContext ) { } private List getArgumentsFrom( Method method, ITestResult paramITestResult ) { - return ScenarioUtil.mapArgumentsWithParameterNamesOf( method, asList( paramITestResult.getParameters() ) ); + return ScenarioUtil.mapArgumentsWithParameterNames( method, asList( paramITestResult.getParameters() ) ); } } diff --git a/jgiven-tests/pom.xml b/jgiven-tests/pom.xml index 217e63f78f..a0556d259c 100644 --- a/jgiven-tests/pom.xml +++ b/jgiven-tests/pom.xml @@ -76,14 +76,6 @@ - - org.apache.maven.plugins - maven-deploy-plugin - ${maven-deploy-plugin.version} - - true - - diff --git a/jgiven-tests/src/test/java/com/tngtech/jgiven/report/html/HtmlWriterScenarioTest.java b/jgiven-tests/src/test/java/com/tngtech/jgiven/report/html/HtmlWriterScenarioTest.java index 88f32c62ee..f30570cb74 100644 --- a/jgiven-tests/src/test/java/com/tngtech/jgiven/report/html/HtmlWriterScenarioTest.java +++ b/jgiven-tests/src/test/java/com/tngtech/jgiven/report/html/HtmlWriterScenarioTest.java @@ -12,6 +12,7 @@ import com.tngtech.jgiven.report.model.GivenReportModel; import com.tngtech.jgiven.report.model.StepStatus; import com.tngtech.jgiven.tags.FeatureDataTables; +import com.tngtech.jgiven.tags.FeatureDerivedParameters; import com.tngtech.jgiven.tags.FeatureDuration; import com.tngtech.jgiven.tags.FeatureHtmlReport; import com.tngtech.jgiven.tags.Issue; @@ -81,6 +82,27 @@ public void when_data_tables_are_generated_then_step_parameter_placeholders_are_ .and().the_data_table_has_one_line_for_the_arguments_of_each_case(); } + @Test + @FeatureDataTables + @FeatureDerivedParameters + public void derived_parameters_appear_in_the_data_table() { + given().a_report_model_with_one_scenario() + .and().the_scenario_has_$_cases( 2 ) + .and().the_scenario_has_parameters( "param1" ) + .and().case_$_has_arguments( 1, "a" ) + .and().case_$_has_a_when_step_$_with_argument( 1, "uses the first parameter", "a" ) + .and().case_$_has_a_when_step_$_with_argument_$_and_argument_name_$( 1, "derive a parameter", "aa", "arg" ) + .and().case_$_has_arguments( 2, "b" ) + .and().case_$_has_a_when_step_$_with_argument( 2, "uses the first parameter", "b" ) + .and().case_$_has_a_when_step_$_with_argument_$_and_argument_name_$( 2, "derive a parameter", "bb", "arg" ); + + when().the_HTML_report_is_generated(); + then().the_HTML_report_contains_pattern( "uses the first parameter.*<param1>.*derive" ) + .and().the_HTML_report_contains_pattern( "derive a parameter.*<arg>.*Cases" ) + .and().the_HTML_report_contains_a_data_table_with_header_values( "param1", "arg" ) + .and().the_data_table_has_one_line_for_the_arguments_of_each_case(); + } + @Test public void the_error_message_of_failed_scenarios_are_reported() { given().a_report_model_with_one_scenario() diff --git a/jgiven-tests/src/test/java/com/tngtech/jgiven/report/model/GivenReportModel.java b/jgiven-tests/src/test/java/com/tngtech/jgiven/report/model/GivenReportModel.java index a5f6d43b60..ae11791592 100644 --- a/jgiven-tests/src/test/java/com/tngtech/jgiven/report/model/GivenReportModel.java +++ b/jgiven-tests/src/test/java/com/tngtech/jgiven/report/model/GivenReportModel.java @@ -49,9 +49,10 @@ private void addCase( ScenarioModel scenarioModel ) { } scenarioCaseModel.addStep( "something_happens", Arrays.asList( Word.introWord( "given" ), new Word( "something" ) ), InvocationMode.NORMAL ); + i = 0; for( String arg : scenarioCaseModel.arguments ) { scenarioCaseModel.addStep( "something_happens", asList( Word.introWord( "when" ), - Word.argWord( arg ) ), InvocationMode.NORMAL ); + Word.argWord( "stepArg" + i++, arg ) ), InvocationMode.NORMAL ); } } @@ -138,7 +139,11 @@ private ScenarioCaseModel getCase( int ncase ) { } public SELF case_$_has_a_when_step_$_with_argument( int ncase, String name, String arg ) { - getCase( ncase ).addStep( name, Arrays.asList( Word.introWord( "when" ), new Word( name ), Word.argWord( arg ) ), + return case_$_has_a_when_step_$_with_argument_$_and_argument_name_$( ncase, name, arg, "argName" ); + } + + public SELF case_$_has_a_when_step_$_with_argument_$_and_argument_name_$( int ncase, String name, String arg, String argName ) { + getCase( ncase ).addStep( name, Arrays.asList( Word.introWord( "when" ), new Word( name ), Word.argWord( argName, arg ) ), InvocationMode.NORMAL ); return self(); } diff --git a/jgiven-tests/src/test/java/com/tngtech/jgiven/tags/FeatureDataTables.java b/jgiven-tests/src/test/java/com/tngtech/jgiven/tags/FeatureDataTables.java index 4e049dc524..f7e2dd2d73 100644 --- a/jgiven-tests/src/test/java/com/tngtech/jgiven/tags/FeatureDataTables.java +++ b/jgiven-tests/src/test/java/com/tngtech/jgiven/tags/FeatureDataTables.java @@ -5,7 +5,10 @@ import com.tngtech.jgiven.annotation.IsTag; -@IsTag( type = "Feature", value = "Data Tables", description = "Data tables can be generated in reports" ) +@IsTag( type = "Feature", value = "Data Tables", + description = "In order to get a better overview over the different cases of a scenario
" + + "As a human,
" + + "I want to have different cases represented as a data table" ) @Retention( RetentionPolicy.RUNTIME ) public @interface FeatureDataTables { diff --git a/jgiven-tests/src/test/java/com/tngtech/jgiven/tags/FeatureDerivedParameters.java b/jgiven-tests/src/test/java/com/tngtech/jgiven/tags/FeatureDerivedParameters.java new file mode 100644 index 0000000000..2c5749e014 --- /dev/null +++ b/jgiven-tests/src/test/java/com/tngtech/jgiven/tags/FeatureDerivedParameters.java @@ -0,0 +1,15 @@ +package com.tngtech.jgiven.tags; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import com.tngtech.jgiven.annotation.IsTag; + +@IsTag( type = "Feature", value = "Derived Parameters", + description = "In order to not have to specify easily derivable parameters explicitly
" + + "As a developer,
" + + "I want that step arguments derived from parameters appear in a data table" ) +@Retention( RetentionPolicy.RUNTIME ) +public @interface FeatureDerivedParameters { + +} diff --git a/jgiven-tests/src/test/java/com/tngtech/jgiven/tags/FeatureNotImplementedYet.java b/jgiven-tests/src/test/java/com/tngtech/jgiven/tags/FeatureNotImplementedYet.java index 8142d30c7a..7dd55229a8 100644 --- a/jgiven-tests/src/test/java/com/tngtech/jgiven/tags/FeatureNotImplementedYet.java +++ b/jgiven-tests/src/test/java/com/tngtech/jgiven/tags/FeatureNotImplementedYet.java @@ -6,7 +6,9 @@ import com.tngtech.jgiven.annotation.IsTag; @IsTag( type = "Feature", value = "NotImplementedYet Annotation", - description = "tests can be annotated with @NotImplementedYet" ) + description = "As a good BDD practitioner,
" + + "I want to write my scenarios before I start coding
" + + "In order to discuss them with business stakeholders" ) @Retention( RetentionPolicy.RUNTIME ) public @interface FeatureNotImplementedYet {