-
-
Notifications
You must be signed in to change notification settings - Fork 520
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
dominoes: Add generator and example #484
Conversation
<<-WL.chomp | ||
input_dominoes = #{input} | ||
output_chain = Dominoes.chain(input_dominoes) | ||
#{can_chain ? 'assert_correct_chain(input_dominoes, output_chain)' : 'assert_equal nil, output_chain, "There should be no chain for #{input_dominoes}"'} |
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.
Line is uncomfortably long. Any way to shorten?
Note that it is not so long in the generated output, since only one of two alternatives is taken.
skip | ||
input_dominoes = [[1, 2]] | ||
output_chain = Dominoes.chain(input_dominoes) | ||
assert_equal nil, output_chain, "There should be no chain for #{input_dominoes}" |
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.
try assert_nil
fd0f73f
to
b04e14c
Compare
} | ||
|
||
# Found no suitable chain. | ||
nil |
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.
unfortunately, it's going to have to be nil
and not []
.
Why? Because for the input Dominoes.chain([])
, []
is the valid answer. Therefore, returning []
when you have no chain will mean that the answer []
is ambiguous.
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.
It might be useful to add this in a comment in the example.rb
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. I'll note that that comment was partially targeting #469 (comment) to explain why in this case it cannot be that this method always returns the same object - it has to sometimes return nil instead of list. It's useful to have the comment somewhere, so example
it will be. Putting it in the test file didn't seem right since it wasn't relevant to all students.
Oh yeah, I was going to mention I did this to test out the instructions for writing a generator. Seems to work OK. I'd mention that you need to touch |
Note that this exercise appears to differ from other xruby exercises in the following significant manner: Verification that the student function under test has produced a correct answer is NOT done by comparing the observed output to an expected output (or even a list of expected outputs), because the set of acceptable outputs is quite large even for a small input set. Thus, verification is done by running the observed output against a function that verifies various properties about the output. https://github.com/exercism/x-common/blob/master/exercises/dominoes/canonical-data.json explains the approach taken.
# Test data version: | ||
# <%= sha1 %> | ||
class DominoesTest < Minitest::Test | ||
def assert_correct_chain(input_dominoes, output_chain) |
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.
Could you move this to the bottom of the class please.
In real life I'd put it at the top, but here it would be good if the first thing the student saw was the first test they need to implement.
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 that's probably better. I imagine the student might wonder "what's assert_correct_chain
", but hopefully they can just search the file!
input_normal = input_dominoes.map(&:sort).sort | ||
output_normal = output_chain.map(&:sort).sort | ||
assert_equal input_normal, output_normal, | ||
'Dominoes used in the output must be the same as the ones given in the input' |
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'd extract this test.
output_chain.each_cons(2).with_index { |(d1, d2), i| | ||
_, d1_right = d1 | ||
d2_left, _ = d2 | ||
assert_equal d1_right, d2_left, |
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.
What about using .first
and .last
here instead of _right
and _left
?
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 seems good. I got too paranoid with thinking people would submit lists with length != 2, but I realize now that the same_dominoes check catches that.
"In chain #{output_chain}, right end of domino #{i} (#{d1}) and left end of domino #{i + 1} (#{d2}) must match" | ||
} | ||
|
||
return if output_chain.empty? |
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.
Move this up above line 17. If we're short-circuiting short circuit as early as possible.
d2_left, _ = d2 | ||
assert_equal d1_right, d2_left, | ||
"In chain #{output_chain}, right end of domino #{i} (#{d1}) and left end of domino #{i + 1} (#{d2}) must match" | ||
} |
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'd extract this too.
d1_left, _ = first_domino | ||
_, d2_right = last_domino | ||
assert_equal d1_left, d2_right, | ||
"In chain #{output_chain}, left end of first domino (#{first_domino}) and right end of last domino (#{last_domino}) must match" |
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.
Again extract.
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 am assuming since made it a separate comment that the "consecutive" test and the "dominoes at the ends" should be in two separate methods instead of the same. I'll go with that for now, it's easy to bring them together if I was mistaken in my interpretation.
Great, thanks. (We're working on improving/simplifying the generator creation process.) |
Thanks for doing this @petertseng |
Missclicked close 😿 |
TODO list for me so I understand what feedback I've addressed.
|
the old sha was on a personal branch and it would just confuse people if it were kept in this file. this one is on master
No need to worry about list lengths != 2. assert_same_dominoes has got it covered.
Review note: These commits are meant to be squashed. The only reason they are currently separate is for ease of understanding what chaged. |
assert_correct_chain(input_dominoes, output_chain) | ||
end | ||
|
||
def test_can_t_be_chained |
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.
slightly ugly name. Should it be cant
instead? Can be done without too much pain (.tr("'", '')
the test name instead of replace with underscore)`
assert_correct_chain(input_dominoes, output_chain) | ||
end | ||
|
||
def test_singleton_that_can_t_be_chained |
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.
another ugly can_t
@@ -1,6 +1,6 @@ | |||
class DominoesCase < OpenStruct | |||
def test_name | |||
'test_%s' % description.gsub(/['= -]+/, '_') | |||
'test_%s' % description.tr("'", '').gsub(/[= -]+/, '_') |
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.
or even better: gsub(/can't/,'can not')
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 like it, suggestion taken.
🎉 Thanks @petertseng |
skip | ||
input_dominoes = [[1, 2]] | ||
output_chain = Dominoes.chain(input_dominoes) | ||
assert_nil output_chain, "There should be no chain for #{input_dominoes}" |
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.
What do you think about making a helper method refute_correct_chain
?
I'm not entirely sure it's a good idea, but I like the symmetry of all the tests using similar terminology.
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.
From the point of view of the student looking at this test generator: There is a lot of repetitive "There should be no chain for #{input_dominoes}"
. Moving it into refute_correct_chain
removes this repetition. So that's one point in favour of it. One point against is that it'd be very simple, but that's very minor.
Here is another proposal that might also go along with refute_correct_chain
:
Should output_chain = Dominoes.chain(input_dominoes)
go inside assert_correct_chain
and refute_correct_chain
? Again it would reduce duplication, and then those two methods would only have to take in the input_dominoes
. Though this might make it harder for students to understand what they're supposed to implement (they have to look for the definitions of {assert,refute}_correct_chain
).
So currently: I am thinking yes to refute_correct_chain
and no to "move output_chain = Dominoes.chain(input_dominoes) into
{assert,refute}_correct_chain`"
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 am thinking yes to refute_correct_chain and no to "move output_chain = Dominoes.chain(input_dominoes) into{assert,refute}_correct_chain`"
I agree with this.
It is important not to hide the subject of the test.
require_relative 'dominoes' | ||
|
||
# Test data version: | ||
# 82eb00d |
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.
Another minor minor thing, I've started putting these on 1 line.
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.
Ah, okay. One line it is. I think putting it on a separate line was an artifact of the time when both the sha and the git commit summary was included... nowadays since it's just the sha it probably makes sense on one line.
<<-WL.chomp | ||
input_dominoes = #{input} | ||
output_chain = Dominoes.chain(input_dominoes) | ||
#{can_chain ? 'assert_correct_chain(input_dominoes, output_chain)' : 'assert_nil output_chain, "There should be no chain for #{input_dominoes}"'} |
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.
continuing a conversation in #484 (comment) about whether we should have refute_correct_chain
Yet another argument for:
There's a bit of asymmetry in this line.
There's a slight bit more logic in assert_nil output_chain, "There should be no chain for #{input_dominoes}"
versus assert_correct_chain(input_dominoes, output_chain)
where all the logic is offloaded to the example.tt
file. I think it makes more sense to be consistent, so I have added refute_correct_chain
. If we change our minds about wanting it we just remove the commit.
Once this is ready to go I'll try solving it* and see how the tests work in practice. * Maybe not today. |
The candidates are the domino or the domino flipped. We can reverse to flip the domino! It's just a list!
skip | ||
assert_equal 1, BookKeeping::VERSION | ||
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.
Would it be worth inserting the comment about why we can't just use input/output tests in between the bookkeeping and the custom asserts?
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.
Seems reasonable. Added.
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 think this looks good to go.
I've not done the exercise myself yet, but will do when I get a chance in the next few days.
Thanks for all your quick responses @petertseng ❤️
👍 Note! Starting in three days from now my availability will be limited. Of course, if everything is fine, I am sure the squash button will do just fine, I think it'll only be necessary to keep the first commit message. |
With my apologies to #424, but @seanreed1111 claimed it was OK to let someone else take it.
This PR complies with the new names in #463.
Note that this exercise appears to differ from other xruby exercises in
the following significant manner:
Verification that the student function under test has produced a correct
answer is NOT done by comparing the observed output to an expected
output (or even a list of expected outputs), because the set of
acceptable outputs is quite large even for a small input set.
Thus, verification is done by running the observed output against a
function that verifies various properties about the output.
https://github.com/exercism/x-common/blob/master/exercises/dominoes/canonical-data.json
explains the approach taken.