-
-
Notifications
You must be signed in to change notification settings - Fork 277
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
refactor: encapsulate minitest assertion matchers #1790
Conversation
ASSERTION_MATCHERS.each do |m| | ||
name = m.name.split('::').last | ||
|
||
def_node_matcher "minitest_#{name}".to_sym, m::NODE_MATCHER_PATTERN |
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.
If you add extend NodePattern::Macros
to BasicAssertion
, it should be possible to add the node matchers in the BasicAssertion
subclasses themselves. I’m not 100% sure if it works, but we’d get rid of this loop defining the node patterns, and #on_send
would also become a lot simpler, e.g.
def on_send(node)
ASSERTION_MATCHERS.each do |matcher|
assertion = matcher.new(node).match
return if assertion.nil?
on_assertion(node, assertion)
end
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.
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.
hmm yeah this is turning ugly a bit too ugly for my taste: since BasicAssertion
is handling a lot of the instance variables, we effectively want an artificial second constructor, and the node matcher method takes a block which makes it messier to do a return (unless I've missed something).
Would you mind sticking with this current layout and I can explore this further as a follow up?
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.
Sure, we can stick with the current layout.
I tried finding a suitable implementation, and it’s tricky. I ended up with something like this in the subclasses:
def initialize(node)
pattern(node) do |expected, actual, failure_message|
super(expected, actual, failure_message.first)
end
end
The “only sometimes call super
” smells a bit, and I had to add this method to the BasicAssertion
:
def match?
!!@expected || !!@actual
end
to be called instead of next if assertion.nil?
:
def on_send(node)
ASSERTION_MATCHERS.each do |m|
assertion = m.new(node)
on_assertion(node, assertion) if assertion.match?
end
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.
Yeah it's annoying how ugly it gets and I'm pretty sure it's all because of node_matcher
not being on the class... 😅
I've got an idea or two on how to approach it though which I'll explore after this is landed.
1379d3e
to
2797e72
Compare
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.
Thanks!
I hope it will turn out to be possible to eliminate the accumulated meta-programming.
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.
Looks great!
This aims to move all of the specific code for each minitest assertion matcher into the assertion class, so that the outer cop becomes responsible for calling a specific assertion matcher class and reporting, and the inner classes become responsible for handling the actual matching.
Opening as a draft for now to get confirmation that people are ok with the direction of this - I'll squash and do any remaining cleanup afterwards.
Before submitting the PR make sure the following are checked:
master
(if not - rebase it).Added tests.Updated documentation.Added an entry to theCHANGELOG.md
if the new code introduces user-observable changes.bundle exec rake
) passes (be sure to run this locally, since it may produce updated documentation that you will need to commit).If you have created a new cop:
Added the new cop toconfig/default.yml
.The cop is configured asEnabled: pending
inconfig/default.yml
.The cop is configured asEnabled: true
in.rubocop.yml
.The cop documents examples of good and bad code.The tests assert both that bad code is reported and that good code is not reported.SetVersionAdded: "<<next>>"
indefault/config.yml
.If you have modified an existing cop's configuration options:
SetVersionChanged: "<<next>>"
inconfig/default.yml
.