Skip to content

grains: add test generator #514

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 1 commit into from
Jan 2, 2017

Conversation

tommyschaefer
Copy link
Contributor

Hi there!

Checking off exercises from #396.

I tried to include tests for the generator this time around. Is this okay? If so, should I add a cases (or another name) test directory to group the tests? Or is it okay as it is?

I also had a couple other questions, but I'll add them as comments on specific lines.

Thank you so much for your time!

module Grains
def self.square(number)
fail ArgumentError if number <= 0 || number > 64
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 had to make an adjustment to the implementation because the shared test data includes some error cases that weren't in the original ruby implementation.

How does this work with book keeping when it wasn't there originally? Normally, I'm assuming we would bump the BookKeeping::VERSION, but this exercise wasn't using book keeping yet.

Copy link
Contributor

Choose a reason for hiding this comment

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

The adjustment looks reasonable.

BookKeeping is related to the tests, so you might need to add it in so that the example.rb still passes the tests.

2**(number - 1)
end

def self.total
square(65) - 1
2**64 - 1
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Because of the change to the square method, calling square(65) would raise an ArgumentError, so I just made the smallest change possible to make the tests pass. Is this okay?

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 fine.

skip
assert_equal 32_768, Grains.square(16)
assert_equal 32768, Grains.square(16)
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 elected to use the32769 notation rather than 32_768 for simplicities sake. Is this okay, or should I update it to use the other notation?

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 fine.

Although supporting the underscore version should just require a simple helper method.

skip
assert_equal 9_223_372_036_854_775_808, Grains.square(64)
assert_equal 9223372036854775808, Grains.square(64)
Copy link
Contributor

Choose a reason for hiding this comment

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

When we get to numbers like this the underscore representation is REALLY helpful.

input: row['input'],
)

GrainsCase.new(row.merge('assertion' => assertion, 'index' => i))
Copy link
Contributor

Choose a reason for hiding this comment

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

Rather than constructing a GrainsCase here, how about subclassing it and just doing

GrainsCase::SquareMethod.new(row: row, index: i)

GrainsCase.new(row.merge('assertion' => assertion, 'index' => i))
end

assertion = GrainsCase::TotalAssertion.new(
Copy link
Contributor

Choose a reason for hiding this comment

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

Why does this look different to the square method code?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is because of the format of the canonical data:

{
  "square": {
    "cases": [
      { "description": "...", "input": 1, "expected": 1 },
      ...
    ]
  },
  "total": {
    "description": "...",
    "expected": "..."
  }
}

@Insti
Copy link
Contributor

Insti commented Dec 28, 2016

Sorry my review pace has slowed down, the holidays seem to leave me with less time rather than more!. I've still got my eye on these PRs, I'm just trying to find some time to give them a final test/review before merging them.

@tommyschaefer
Copy link
Contributor Author

@Insti No problem at all! I totally understand 😄

GrainsCase::SquareMethod.new(row.merge('index' => i))
end

cases << GrainsCase::TotalMethod.new(data['total'])
Copy link
Contributor

Choose a reason for hiding this comment

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

This case doesn't know its index.

Copy link
Contributor

Choose a reason for hiding this comment

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

btw: This looks much nicer than it did last time I looked at it.

private

def error_assertion
"assert_raises(ArgumentError) { Grains.square(#{input}) }"
Copy link
Contributor

Choose a reason for hiding this comment

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

dodgy indentation.

private

def formatted_expected
expected.to_s.reverse.gsub(/...(?=.)/,'\&_').reverse
Copy link
Contributor

Choose a reason for hiding this comment

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

This would be nicer as a function that took an argument.

def underscore_format(number)

Then you use it as: underscore_format(expected)

@@ -0,0 +1,87 @@
require_relative 'test_helper'

class GrainsTest < Minitest::Test
Copy link
Contributor

Choose a reason for hiding this comment

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

I really like that you're unit testing the Case classes. 👍

Hopefully doing this will help you realise which methods should be part of some kind of BaseCase class that could be inherited from rather than always starting from OpenStruct

assert_equal '# skip', GrainsCase.new(index: 0).skipped
end

def test_skipped_with_nil_index
Copy link
Contributor

@Insti Insti Dec 29, 2016

Choose a reason for hiding this comment

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

Should a case ever have a nil index?
What is the point of an index?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is a little bit of a tricky one that I'm not sure on the best way to handle.

In my (limited) experience, when the canonical data has more than one "type" of test case, they both each follow the same JSON structure (e.g. allergies). This lets you do something like this when assembling the cases (not an optimal solution necessarily, just demonstrating):

i = -1

JSON.parse(data).flat_map do |type, test_data|
  test_data['cases'].map do |test_case|
    i += 1
    # Determine which kind of Case to make based on type
    Case.new(...) 
  end
end

But in this case the JSON is structured like:

{
  "square": {
    "cases": [
      { "description": "...", "input": 1, "expected": 1 },
      ...
    ]
  },
  "total": {
    "description": "...",
    "expected": "..."
  }
}

Because of this, I'm not too sure how to get a real index for this case.

Do you have any thoughts on this? Would it be better to explicitly pass index: false for that case? It's still pretty strange though 😞

Copy link
Contributor

@Insti Insti Dec 29, 2016

Choose a reason for hiding this comment

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

What does the index represent? What is it the index of?
Does it mean different things in different places?

(I know the answer, I'm just trying to help you get there.)
(index: false is not the correct answer)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's the index of the set of test cases. Would something like this work:

data = JSON.parse(data)
i = -1

cases = data['square']['cases'].map do |row|
  i += 1
  GrainsCase::SquareMethod.new(row.merge('index' => i))
end

cases << GrainsCase::TotalMethod.new(data['total'], index: i+1)

It adds a bit of complexity, but at least the index is correct.

Thank you so much for your help and feedback!

Copy link
Contributor

Choose a reason for hiding this comment

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

Getting closer 😄

In the above example, how does i relate to cases.size?

@tommyschaefer tommyschaefer force-pushed the generate-grains-tests branch 2 times, most recently from 883d1f9 to 577fa7e Compare December 30, 2016 17:34
@Insti
Copy link
Contributor

Insti commented Dec 30, 2016

Is this ready to merge?

@tommyschaefer
Copy link
Contributor Author

tommyschaefer commented Dec 30, 2016

As far as I'm concerned, this should be good to go 😄

If there are any other changes you all would like to see, I'm happy to keep working at it!

@Insti Insti merged commit 745d35a into exercism:master Jan 2, 2017
@Insti
Copy link
Contributor

Insti commented Jan 2, 2017

Thanks for all your work on this @tommyschaefer ❤️

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