Closed robstoll closed 4 years ago
Is it an option to add an overloaded expect
for every array type, that returns an appropriate List
? As far as I can see, there is nothing specific about an array that we cannot check on a List
. It would make the API easier to use and mean a manageable amount of maintenance effort.
You mean overloads which basically delegate to expect(arr).asList()
right?
That would work, but only cover this particular case. It would not cover the following cases:
asList()
asIterable()
or asList()
and I would prefer if things always need to be done the same way. Otherwise it seems a bit like an inconsistency that one can do expect(arr).contains(...)
but not eg. expect(arrs).get(0).contains(...)
Hm, I see your point. Then generating the functions for any matcher that is defined for Expect<List<…>>
would be the most maintainable solution. But I agree that it’s questionable whether it is worth the effort. Note that we would also need to make sure that third parties can use such a generator for their extensions.
On the other hand, this peculiarity of atrium might be something that shies people away in the very beginning, when picking their assertion library (“I am not going to use that, I can’t even test arrays!”).
On the other hand, this peculiarity of atrium might be something that shies people away in the very beginning, when picking their assertion library (“I am not going to use that, I can’t even test arrays!”).
Would be an argument to add at least overloads for expect(arr)
but I don't know... is expect(arr).asList()
really such a pain?
@dalewking what Array type do you usually deal with: Array<T>
or one of the specializations IntArray
etc.?
In the case I needed it was an Array
is
expect(arr).asList()
really such a pain?
I think using it is not a big deal once you know it. However, finding out that it is meant to be used like that might be. People just want to write down expect(myArr).hasLength(3)
or expect(myArr).get(2).toBe(42)
and are irritated why it is not possible. asList
is not the obvious solution because, in that situation, people don’t want a list. They want to test an array.
That’s my take. Maybe @dalewking can talk about his experience.
On another note: would adding array overloads to all functions that deconstruct an Expect
be that much of a maintenance burden? It clutters the code, that is true. But it would only be at the API level. And since the API is meant to be stable anyway, we would likely seldomly touch the functions again. So maybe it would be less of a maintenance burden (because there is no maintenance on them) but rather a mere annoyance?
is expect(arr).asList() really such a pain?
Believe you mean expect(arr.toList()). Is it a show stopper issue? No, and that is of course what I had to do. But when you try expect(arr).contains(...)
and find that it is not supported would you feel that the fault of the test I am writing or that Atrium is missing a very basic feature?
I meant expect(arr).asList()
or expect(arr).asIterable()
in case you use a version < 0.9.0
This can be used at any place where your subject is an array to turn it into a list. E.g.:
expect(enum).feature { f(it::arr) }.asList().contains(....)
dalewking: But when you try expect(arr).contains(...) and find that it is not supported would you feel that the fault of the test I am writing or that Atrium is missing a very basic feature?
I see your point and I am not against adding more support for arrays it should just be in a way which is manageable.
jGleitz: Would adding array overloads to all functions that deconstruct an Expect be that much of a maintenance burden?
Yes, I think you really underestimate this. Have a look at arrayAssertions.kt it covers one functionality asList
once without argument and once with an assertionCreator
. We had to define 19 functions to support all Arrays. And the same will apply to most functions we defined for Iterable/List.
It's not like we don't add new functionality. Adding something to Iterable/List means again 9-11 more overloads to add for Array and if we are on it, why not also for Sequence
. And it is not like we never change something in the API. A simple thing such as fixing a typo in KDoc suddenly needs to be done at ~10 places in addition.
Note further, that expect(arrayOf(1,2)).get(0) { ... }
should return Expect<Array<Int>>
and not Expect<List<Int>>
. Which means we need to adopt the current builders which are based on Iterable or provide own builders for Array (which would be the better choice). There was some years back one contributor which started to work on this but eventually stopped because in his eyes it seemed not worth enough to go down this path and this was a contributor working a lot with arrays. He finally preferred the asIterable
function over adding so much duplication.
Nevertheless, I support the idea of having better support for array. I would strive for a generative approach but would also be fine with the following compromise:
good first issue
for each feature we currently have (I guess will be something around 20 tasks) to duplicate the corresponding feature for the 9 array types.How does that sound to you? If you don't like the compromise then I'll create an issue for the generative approach instead.
Thanks for the elaborate explanation.
I want to clarify before I make a decision on whether I want to put up with this: I was still thinking of making deconstructing functions support arrays. E.g. add overloads like
fun expect(subject: Array) = expect(subject).asList()
and
Expect<List<Array<*>>.get(index: Int) = get(index).asList()
and similarily for the feature functions, etc. However, your explanation still seems to talk about copying List
matchers.
From my point of view, my proposal has some advantages: When copying the List
matchers to add Array
versions, we need to cover almost all of them for the change to be useful. On the other hand, I expect that we already cover 80% of use cases if we just add the expect
overload I propose.
Note further, that
expect(arrayOf(1,2)).get(0) { ... }
should returnExpect<Array<Int>>
and notExpect<List<Int>>
.
With my approach, I think it would rightfully return an Expect<ListExpect<Array>
versions.
What are your thoughts on the advantages and disadvantages of the two approaches?
and if we are on it, why not also for
Sequence
I agree, if we tackle arrays, we should also support Sequence
.
How does that sound to you?
Bowing out of this conversation as I have had to abandon Atrium due to lack of Kotlin Native iOS support
@dalewking fair enough, thanks for your input so far, appreciated :+1:
@jGleitz if I get you right, you would like to turn Arrays into Lists for all cases where a function could potentially return an array. I have a very bad feeling with this because:
Also, if I were using array a lot then it is likely that I would create extension-methods for it. if we turn array into a list, then I could not use it in tests and would need to duplicate it on List.
if I get you right, you would like to turn Arrays into Lists for all cases where a function could potentially return an array.
Yes, that is my idea.
it could be it requires even more duplication as you need to add special overloads for the case where a type parameter is an Array
I am not sure what you mean by that. From my understanding, we would need array overloads for every function that deconstructs an Expect
. I don’t know whether those are more or less, but I would intuitively expect that there are less.
we cannot force a user to do this and most likely a user won't, so back to the initial problem that the user has suddenly an Array at hand.
I am also not sure what you mean by that. Somebody who is just using the atrium API would automatically get an Expect<List>
whenever they would normally get an Expect<Array>
. If they use custom matchers which are not based on atrium’s existing matchers and hence do not convert, then users would be forced to call toList()
themselves, that is true. But I expect that those cases will be very rare. And the remedy is still not that bad.
Also, if I were using array a lot then it is likely that I would create extension-methods for it. if we turn array into a list, then I could not use it in tests and would need to duplicate it on List.
That is a good point. I would need to see some use cases to judge how bad that is, though. Because where would I use those functions? Maybe we can adapt the feature functions to deal with that? Maybe I use the functions before handing the values to atrium? As far as I can see, it would only be a problem if I use the functions on a value after I have passed it to expect
, e.g. in feature matchers.
above I guessed that we have around 20 functions to transform for Array. We have alone 18 feature overloads + 14 MetaFeatureOption + 10 MetaFeatureBuilder This would not be an issue if we use a generative approach but would be if we do it manually IMO.
What I meant with
you need to add special overloads for the case where a type parameter is an Array
Every time you write an own assertion function for a type which has type parameters where you want to extract this type you would need to add the 9 to 11 overloads for array. For instance:
Expect<Result<T>>.isSuccess
With your proposition we put the maintenance burden not only on us but also on potential Atrium extension writers. Say one writes an extension for Arrow which has a lot of data structures with type parameters (and I guess this will happen at one point). Do you see what I mean?
If we duplicate the Iterable/List functions on the other hand, then we have:
asList()
feature
Hm. It sounded all well in my head. But you are right, duplicating the assertions is the better approach.
Alright, the remaining question is how we want to proceed: create a task for a generative approach or are you willing to do the work as suggested hear: https://github.com/robstoll/atrium-roadmap/issues/75#issuecomment-615448019
I’ve done some code generations with kotlinpoet and it was fairly easy. Especially because building Gradle tasks is so easy. So if I was to pick it up (which I plan to, but I can’t promise), I would probably do that.
good, I will create a task and whoever wants to work on it can (I'll leave it open to the implementer what generative approach is taken)
Created, https://github.com/robstoll/atrium/issues/459 closing this
Since I got another request (https://github.com/robstoll/atrium/issues/419) regarding
I am opening the discussion if we want to support this.
So far people where happy enough with:
But I understand that it is kind of cumbersome if one works a lot with arrays.
The problem with better support for array is that it would require a lot of duplication. There are 9 different Array types and everywhere we support Iterable we would need to add 9 additional duplicates. That's a lot of maintenance for little gain IMO.
I could imagine that we use a generative approach to tackle it so that we only have to write the Iterable version and derive the ones for the 9 different array types. However, still questionable if the effort and the maintenance afterwards is worth it.
Thoughts?