-
-
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
[RFC] Spec: add a way to check that a method's value is truthy or falsey #8527
Comments
FWIW you can already do EDIT: We also have |
Now that I think about it, this could also work as a top-level method: assert ary.empty?
assert File.exists?(...) which is easier to spot and easier to read and write. |
|
Sorry I mean that you can already do that. https://play.crystal-lang.org/#/r/832l IMO, I find Having a whole different syntax/structure just for this seems a bit overkill. |
Are you really gonna tease |
Hehe, you are right. So bad macro code is slow and we can't use it for this, but it seems I just proposed doing that :-) I guess there's no need to improve this right now. |
I might've spiked this discussion, but I'm a bit skeptical. Seems like I'm not the only one.
But then there are a few hard coded matchers, like the comparison operators or |
Well, with a generic assert ary.empty?
# Failure: Expected [1, 2, 3] to be empty The problem is that doing such macro expansion for each and every assert in a big test suite takes a considerable amount of time. Maybe I should create such macro and use it a lot and benchmark the compiler to see where that time is going... if there's a way to optimize it, it can be a solution. |
So this is interesting... I did the above test. I tried it with this (dummy) program: require "spec"
macro assert(call, file = __FILE__, line = __LINE__)
{% obj = call.receiver %}
{% name = call.name %}
%obj = {{obj}}
%call = %obj.{{name}}
unless %call
fail "Expected #{%obj} to be {{name[0...-1]}}"
end
end
describe "Foo" do
{% for i in 0..12_000 %}
it "bar {{i}}" do
a = [1, 2, 3]
assert a.empty?
end
{% end %}
end Profiling it I can see most of the time, 12s, is gone in I hope to find many more optimizations, maybe we can optimize macros a lot more! |
I'll reopen because we might end up having an |
Welp, I was able to optimize this case particular case (for which there was something really bad going on) down to 0.4s :-) I'll send a PR soon. Next up: someone (or me, if I have time, but I doubt it) could try writing that fancy |
References to my prior implementations of power assert:
|
Nice! I just tried it with this code: require "spec"
describe "Foo" do
{% for i in 0..15000 %}
it "bar" do
a = 1
b = 2
assert a == b
end
it "baz" do
a = 1
assert a.is_a?(String)
end
{% end %}
end It takes about 20 seconds to compile. If I use I think I like it. That said, I can't see how this can be extended. For example if I do:
it says "expected false to be truthy". Maybe for this specific case we can change it to be "expected [1] to be empty", if it's a bang call with no arguments. Maybe that's enough. Then there's Thoughts? |
The assert a == b
assert b == a Both expressions should be equivalent. But which value is the actual and which the expected? It's similar with testing a property vs. testing with a predicate. a.should_be &.foo? # test `a` using predicate #foo?
a.foo?.should be_true # test property `a.foo?` While both tests look similar, the With assert a.foo? # test `a` using predicate #foo?
assert a.foo? == true # test property `a.foo?` Both variants seem equivalently interchangeable, and the second one unnecessarily verbose. The implicit semantics would not be directly visible. So again, different semantics would only be based on convention. Relying on convention is not a critical stopper, but a thing to consider. Explicitness is always better. |
Good point. It can be the same order as arguments order used in |
Is The problem comes when you have an expression like So how about:
You could pretty print the call args too, if any. That is, take the top-level call only, take the name of the receiver and all arguments, and pretty-print them. It'll end up being a pretty simple, fast, macro, I think. Since most of the work is just done by the pretty printer. |
I really can't imagine a spec scenario where what's being tested isn't obvious from the spec name and the body combined. You can write bad code in specs as well as application code, but I don't think that's a good point against assert. |
I think this is pretty much 95% of the way to how I'd like the assert macro to work: https://carc.in/#/r/8515 |
Solution proposed by @asterite in the description seems to me like a valuable addition to the %w(soo nice).refute &.empty?
# vs
%w(not so nice).empty?.should be_false |
I'm already not a big fan of polluting |
Right now we can do this:
That's fine but we could improve this situation.
In RSpec one can do:
This works by letting
be_empty
usemethod_missing
and creating a matcher on the fly. This can't work in Crystal, not because ofmethod_missing
but because the generated matcher can't be aware of what's the target object being sent to it.Some ideas that were proposed in another PR, starting from #7500 (comment) are to be able to do something like this:
ary.should &.empty?
I pointed out that the readability of that can be improved a bit:
ary.should_be &.empty?
But when I went ahead to implement that I noticed in specs we have, for example:
File.exists?(...).should be_true
Replacing that with
should_be
looks awkward:So I had that idea that we could have
assert
andrefute
, similar to other languages (but it's not the same as those other languages).Essentially, the above two examples would be:
Then we could also do:
One downside of using those names is that they don't have "should" in them. When looking at some specs right now the assertions are easy to spot because they almost always have "should" in their names (the exception being
expect_raises
).But maybe it's fine?
assert
andrefute
are very convenient methods to have, to avoid theshould be_true
boilerplate.The implementation is pretty straight-forward:
Thoughts? Any other names we could use?
The text was updated successfully, but these errors were encountered: