-
-
Notifications
You must be signed in to change notification settings - Fork 695
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Cross platform DataTable implementation #210
Comments
im here, my name is Osher If I get you right - you want instead of:
to write:
and assume ParameterType like
@aslakhellesoy, So far, did I get you right? My only fear is that it makes the table technical. I can generally see the consistency - we imply that we require them to use quotes in steps - But this is not a standard. I saw many step definitions that extract string values from gherkin lines without requiring them to be enclosed with quotes. |
one more question - I need to clarify I understand how ParameterType works: i.e - I speculate order of operation, can you confirm it?
Can you point me to the relevant code so I can read it? |
About quotes - i.e - enclosed string will take precedence, and defaulting to string if no other type is matched What happens if a capture matches two types? do we intend to throw like we do with matching two step defs? |
I just had an idea for how to use parameter types with tables. The Cucumber Expression can be followed by a type to be used for transformation. This can default to The original discussion was in slack |
Not fully clear... |
I'll try to give an example (in JavaScript): defineParameterType({
name: 'ingredient',
transformer: ({name, amount, unit}) => new Person({name, amount, unit}),
// no regexp - this parameter type is only used for tables, not cucumber expressions
}) A class: class Ingredient {
constructor({name, amount, unit}) {}
} A data table: Given the following ingredients:
| name | amount | unit |
| sugar | 3 | tbsp |
| lemons | 3 | count |
| water | 1 | l | A step definition: Given('the following ingredients:', 'ingredient', function(ingredients) {
// ingredients is an Array of Ingredient
}) This is different from what I said in the comment above (but more aligned with the end of the linked slack discussion) |
Well, I'm not aware of that discussion, however - I can see that this solution does not work out of the box - it requires users to define transformers. Before that - mind that we started with a PR that aims to let user define data-type of fields vertically, and we end up discussing defining data-type of a complete object horizontally Last groan here - is that the requirement for explicitness and readability is lost, because the Gherkin spec doc does not provide the data-types - they are inferred. Anyway - I don't see how one is related to the other - these are two different use-cases - we can support both. Now - to business 😄
but
AFAI understand - here the class
or
I hope I'm right here... ... And that the 2nd argument in
refers to
which is AFAIK - an API change of a future change. In that case I would propose to try take the argument name from I also can suggest that if we support optional attributes (like |
one more last cent: If we really must go for an api change and introducing an additional argument - let it be a destructurable property-bag, and not a string. |
FYI in ruby we already can call in World:
|
Some notes for Java. Currently the ParameterTypeRegistry only contains a mapping of Without naming a table row transform lambda step definitions should be able to handle any non generic type as their arguments. When declaring a table row transform they must be able to handle any type. Annotation based step definitions should be able to handle all types without naming a table row transform. Though they may still do so as a form disambiguation. When no Java provides several mappings from a DataTable. They're currently all Type based but should also get a name based version.
To facilitate diffing domain objects against data tables java also provides a transform
This would require that the mappings are extended to include a reverse transform. E.g. edit: To avoid creating dependencies between the cucumber-expressions library and the data-table library |
A ParameterType has a If we do that, the Java API for specifying what Given("the following ingredients:", Ingredient.class, (List<Ingredient> ingredients) -> {
// ingredients is an Array of Ingredient
}) Or if the generic type can be inferred: Given("the following ingredients:", (List<Ingredient> ingredients) -> {
// ingredients is an Array of Ingredient
}) |
Its type is bound to the transform. So the Transform would have to be public ParameterType(String name, String regexp, Class<T> type, Transformer<String, T> transformer) {
this(name, singletonList(regexp), type, transformer, true, false);
}
public ParameterType(String name, Class<T> type, Transformer<Map<String,String>, T> transformer) {
this(name, emptyList(), type, transformer, false, false);
}
Yes and when there are multiple ways to create ingredients from a data table we can also use the name based version: Given("the following english ingredients:", "english-ingredients" (List<Ingredient> ingredients) -> {
// ingredients is an Array of Ingredient
})
Given("the following spanish ingredients:", "spanish-ingredients" (List<Ingredient> ingredients) -> {
// ingredients is an Array of Ingredient
}) edit: There might not always be a |
Overall I am not seeing any disambiguations problems with I am seeing about disambiguation problems with Given some stuff:
| color | currency |
| red | GBP |
| green | NOK |
| blue | EUR | without explicitly defining a transform for BankNotes. Then for I suppose I could select the one that matches the regex? And if multiple match, throw an exception? |
See e032eba (branch |
The code that is now in Transform<Map<String,String>, String> rowTransformer = registry.lookupByType(rowtype);
for (Map<String, String> tableRow : rows) {
list.add(rowTransformer.transform(tableRow);
...
} It shouldn't be too different from the existing TableConverter.java. Replace xStream with registry.lookupByType(rowtype).transform(tableRow) and we've got most of it. edit: The transform should work on a |
You mean The existing Could illustrate what you mean by adding some tests and code to my spike branch? |
Sure! Just need some time. |
Spike got a bit big. Made a new branch. I think this is how it should work: |
This issue has been automatically marked as stale because it has not had recent activity. It will be closed in a week if no further activity occurs. |
I started the data tables/ cucumber expression spike for cucumber-jvm with the assumption that we could use the cucumber expressions to transform single elements in a data table. For example:
Should be transformed to a This supposedly could be done by looking up an expression for a type say Integer and use the associated transform to transform the field of the data table. In practice however this will run into disambiguation problems and other unexpected chicanery. For example looking up String forces us to chose between the expressions And this is just the build in types. With custom types it gets even more interesting. So right now I reckon that using cucumber expressions for data tables is just the wrong idea. We might get it to work by force but conceptually the mapping is wrong. |
There are three ways in which a table might be converted to an instance of a certain type.
The the transformer that handles this conversion can either be explicitly defined by name or implicitly by the target type. When trying to find a transformer by type they should be considered in order from most (convert the whole table) to least (convert individual cells). Dealing with Lists, Lists of Lists and single instances is fairly straightforward. Search the converters in order and apply its transform. If no transform is found. Look for a list of the type you want to create. If no transform is found look for a list of a list of the type. Give up otherwise. Dealing with maps requires some additional convention. First lookup a single instance transformer. If none exists then a map consists of a keyset and value set. The key set is created by taking the left column of a data table and transforming it to a list of keys. Note that a single column is also a datatable. The value set is created by taking the other columns and transforming them into a list of values. Note that this is recursive (but only once). These two lists are then paired up into a map. This leads to the following possibilities: Scenario: Datatable to single object
Given the following chessboard:
| |1|2|3|
|A|♘| |♝|
|B| | | |
|C| |♝| |
Then I have a board with one knight and two bishops
Scenario: Datatable rows to list of object
Given the following groceries:
| name | price |
| milk | 9 |
| bread | 7 |
| soap | 5 |
Then I have:
"""
[
{name: "milk", price: "9"},
{name: "bread", price: "7"},
{name: "soap", price: "5"}
]
"""
Scenario: Datatable to list of lists of objects
Given the following groceries:
| name | price |
| milk | 9 |
| bread | 7 |
| soap | 5 |
Then I have:
"""
[
["name", 9"],
["milk", "7"],
["bread", "5"],
]
"""
Scenario: Datatable rows to map
Given the priorities for groceries:
| low | milk |
| medium | bread |
| high | soap |
Then I have:
"""
{
low: "milk",
medium: "bread",
high: "soap"
]
"""
Scenario: Datatable rows to map of maps
Given the following groceries:
| | name | price |
| low | milk | 9 |
| medium | bread | 7 |
| high | soap | 5 |
Then I have:
"""
{
low: {name: "milk", price: "9"},
medium: {name: "bread", price: "7"},
high: {name: "soap", price: "5"}
]
"""
Scenario: Datatable rows to map of lists
Given the following groceries:
| jones | milk | bread | soap |
| smith | bread | milk | |
| jackson | soap | | |
Then I have:
"""
{
jones: ["milk", "bread", "soap" ],
smith: ["bread", "milk" ],
jackson: ["soap" ],
]
"""
Scenario: Datatable rows to map of values
Given the following groceries:
| jones | milk | bread | soap |
| smith | bread | milk | |
| jackson | soap | | |
Then I have:
"""
{
jones: Grocceries ["milk", "bread", "soap" ],
smith: Grocceries ["bread", "milk" ],
jackson: Grocceries ["soap" ],
]
""" But I might have missed a few |
I would have loved to see this, but I'm closing this issue. We're establishing a new issue triage process where we prioritise the oldest issues first. Given the choice to work on this next or close it, I'm closing it because it's too big. If anyone feels strongly about this, please submit one or more pull requests. |
There are several
DataTable
implementations around, (in order of appearance):We should take the best parts of all these (slightly divergent) implementations and reimplement it as a standalone (cross platform) library with a shared test suite. This has already been done in Cucumber Expressions, Tag Expressions and of course, Gherkin.
I think this implementation should be decoupled from the Gherkin library - it should not depend on Gherkin's DataTable implementation. Instead, it should be possible to construct a new
DataTable
object with a simple array of array of strings.For this new implementation I would like data tables to support the Cucumber Expressions' custom parameter types.
This would allow users to define parameter types once, and use them both in steps and data table headers. For example:
The corresponding
DataTable
would then automatically convert columns to the right type.If you're interested in helping out building this new
DataTable
put your name below!The text was updated successfully, but these errors were encountered: