-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Add spec helper it_iterates
for iteration methods
#10158
Add spec helper it_iterates
for iteration methods
#10158
Conversation
Was this already merged in a previous PR? |
Yes #10130 already added I've merged master and added a commit to remove the internal implementation. |
See also: #4813 :D |
I like it as an internal helper for the std-lib specs, but I don't feel comfortable with it being a feature of the spec module in the std-lib. Specially if it is included by default. |
Why not? It's generally useful for testing any It can certainly be an optional require, of course. |
# | ||
# If the iteration elements are tuples (i.e. multiple values), the yielding | ||
# variant by default only catches the first value because of the block argument | ||
# mechanics. Passing `tuple: true` ensures all yielded arguments are collected |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tuple: true is a bit cryptic. Maybe something like:
yielded_args: :single (default)
yielded_args: :all
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reminded me of this discussion
#10109 (comment)
But actually here I'd more likely support a boolean. It's a bit similar to reuse: true
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe a boolean single_block_arg = true
? Or tuple_block_arg = false
?
@@ -0,0 +1,94 @@ | |||
module Spec::Methods |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Location-wise WDTY of "iterator/spec". I think that each module in the std-lib could use that as spec helpers. "log/spec" is already there.
Iterator is in the prelude so it might be special.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that's probably a good idea in general. However, this helper is not just for Iterator
but also Enumerable
and in fact any iterating method. So I'd rather place it in a more generic location.
Bump. Having these advanced spec helpers available would be really nice. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's merge this! But CI is red.
src/spec/helpers/iterate.cr
Outdated
it "{{ description.id }} yielding" do | ||
assert_iterates_yielding {{ expected }}, {{ method }}, infinite: {{ infinite }}, tuple: {{ tuple }} | ||
end | ||
it "{{ description.id }} iterator" do | ||
assert_iterates_iterator {{ expected }}, {{ method }}, infinite: {{ infinite }} | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As written, the description can't safely contain "
and other special characters
it "{{ description.id }} yielding" do | |
assert_iterates_yielding {{ expected }}, {{ method }}, infinite: {{ infinite }}, tuple: {{ tuple }} | |
end | |
it "{{ description.id }} iterator" do | |
assert_iterates_iterator {{ expected }}, {{ method }}, infinite: {{ infinite }} | |
end | |
it {{ "#{description} yielding" }} do | |
assert_iterates_yielding {{ expected }}, {{ method }}, infinite: {{ infinite }}, tuple: {{ tuple }} | |
end | |
it {{ "#{description} iterator" }} do | |
assert_iterates_iterator {{ expected }}, {{ method }}, infinite: {{ infinite }} | |
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also wonder if marcos could be avoided at all for this feature.
Co-authored-by: Oleh Prypin <oleh@pryp.in>
@asterite (#10158 (comment)): Macros are necessary to call both, yielding and iterator, overloads from the same expression. Simplified example: it_iterates x.each # macro expands to `x.each { }` (yielding) and `x.each` (iterator) This is impossible without macros. If we avoid macros, we need to make both calls separate. I believe the API could receive the iterator as argument and the yielding variant as block argument: it_iterates x.each do # iterator
x.each { |item| yield item } # yielding
end But: This is harder to understand, the block forwarding adds more complexity. We end up with a lot of boilerplate and duplication (which becomes even more a problem when the specced method is more complex than just |
it_iterates
for iteration methods
It is a common idiom that iteration methods come in two flavours: One yielding a block, and one returning an iterator and both targeting different use cases.
Simple examle:
The implementation is different. Every iterator is implemented as a separat type. But the core behaviour is identical: both flavours should iterate exactly the same lements.
This patch introduces a spec helper which makes it easy to test such methods very consicely and equally for both flavours:
The way it works is that it creates two examples which each call one flavour of the method and collects the iterated elements in an array. The result is compared to the expected value.
Because it needs to construct the method call to the yielding method, it needs to be a macro. I don't think there could be a reasonably useful implementation without a macro.
Specs with this helper are easier to read and write then doing it manually (the second commit contains many examples for that) and a single specification test both flavours.
This is very helpful to improve stdlib specs, but it's also generally useful for other libraries, so it is not just be a stdlib spec helper, but part of the spec module.
I already use this helper in #10130, but this is a proper proposal.