JBehave
  1. JBehave
  2. JBEHAVE-398

Provide table row values as converted parameters

    Details

    • Type: New Feature New Feature
    • Status: Resolved Resolved
    • Priority: Major Major
    • Resolution: Fixed
    • Affects Version/s: 3.1.2
    • Fix Version/s: 3.2
    • Component/s: Core
    • Labels:
      None
    • Number of attachments :
      0

      Description

      Currently, ExamplesTable provides the row values as Map<String,String>.

      In most cases, users will need to convert these values. We should provide the row values converted as specified types using the same ParameterConverters defined for the step conversion.

      We should also allow for provision of defaults, both accessing single value and as a table structure.

        Activity

        Hide
        Daniel Godfrey added a comment -

        I've pushed a patch into a branch on my github fork for this.
        https://github.com/dgodfrey/jbehave-core/tree/nested-conversion

        Show
        Daniel Godfrey added a comment - I've pushed a patch into a branch on my github fork for this. https://github.com/dgodfrey/jbehave-core/tree/nested-conversion
        Hide
        Mauro Talevi added a comment -

        The reason for having different parameter converters for different raw types is to distinguish the parameter List<String> from List<Number> (as you can see in the accept(Type) method.
        We can add a parameter converter for lists that delegates to the element parameter converter (which would be specified when instantiating the converter), so it can reused for different types of element converters, but I don't see the benefit of some of the elements in the patch. In particular, changing the ParameterConverters from class to interface and changing the converter method signature would break backward compat.

        Show
        Mauro Talevi added a comment - The reason for having different parameter converters for different raw types is to distinguish the parameter List<String> from List<Number> (as you can see in the accept(Type) method. We can add a parameter converter for lists that delegates to the element parameter converter (which would be specified when instantiating the converter), so it can reused for different types of element converters, but I don't see the benefit of some of the elements in the patch. In particular, changing the ParameterConverters from class to interface and changing the converter method signature would break backward compat.
        Hide
        Daniel Godfrey added a comment -

        Creating a ListConverter which takes a delegate ParameterConverter in the constructor still entails boilerplate and also means I can't then (easily) use a @AsParameterConverter method as the delegate. The ListConverter I pushed solves all of this and works for List<T> where T can be any Class, by delegating back to ParametersConverters to do the actual conversion.

        I only renamed ParametersConverters -> DefaultParametersConverters and extracted the interface, so I could mock the interface when testing some of the ParameterConverters. Just made the testing a bit easier.

        I also pushed a follow-up patch that adds a ParameterTable, which wraps an ExamplesTable, providing access to converting the cells into other types, again using the existing ParametersConverters. I tried just injecting an instance of ParametersConverters using Guice, but it didn't include any of my @AsParameterConverter conversions, only the default ones.

        Show
        Daniel Godfrey added a comment - Creating a ListConverter which takes a delegate ParameterConverter in the constructor still entails boilerplate and also means I can't then (easily) use a @AsParameterConverter method as the delegate. The ListConverter I pushed solves all of this and works for List<T> where T can be any Class, by delegating back to ParametersConverters to do the actual conversion. I only renamed ParametersConverters -> DefaultParametersConverters and extracted the interface, so I could mock the interface when testing some of the ParameterConverters. Just made the testing a bit easier. I also pushed a follow-up patch that adds a ParameterTable, which wraps an ExamplesTable, providing access to converting the cells into other types, again using the existing ParametersConverters. I tried just injecting an instance of ParametersConverters using Guice, but it didn't include any of my @AsParameterConverter conversions, only the default ones.
        Hide
        Mauro Talevi added a comment -

        The rename would break backward compat, and if primarily motivated by testing reasons, it is not a good idea.

        Let's focus on the functionality requirement and try to satisfy it with current design. I see no problem in adding other parameter converters but would not want to break backward compat.

        So what is your primary use case: using Lists of parameters converted via methods annotated with @AsParameterConverter?

        Show
        Mauro Talevi added a comment - The rename would break backward compat, and if primarily motivated by testing reasons, it is not a good idea. Let's focus on the functionality requirement and try to satisfy it with current design. I see no problem in adding other parameter converters but would not want to break backward compat. So what is your primary use case: using Lists of parameters converted via methods annotated with @AsParameterConverter?
        Hide
        Daniel Godfrey added a comment -

        I've pushed my extensions to the initial patch for this onto
        https://github.com/dgodfrey/jbehave-core/tree/jbehave_398

        Show
        Daniel Godfrey added a comment - I've pushed my extensions to the initial patch for this onto https://github.com/dgodfrey/jbehave-core/tree/jbehave_398
        Hide
        Mauro Talevi added a comment -

        Hi Dan, I've pull the latest change from branch. I'm really not too convinced about the rename of Row -> Record. Also, can you please clarify the usecase for the chaining implementation and the defaults?

        Show
        Mauro Talevi added a comment - Hi Dan, I've pull the latest change from branch. I'm really not too convinced about the rename of Row -> Record. Also, can you please clarify the usecase for the chaining implementation and the defaults?
        Hide
        Daniel Godfrey added a comment -

        I'm mainly using the tables to set up a database and then to import data. The defaults allows me to specify values in java, so the stories then don't need to contain the data if it's not relevant to the test, but lets me reuse the same steps.

        So (rubbish example) given a database table that needs a name and age, I can have the following line in my @Given step:

        dao.addUser(record.value("Name"), record.valueAs("Age", Integer.class, "19")

        Then in 1 scenario/story where age is irrelevant I can simply add

        When new user added:
          |Name|
          |Dan |
        

        Then in a different scenario where the age is relevant to the test, I can put

        When new user added:
          |Name|Age|
          |Dan |32 |
          |Mark|21 |
        

        The reason for the ExamplesTable#withDefaults(Record) method is it make this easy to do in a scenario:

        Given user defaults:
          |Age|
          |22 |
        
        When new user addded:
          |Name|
          |Dan |
        

        This isn't my actual domain, It's trade settlement/confirmation, so there's lots of data that's required for each record, most of which is pretty irrelevant for the current test, so this allows the tests to focus on what's important.

        I can add some examples to the Trader example in JBehave if you want?

        I'm not convinced myself about the Row -> Record rename either, just convinced that Row is the wrong name as they're more useful than simply as Rows. I've just written a ParamaterConverter that will covert "key:value\nkey2:value2" into a Record and I've been considering using them to read the columns of a table, so you can have row/column headers. One idea that crossed my mind was to make ConvertingRecord extend Map<String, String>, and change ExamplesTable#getRow to return ConvertingRecord (renamed to something sensible like ConvertingMap) rather than Map<String, String>. Haven't really thought about it in detail, IE backwards compatibility implications, etc.

        Show
        Daniel Godfrey added a comment - I'm mainly using the tables to set up a database and then to import data. The defaults allows me to specify values in java, so the stories then don't need to contain the data if it's not relevant to the test, but lets me reuse the same steps. So (rubbish example) given a database table that needs a name and age, I can have the following line in my @Given step: dao.addUser(record.value("Name"), record.valueAs("Age", Integer.class, "19") Then in 1 scenario/story where age is irrelevant I can simply add When new user added: |Name| |Dan | Then in a different scenario where the age is relevant to the test, I can put When new user added: |Name|Age| |Dan |32 | |Mark|21 | The reason for the ExamplesTable#withDefaults(Record) method is it make this easy to do in a scenario: Given user defaults: |Age| |22 | When new user addded: |Name| |Dan | This isn't my actual domain, It's trade settlement/confirmation, so there's lots of data that's required for each record, most of which is pretty irrelevant for the current test, so this allows the tests to focus on what's important. I can add some examples to the Trader example in JBehave if you want? I'm not convinced myself about the Row -> Record rename either, just convinced that Row is the wrong name as they're more useful than simply as Rows. I've just written a ParamaterConverter that will covert "key:value\nkey2:value2" into a Record and I've been considering using them to read the columns of a table, so you can have row/column headers. One idea that crossed my mind was to make ConvertingRecord extend Map<String, String>, and change ExamplesTable#getRow to return ConvertingRecord (renamed to something sensible like ConvertingMap) rather than Map<String, String>. Haven't really thought about it in detail, IE backwards compatibility implications, etc.
        Hide
        Mauro Talevi added a comment -

        Yes, an example story in trader would be most welcome!

        I'm starting to get the use case, but a concrete example would be useful.

        Show
        Mauro Talevi added a comment - Yes, an example story in trader would be most welcome! I'm starting to get the use case, but a concrete example would be useful.
        Hide
        Mauro Talevi added a comment -

        Dan, I've renamed Record -> Parameters (below) as it seems a logic name to describe a collection of parameters. I've removed the need to subclass to a specific converting implementation. The map implementation has been removed as it is a corner case of the converting implementation. The refactoring maintained the unit test behaviour as specified by you. Do note that I've also move the Parameters classes to the steps package, as it's where most of the parameter-related logic is.

        public interface Parameters {
        
            /**
             * Determines if the parameter of a given name is found
             * 
             * @param name the name of the parameter
             * @return A boolean, <code>true</code> if found
             */
            boolean hasValue(String name);
        
            /**
             * Returns the value for parameter of a given name
             * 
             * @param type the Class of type <T> to convert to
             * @param name the name of the parameter
             * @return The value of type <T>
             */
            <T> T valueAs(String name, Class<T> type);
        
            /**
             * Returns the value for parameter of a given name and provides a default
             * value if the name is not found
             * 
             * @param type Class of type <T> to convert to
             * @param name the name of the parameter
             * @param defaultValue the default value if the name doesn't exist.
             * @return The value of type <T>
             */
            <T> T valueAs(String name, Class<T> type, T defaultValue);
        
            /**
             * Returns the map of all the values as Strings
             * 
             * @return The Map of values
             */
            Map<String, String> values();
        
        }
        
        Show
        Mauro Talevi added a comment - Dan, I've renamed Record -> Parameters (below) as it seems a logic name to describe a collection of parameters. I've removed the need to subclass to a specific converting implementation. The map implementation has been removed as it is a corner case of the converting implementation. The refactoring maintained the unit test behaviour as specified by you. Do note that I've also move the Parameters classes to the steps package, as it's where most of the parameter-related logic is. public interface Parameters { /** * Determines if the parameter of a given name is found * * @param name the name of the parameter * @ return A boolean , <code> true </code> if found */ boolean hasValue( String name); /** * Returns the value for parameter of a given name * * @param type the Class of type <T> to convert to * @param name the name of the parameter * @ return The value of type <T> */ <T> T valueAs( String name, Class <T> type); /** * Returns the value for parameter of a given name and provides a default * value if the name is not found * * @param type Class of type <T> to convert to * @param name the name of the parameter * @param defaultValue the default value if the name doesn't exist. * @ return The value of type <T> */ <T> T valueAs( String name, Class <T> type, T defaultValue); /** * Returns the map of all the values as Strings * * @ return The Map of values */ Map< String , String > values(); }
        Hide
        Mauro Talevi added a comment -

        Updated trader example to use table with parameters defaults.

        Show
        Mauro Talevi added a comment - Updated trader example to use table with parameters defaults.
        Hide
        Daniel Godfrey added a comment -

        Makes sense, Parameters is a much better name.

        I don't like the hierarchy reduction though, it means ChainedParameters needs to implement all of the valueAs methods, which as far as I can tell, won't ever be called anywhere. I've pushed another patch to https://github.com/dgodfrey/jbehave-core/tree/jbehave_398, that introduces another interface, RawParameters, that removes this necessity. It also removes the hasValue method completely, as it's not required any more. IMO, it's enough of a code reduction to add the tiny new interface for.

        Show
        Daniel Godfrey added a comment - Makes sense, Parameters is a much better name. I don't like the hierarchy reduction though, it means ChainedParameters needs to implement all of the valueAs methods, which as far as I can tell, won't ever be called anywhere. I've pushed another patch to https://github.com/dgodfrey/jbehave-core/tree/jbehave_398 , that introduces another interface, RawParameters, that removes this necessity. It also removes the hasValue method completely, as it's not required any more. IMO, it's enough of a code reduction to add the tiny new interface for.
        Hide
        Mauro Talevi added a comment -

        Pulled up Row interface with the "raw" values, which is implemented by ChainedRow, as suggested in the patch.

        Show
        Mauro Talevi added a comment - Pulled up Row interface with the "raw" values, which is implemented by ChainedRow, as suggested in the patch.

          People

          • Assignee:
            Mauro Talevi
            Reporter:
            Daniel Godfrey
          • Votes:
            0 Vote for this issue
            Watchers:
            0 Start watching this issue

            Dates

            • Created:
              Updated:
              Resolved: