-
-
Notifications
You must be signed in to change notification settings - Fork 525
DRY test generators #566
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
DRY test generators #566
Conversation
converted: - hamming - ocr-numbers - luhn - pig-latin to use it
lib/exercise_cases.rb
Outdated
|
||
class ExerciseCase < OpenStruct | ||
def name | ||
'test_%s' % description.gsub(/[^\w ?!]/, '').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.
Does this change cause the test names to change by not presenting hyphenated words as individual words, but rather as shown in this diff:
- def test_large_distance_in_off_by_one_strand
+ def test_large_distance_in_offbyone_strand
I would like to place the change back in, due to things like "non_unique" being acceptable and readable, even if it technically isn't a separate word, and to avoid "off-by-one" becoming "offbyone".
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.
Will do (in the morning!)
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 it is a separate commit, so it may be a simple process. That was a great idea to separate it out like that!
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'm not sure if this is premature:
We will need a underscore_format(string)
* method that we can have a bunch of unit tests for so we can keep track of all the weird cases we encounter.
So this line becomes:
'test_' + underscore_format(description)
* name and whether it should be a String
refinement TBD.
The design principles are very much in line with what we want to do, I think.
These places would definitely benefit from being named. |
Firstly: Awesome PR description ❤️ thanks.
This is not a requirement, feel free to break as much stuff as you like.
Excellent.
Excellent.
Excellent. |
lib/exercise_cases.rb
Outdated
@@ -1,2 +1,30 @@ | |||
require 'ostruct' |
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.
This file should be in `lib/generator'
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.
Indeed! I'll go ahead and move it (also in the morning).
lib/exercise_cases.rb
Outdated
@@ -1,2 +1,30 @@ | |||
require 'ostruct' | |||
require 'json' | |||
|
|||
class ExerciseCase < OpenStruct |
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.
Not an issue for this PR, but ExerciseCase
should not be an OpenStruct
and the canonical data should be kept separate from the methods of this class.
See: my imagined version of ExerciseCase
for an example.
lib/luhn_cases.rb
Outdated
|
||
def assertion | ||
expected ? 'assert' : 'refute' | ||
%Q(#{assert_or_refute} Luhn.valid?(#{input.inspect})) |
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 that all these need to do now is implement workload
.
All these TODOs probably deserve their own issue. TODO
Yes.
Yes. Now some of the things from the grand design in my head: Note: "problem" in all these examples is the variable exercise name.
Some of this will require changes to the bin/generator code. By creating the new Everything needed to create a generator for an exercise will be in |
For reference, here is my proof of concept for this from a few months ago: Insti#1 |
lib/exercise_cases.rb
Outdated
expected.to_i == -1 | ||
end | ||
|
||
def assert_or_refute |
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.
Can this be just assert
it doesn't REALLY matter if the final test file contains refute
and
"#{assert} Problem.call(#{input.inspect})"
looks better in the readme than:
"#{assert_or_refute} Problem.call(#{input.inspect})"
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.
Future:
It may be possible to add logic into the assert
method that works out the correct assert to use including assert_equal
or assert_error
I think that's all for now. |
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.
This looks awesome!! Thank you so much for your work on this!
skip | ||
assert_equal "1", OcrNumbers.convert(" \n |\n |\n ") | ||
end | ||
|
||
def test_unreadable_but_correctly_sized_inputs_return_? | ||
def test_Unreadable_but_correctly_sized_inputs_return_? |
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 ExerciseCase#name
should probably also downcase the description.
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 to do about punctuation characters is another issue.
But this one is probably a job for a pre-processor, since it's problem dependent.
s/"?"/question mark string/
as the json description is being read in and before ExerciseCase#name
sees it.
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.
Heh. I had downcase
in there at one point, then took it out. Shoulda trusted my first instincts on that one 😄
@@ -1,5 +1,3 @@ | |||
require 'exercise_cases' |
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.
Not entirely sure I agree with removing this line.
Sure require_all
is awesome, but there's only one dependency and it's nice to be able to see where ExerciseCase
comes from.
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.
And it simply doesn't break anything... if that dependency goes away, no problem, it is a documentation value at this point.
assert_equal 'A string with TLA'.underscore, 'a_string_with_tla' | ||
end | ||
|
||
def hyphenated_text |
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.
def test_hyphenated_text
lib/luhn_cases.rb
Outdated
class LuhnCase < ExerciseCase | ||
def workload | ||
%Q(#{assert_or_refute} Luhn.valid?(#{input.inspect})) | ||
"#{assert} Luhn.valid?(#{input.inspect})" |
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's a good idea to keep your commits atomic.
This has nothing to do with underscores and should be in its own commit.
It is a discipline that needs to be trained. Commit early, commit often.
@@ -1,5 +1,3 @@ | |||
require 'exercise_cases' |
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.
Should have been another separate commit.
assert_raises(ArgumentError) { <%= test_case.workload %> }<% else %> | ||
assert_equal <%= test_case.expected %>, <%= test_case.workload %><% end %> | ||
<%= test_case.skipped %> | ||
<%= test_case.workload %> |
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.
Very nice.
else | ||
"assert_equal #{expected.inspect}, #{test_case}" | ||
assert_equal { test_case } | ||
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.
Definitely a better place for this.
Putting this here so we don't forget: We currently create |
Motivation and Context
The test generators have a lot of duplicate code. Do we need to discuss why that's not good? I decided to take a pass at DRYing up the test generators before moving on to writing a test generator scaffold generator...
Design principles:
This effort:
Description
I introduced a base class,
ExerciseCase
intolib/exercise_cases.rb
from whichProblemNameCase
can inherit.ExerciseCase
provides public methodsname
andskipped
as well as a variety of protected helper methods.I converted three generators I wrote, and one I didn't write, to use
ExerciseCase
. The generated test files remain (almost) the same while the generators exhibit far more consistency. (The only changes to the test files are some small changes in how the descriptions are processed to produce the test names.I updated the README to reflect the changed workflow.
How Has This Been Tested?
I can regenerate the tests with no changes except for the small changes noted above.
The tests pass under
rake test
.Next Steps
ProblemNameCases
logic. We can inspect and extract the cases from the JSON programmatically.