Skip to content

Commit 6bc083b

Browse files
authored
Merge pull request #552 from exercism/generator-readme
Update documentation about generated test cases
2 parents 40e35ed + efc7fef commit 6bc083b

File tree

1 file changed

+138
-23
lines changed

1 file changed

+138
-23
lines changed

README.md

Lines changed: 138 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -65,50 +65,163 @@ Note that flags which have an attached value, like above, must take the form
6565
### Generated Test Suites
6666

6767
If you find an `example.tt` file in a problem directory, then the test suite is
68-
generated from shared data. In this case changing the test file itself will
69-
not be enough.
68+
generated from shared data, which can be found in the exercise definition in the [x-common][]
69+
repository.
7070

71-
You will need to have cloned [the shared metadata](https://github.com/exercism/x-common)
72-
at the same level as the xruby repository. E.g.
71+
Typically you will want to do one of the following:
72+
73+
* [Regenerate the test suite](#regenerating-an-exercise) based on updated canonical data
74+
* [Make changes to a generated exercise](#changing-a-generated-exercise)
75+
* [Implement a new generator](#implementing-a-generator)
76+
77+
Generated exercises depend on the [the shared metadata][x-common], which must be
78+
cloned to the same directory that contains your clone of the xruby repository:
7379

7480
```
7581
tree -L 1 ~/code/exercism
7682
├── x-common
7783
└── xruby
7884
```
7985

80-
1. `xruby/$PROBLEM/example.tt` - the Erb template for the test file, `$PROBLEM_test.rb`.
81-
1. `x-common/$PROBLEM.json` - the shared inputs and outputs for the problem.
82-
1. `lib/$PROBLEM.rb` - the logic for turning the data into tests.
83-
1. `xruby/bin/generate $PROBLEM` - the command to actually generate the test suite.
84-
1. `.version` - used to keep track of the version of the test files as the data changes.
86+
#### Regenerating an Exercise
87+
88+
From within the xruby directory, run the following command, where $PROBLEM is the slug
89+
of the exercise, e.g. `clock` or `atbash-cipher`:
90+
91+
```
92+
bin/generate $PROBLEM
93+
```
94+
95+
#### Changing a Generated Exercise
96+
97+
The `$PROBLEM/$PROBLEM_test.rb` will never be edited directly.
98+
99+
There are two reasons why a test suite might change:
100+
101+
1. the tests are wrong (an incorrect expectation, a missing edge case, etc)
102+
1. there might be issues with the style or boilerplate
103+
104+
In the first case, the changes need to be made to the `canonical-data.json` file for
105+
the exercise, which lives in the x-common repository.
106+
107+
```
108+
../x-common/exercises/$PROBLEM/
109+
├── canonical-data.json
110+
├── description.md
111+
└── metadata.yml
112+
```
113+
114+
This change will need to be submitted as a pull request to the x-common repository. This pull
115+
request needs to be merged before you can regenerate the exercise.
85116

86-
Additionally, there is some common generator logic in `lib/generator.rb`.
117+
Changes that don't have to do directly with the test inputs and outputs, will either need to be
118+
made to `exercises/$PROBLEM/example.tt` or `lib/$PROBLEM_cases.rb`. Then you can regenerate the
119+
exercise with `bin/generate $PROBLEM`.
87120

88-
For example, take a look at the `hamming.json` file in the x-common repository, as well
89-
as the following files in the xruby repository:
121+
#### Implementing a Generator
90122

91-
1. `hamming/example.tt`
92-
1. `bin/generate hamming`
93-
1. `lib/hamming.rb`
94-
1. `lib/generator.rb`
123+
You will need to implement three files to create a generator:
95124

96-
The `hamming/hamming_test.rb` will never be edited directly. If there's a missing test case,
97-
then additional inputs/outputs should be submitted to the x-common repository.
125+
1. `exercises/$PROBLEM/example.tt` - the Erb template for the test file, `$PROBLEM_test.rb`.
126+
1. `exercises/$PROBLEM/.meta/.version` - used to keep track of the version of the test files as the data changes.
127+
1. `lib/$PROBLEM_cases.rb` - the logic for turning the data into tests.
98128

99-
Changes to the test suite (style, boilerplate, etc) will probably have to be made to
100-
`example.tt`.
129+
You will not need to touch the top-level script, `bin/generate`.
101130

102-
### Exercise Generators
131+
The `bin/generate` command relies on some common logic implemented in `lib/generator.rb`.
132+
You probably won't need to touch that, either.
103133

104-
If you wish to create a new generator, or edit an existing one, the generators currently live in the lib directory and are named `$PROBLEM_cases.rb`. For example, the hamming generator is `lib/hamming_cases.rb`.
134+
The `lib/$PROBLEM_cases.rb` file should contain a small class that wraps the JSON for a single test case:
105135

106-
All generators currently adhere to a common public interface, and must define the following three methods:
136+
```
137+
require 'exercise_cases'
138+
139+
class ProblemNameCase < OpenStruct
140+
def test_name
141+
'test_%s' % description.gsub(/[ -]/, '_')
142+
end
143+
144+
def workload
145+
# implement main logic of test here
146+
end
147+
148+
def skipped
149+
index.zero? ? '# skip' : 'skip'
150+
end
151+
end
152+
```
153+
154+
Instead of `ProblemName` use the name of the actual problem. This is important, since
155+
the generator script will infer the name of the class from the argument that is passed.
156+
157+
This class must implement the following methods:
107158

108159
- `test_name` - Returns the name of the test (i.e `test_one_equals_one`)
109160
- `workload` - Returns the main syntax for the test. This will vary depending on the test generator and its underlying implementation
110161
- `skipped` - Returns skip syntax (i.e. `skip` or `# skip`)
111162

163+
Beyond that, you can implement any helper methods that you need.
164+
165+
Below this class, implement a small loop that will generate all the test cases by reading the
166+
`canonical-data.json` file, and looping through the test cases.
167+
168+
You will need to adjust the logic to match the structure of the canonical data.
169+
170+
For example, if there is a single top-level key named "cases", then you can loop through
171+
them as follows:
172+
173+
```
174+
ProblemNameCases = proc do |data|
175+
JSON.parse(data)['cases'].map.with_index do |row, i|
176+
ProblemNameCase.new(row.merge('index' => i))
177+
end
178+
end
179+
```
180+
181+
If there are multiple sections, then you will need to loop through the sections, and then
182+
loop through each of the cases in an inner loop:
183+
184+
```
185+
ProblemNameCases = proc do |data|
186+
i = 0
187+
json = JSON.parse(data)
188+
cases = []
189+
%w(section1 section2 etc).each do |section|
190+
json[section]['cases'].each do |row|
191+
row = row.merge(row.merge('index' => i, 'section' => section))
192+
cases << ProblemNameCase.new(row)
193+
i += 1
194+
end
195+
end
196+
cases
197+
end
198+
```
199+
200+
Finally, you need to create a text template, `example.tt`, as the bases for the test suite.
201+
202+
Start with the following boilerplate, and adjust as necessary:
203+
204+
```
205+
#!/usr/bin/env ruby
206+
gem 'minitest', '>= 5.0.0'
207+
require 'minitest/autorun'
208+
require_relative '$PROBLEM'
209+
210+
# Common test data version: <%= abbreviated_commit_hash %>
211+
class ProblemNameTest < Minitest::Test<% test_cases.each do |test_case| %>
212+
def <%= test_case.name %>
213+
<%= test_case.skipped %>
214+
assert_equal <%= test_case.expected %>, <%= test_case.work_load %>
215+
end
216+
<% end %>
217+
<%= IO.read(XRUBY_LIB + '/bookkeeping.md') %>
218+
def test_bookkeeping
219+
skip
220+
assert_equal <%= version %>, BookKeeping::VERSION
221+
end
222+
end
223+
```
224+
112225
## Pull Requests
113226

114227
We welcome pull requests that provide fixes to existing test suites (missing
@@ -168,3 +281,5 @@ Copyright (c) 2014 Katrina Owen, [email protected]
168281

169282
## Ruby icon
170283
The Ruby icon is the Vienna.rb logo, and is used with permission. Thanks Floor Dress :)
284+
285+
[x-common]: https://github.com/exercism/x-common

0 commit comments

Comments
 (0)