Skip to content

Commit

Permalink
Implement derived parameters (#15)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jan Schäfer committed Sep 18, 2014
1 parent 7aa69a8 commit 82ad3be
Show file tree
Hide file tree
Showing 29 changed files with 280 additions and 93 deletions.
15 changes: 9 additions & 6 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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)
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand Down Expand Up @@ -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<NamedArgument> namedArguments = ScenarioUtil.mapArgumentsWithParameterNames( paramMethod, Arrays.asList( arguments ) );
listener.stepMethodInvoked( paramMethod, namedArguments, mode );
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public void scenarioStarted( String string ) {}
public void scenarioStarted( Method method, List<NamedArgument> arguments ) {}

@Override
public void stepMethodInvoked( Method paramMethod, List<Object> arguments, InvocationMode mode ) {}
public void stepMethodInvoked( Method method, List<NamedArgument> arguments, InvocationMode mode ) {}

@Override
public void introWordAdded( String word ) {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public interface ScenarioListener {

void scenarioStarted( Method method, List<NamedArgument> arguments );

void stepMethodInvoked( Method paramMethod, List<Object> arguments, InvocationMode mode );
void stepMethodInvoked( Method method, List<NamedArgument> arguments, InvocationMode mode );

void introWordAdded( String word );

Expand All @@ -22,5 +22,4 @@ public interface ScenarioListener {
void stepMethodFinished( long durationInNanos );

void scenarioFinished();

}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public class ScenarioUtil {
/**
* @throws NullPointerException iif {@code constructorOrMethod} is {@code null}
*/
public static List<NamedArgument> mapArgumentsWithParameterNamesOf( AccessibleObject contructorOrMethod, List<Object> arguments ) {
public static List<NamedArgument> mapArgumentsWithParameterNames( AccessibleObject contructorOrMethod, List<Object> arguments ) {
Preconditions.checkNotNull( contructorOrMethod, "constructorOrMethod must not be null." );
Preconditions.checkNotNull( arguments, "arguments must not be null" );

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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( "<br />" ) ? "multiline" : "";
String caseClass = word.getArgumentInfo().isCaseArg() ? "caseArgument" : "argument";
String caseClass = word.getArgumentInfo().isParameter() ? "caseArgument" : "argument";
writer.print( format( "<span class='%s %s'>%s</span>", caseClass, multiLine, value ) );
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 {
Expand All @@ -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."
Expand All @@ -63,7 +66,7 @@ private boolean wordsAreEqual( List<Word> firstWords, List<Word> 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 ) ) ) {
Expand All @@ -73,10 +76,25 @@ private boolean wordsAreEqual( List<Word> firstWords, List<Word> words ) {
return true;
}

private void reduceMatrix( List<List<ArgumentHolder>> argumentMatrix ) {
int nArguments = argumentMatrix.get( 0 ).size();
private static final class CaseArguments {
final ScenarioCaseModel caseModel;
final List<ArgumentHolder> arguments;

private CaseArguments( ScenarioCaseModel model, List<ArgumentHolder> arguments ) {
this.caseModel = model;
this.arguments = arguments;
}

public ArgumentHolder get( int i ) {
return arguments.get( i );
}
}

private void reduceMatrix( ScenarioModel scenarioModel, List<CaseArguments> argumentMatrix ) {
int nArguments = argumentMatrix.get( 0 ).arguments.size();
List<String> derivedParams = Lists.newArrayList();
for( int iArg = 0; iArg < nArguments; iArg++ ) {
Set<Integer> currentSet = Sets.newLinkedHashSet();
Set<String> 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 );
Expand All @@ -85,37 +103,70 @@ private void reduceMatrix( List<List<ArgumentHolder>> 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<CaseArguments> 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<Integer> params;
Set<String> params;
}

/**
* Collect all possible argument matches.
* This results in a set of possible case arguments for each step argument
*/
static class CollectPhase extends ReportModelVisitor {
List<List<ArgumentHolder>> argumentMatrix = Lists.newArrayList();
List<CaseArguments> argumentMatrix = Lists.newArrayList();
List<ArgumentHolder> argumentsOfCurrentCase;
List<List<Word>> allWords = Lists.newArrayList();
List<Word> 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 );
}
Expand All @@ -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<Integer> getMatchingIndices( Word word ) {
Set<Integer> matchingIndices = Sets.newLinkedHashSet();
private Set<String> getMatchingParameters( Word word ) {
Set<String> 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;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 );
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ private String camelCaseToReadableText( String camelCase ) {
return WordUtil.capitalize( scenarioDescription );
}

public void addStepMethod( Method paramMethod, List<Object> arguments, InvocationMode mode ) {
public void addStepMethod( Method paramMethod, List<NamedArgument> arguments, InvocationMode mode ) {
String name;
Description description = paramMethod.getAnnotation( Description.class );
if( description != null ) {
Expand Down Expand Up @@ -158,7 +158,7 @@ private ScenarioCaseModel getCurrentScenarioCase() {
}

@Override
public void stepMethodInvoked( Method paramMethod, List<Object> arguments, InvocationMode mode ) {
public void stepMethodInvoked( Method paramMethod, List<NamedArgument> arguments, InvocationMode mode ) {
if( !isStepMethod( paramMethod ) ) {
return;
}
Expand Down
Loading

0 comments on commit 82ad3be

Please sign in to comment.