Skip to content
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

provide contains for array #75

Closed
robstoll opened this issue Apr 2, 2020 · 20 comments
Closed

provide contains for array #75

robstoll opened this issue Apr 2, 2020 · 20 comments
Labels
do-it it was decided that this issue (or part of it) shall be done needs discussion

Comments

@robstoll
Copy link
Owner

robstoll commented Apr 2, 2020

Since I got another request (robstoll/atrium#419) regarding

add support for contains for Array

I am opening the discussion if we want to support this.

So far people where happy enough with:

expect(arr).asList().contains....

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?

@jGleitz
Copy link
Collaborator

jGleitz commented Apr 15, 2020

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.

@robstoll
Copy link
Owner Author

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:

  • feature which return array -> one still has to use asList()
  • functions which expect iterable/collection/List one still has to conver the array with 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(...)

@jGleitz
Copy link
Collaborator

jGleitz commented Apr 15, 2020

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!”).

@robstoll
Copy link
Owner Author

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?

@robstoll
Copy link
Owner Author

robstoll commented Apr 15, 2020

@dalewking what Array type do you usually deal with: Array<T> or one of the specializations IntArray etc.?

@dalewking
Copy link

In the case I needed it was an Array, in fact I was trying to assertions on the values property of an enum

@jGleitz
Copy link
Collaborator

jGleitz commented Apr 16, 2020

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?

@dalewking
Copy link

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?

@robstoll
Copy link
Owner Author

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:

  • someone of you two create a 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.
  • someone of you two (or any other contributor) implements these tasks
  • someone of you two will pre-review the corresponding PRs
  • if there are maintenance tasks to do (such as update KDoc) then I'll ping one of you two and you do the necessary changes in a timely manner.
  • we will come back to the generative approach once you two are sick of the search&replace, copy&paste maintenance tasks

How does that sound to you? If you don't like the compromise then I'll create an issue for the generative approach instead.

@jGleitz
Copy link
Collaborator

jGleitz commented Apr 17, 2020

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 return Expect<Array<Int>> and not Expect<List<Int>>.

With my approach, I think it would rightfully return an Expect<List>. Because we would try to eliminate all Expect<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.

@dalewking
Copy link

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

@robstoll
Copy link
Owner Author

@dalewking fair enough, thanks for your input so far, appreciated 👍

@robstoll
Copy link
Owner Author

@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:

  • 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
  • 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.

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.

@jGleitz
Copy link
Collaborator

jGleitz commented Apr 18, 2020

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.

@robstoll
Copy link
Owner Author

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:

  • clean functions for Array which return the same type; which means Array will also be a first type citizen in Atrium - a user never has to call asList()
  • the maintenance burden is only on us, assuming the generator we use is re-usable for others -- I would assume that it could be re-usable also for other projects not only in the context of Atrium, other libraries certainly have the same problem. That being said, maybe there is already a generator (Jetbrains has one to generate their Array functions but I don't know if it is open source)
  • we don't require overloads for expect
  • extension methods for Array (as any other existing method for array types) can be used in feature

@jGleitz
Copy link
Collaborator

jGleitz commented Apr 21, 2020

Hm. It sounded all well in my head. But you are right, duplicating the assertions is the better approach.

@robstoll
Copy link
Owner Author

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:
#75 (comment)

@jGleitz
Copy link
Collaborator

jGleitz commented Apr 21, 2020

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.

@robstoll
Copy link
Owner Author

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)

@robstoll robstoll added the do-it it was decided that this issue (or part of it) shall be done label Apr 21, 2020
@robstoll
Copy link
Owner Author

Created, robstoll/atrium#459 closing this

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
do-it it was decided that this issue (or part of it) shall be done needs discussion
Projects
None yet
Development

No branches or pull requests

3 participants