Skip to content

generators: Auto extract cases #568

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

Merged
merged 26 commits into from
Apr 27, 2017
Merged

Conversation

hilary
Copy link
Contributor

@hilary hilary commented Apr 24, 2017

Motivation and Context

While working on #566, I noticed that a number of the existing generators would not run because the loop which extracts cases from the data did not match the current structure. I also noticed a lot of consistency in the loops for both failing and successful generators. Automatically extracting cases from the JSON feels like a big win.

Description

Added Generator::CaseValues (in lib/generator/case_values.rb, which extracts the cases from the canonical data. It uses a custom extractor if such an extractor exists, otherwise it extracts the cases automagically.

How Has This Been Tested?

  • I wrote tests for Generator::CaseValues for both custom extractor and not custom extractor and for both simple and complex canonical data.
  • I wrote a test for a public helper method I introduced to Generator::Files::GeneratorCases.
  • The existing developer tests pass.

Types of changes

No breaking changes.
New feature: auto extraction of cases.

References and Closures

Checklist:

  • My change requires a change to the documentation.
  • My change relies on a pending issue/pull request
  • I have updated the documentation accordingly.
  • I have read the CONTRIBUTING document.
  • I have added tests to cover my changes.
  • All new and existing tests passed.

@@ -31,8 +31,40 @@ def cases_require_name
Files::GeneratorCases.filename(exercise_name)
end

def test_cases
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a LOT of untested logic here. That scares me.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Me too!

end

def test_cases_proc?
Object.const_defined?(test_cases_procname)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use the method you already have: test_cases_proc.exists?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test_cases_proc returns NameError if the constant is not defined. I dislike rescuing an error in this situation, but would prefer to check to see if it is there.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was desirable before, for the whole thing to blow up if the test case class did not exist. Since that's no longer the case it suggests to me that test_cases_proc needs changing to something like:

def test_cases_proc
  Object.const_get(test_cases_procname) if Object.const_defined?(test_cases_procname)
end

(This may be moot due to upcoming object extraction.)

@@ -25,6 +25,10 @@ def test_filename
assert_equal 'two_parter_cases', GeneratorCases.filename(exercise_name)
end

def test_class_name
assert_equal 'TwoParterCase', GeneratorCases.class_name('two-parter')
end
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a nice test

def proc_name(exercise_name)
filename(exercise_name).split('_').map(&:capitalize).join
"#{class_name(exercise_name)}s"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you're just adding an 's' do class_name(exercise_name) + 's'

@Insti
Copy link
Contributor

Insti commented Apr 24, 2017

I'd like to see an example of how class_name and proc_name are used as part of this PR.

test_cases_proc.call(canonical_data.to_s)
else
test_cases_with_index
end
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's another object hiding in here.
Replace all this with:

test_cases_foobar.call(canonical_data.to_s)

Then work out what test_cases_foobar is.

Copy link
Contributor

@Insti Insti Apr 24, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will also help solve your (my) "bunch of untested private methods" problem.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💯 Win, win, win. I'll refactor and dig that object out tomorrow.

@hilary
Copy link
Contributor Author

hilary commented Apr 24, 2017

proc_name is an existing method. I'm preserving it for backwards compatibility. Once we've converted we can ditch it.

end

def test_case_class
Object.const_get(Files::GeneratorCases.class_name(exercise_name))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Insti here is where class_name is used.

Copy link
Contributor

@Insti Insti Apr 24, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So proc_name is never used externally and should be private?
I read the comment above.

@hilary
Copy link
Contributor Author

hilary commented Apr 24, 2017

Refactoring to split apart the adapter and the two solutions

simple_canonical_data
end

def complex_canonical_data
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These (simple|complex) can live in /fixtures somewhere.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We discussed this in gitter and have decided to try a data section at the end of the test file.
The main thing is removing the big heredoc from the middle of the test file.


class CaseValuesTest < Minitest::Test
def setup
$LOAD_PATH.unshift 'test/fixtures/xruby/lib'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Modifying the load path for every test should not be necessary.
But there might be something else to do so that everything still works.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was following the existing pattern.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I probably should fix that then 😊

end

def simple_canonical_data
simple_canonical_data = Minitest::Mock.new
Copy link
Contributor

@Insti Insti Apr 25, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this a Mock?
Nevermind.

complex_canonical_data = Minitest::Mock.new
complex_canonical_data.expect(
:to_s,
<<-TEXT
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Were you going to move this data out of the tests?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They're at the bottom of the file now.

Where I'd really like to put these is in a module I could include.

Copy link
Contributor

@Insti Insti Apr 26, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In which case why not use a fixture?
How is a module any different, or even preferable when the data should be non-executable?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A module can easily be included in multiple places, but (may be/is ?) easier to be surgical than a fixture.

I guess the question is, what are you suggesting? That I create fixtures for each of these cases, in which case I will end up duplicating data or reusing fixtures across tests, neither of which is desirable.

While data is non-executable, being able to manipulate the data via code makes the test suite far more manageable.

@@ -0,0 +1,116 @@
require_relative '../test_helper'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

case_values_auto_extractor_test.rb has too many _auto_extractors in the filename

@@ -56,4 +56,5 @@ def teardown
$LOAD_PATH.delete 'test/fixtures/xruby/lib'
end
end

Copy link
Contributor

@Insti Insti Apr 26, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An unnecessary blank line change has snuck in.

$LOAD_PATH.unshift 'test/fixtures/xruby/lib'
end

def test_simple_auto_extraction
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this test is redundant.

Insti added 3 commits April 26, 2017 14:17
Renamed 'gamma' to 'complex'.
Add test that template_values loads problem cases

Remove commented out code.

Make everything that loads AlphaCase(s) clean up after itself.

Undefine AlphaCase(s) during teardown.

Remove redundant setup.

Check for both classes.

Add tests for class based test generation

squash Remove TODO comment
@hilary hilary force-pushed the auto_extract_cases branch from 9a14c4d to 45511ff Compare April 26, 2017 21:18
@Insti Insti merged commit f56c429 into exercism:master Apr 27, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants