methadone 1.0.0.rc2 → 1.0.0.rc3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. data/bin/methadone +2 -2
  2. data/lib/methadone/cucumber.rb +6 -3
  3. data/lib/methadone/error.rb +3 -2
  4. data/lib/methadone/exit_now.rb +25 -0
  5. data/lib/methadone/main.rb +21 -10
  6. data/lib/methadone/sh.rb +19 -4
  7. data/lib/methadone/version.rb +1 -1
  8. data/lib/methadone.rb +1 -0
  9. data/templates/full/Rakefile.erb +1 -1
  10. data/templates/full/features/step_definitions/executable_steps.rb.erb +1 -1
  11. data/test/test_exit_now.rb +37 -0
  12. data/test/test_main.rb +11 -5
  13. data/test/test_sh.rb +17 -0
  14. data/tutorial/.vimrc +6 -0
  15. data/tutorial/1_intro.md +52 -0
  16. data/tutorial/2_bootstrap.md +176 -0
  17. data/tutorial/3_ui.md +349 -0
  18. data/tutorial/4_happy_path.md +437 -0
  19. data/tutorial/5_more_features.md +702 -0
  20. data/tutorial/6_refactor.md +220 -0
  21. data/tutorial/7_logging_debugging.md +304 -0
  22. data/tutorial/8_conclusion.md +11 -0
  23. data/tutorial/code/.rvmrc +1 -0
  24. data/tutorial/code/fullstop/.gitignore +5 -0
  25. data/tutorial/code/fullstop/Gemfile +4 -0
  26. data/tutorial/code/fullstop/LICENSE.txt +202 -0
  27. data/tutorial/code/fullstop/README.rdoc +23 -0
  28. data/tutorial/code/fullstop/Rakefile +31 -0
  29. data/tutorial/code/fullstop/bin/fullstop +43 -0
  30. data/tutorial/code/fullstop/features/fullstop.feature +40 -0
  31. data/tutorial/code/fullstop/features/step_definitions/fullstop_steps.rb +64 -0
  32. data/tutorial/code/fullstop/features/support/env.rb +22 -0
  33. data/tutorial/code/fullstop/fullstop.gemspec +28 -0
  34. data/tutorial/code/fullstop/lib/fullstop/repo.rb +38 -0
  35. data/tutorial/code/fullstop/lib/fullstop/version.rb +3 -0
  36. data/tutorial/code/fullstop/lib/fullstop.rb +2 -0
  37. data/tutorial/code/fullstop/test/tc_something.rb +7 -0
  38. data/tutorial/en.utf-8.add +18 -0
  39. data/tutorial/toc.md +27 -0
  40. metadata +49 -20
data/tutorial/3_ui.md ADDED
@@ -0,0 +1,349 @@
1
+ # UI
2
+
3
+ We're taking an [outside-in][outsidein] approach to our app. This means that we start with the user interface, and work our way
4
+ down to make that interface a reality. I highly recommend this approach, since it forces you to focus on the user of your app
5
+ first. Doing this will result in an app that's easier to use, and thus easier for you to maintain and enhance over time.
6
+
7
+ Before we dive into our Cucumber features, let's take a moment to think about how our app might work. Essentially, we want it to
8
+ clone a git repository somewhere on disk, and then symlink all of those files and directories in the top level of that repo into
9
+ our home directory. If we did this in `bash` on the command line, it might be something like this:
10
+
11
+ ```sh
12
+ $ cd ~
13
+ $ git clone git@github.com:davetron5000/dotfiles.git
14
+ $ for file in `ls -a dotfiles`; do
15
+ > ln -s dotfiles/$file .
16
+ > done
17
+ ```
18
+
19
+ It's worth understanding why we don't just make this entire thing a `bash` script. Our app is going to need more smarts than the
20
+ above code: for example, it will need to be able to check if the repo is cloned already, and update it instead of cloning. It
21
+ will need good error handling so the user knows if they did something wrong. It might need more complex logic as we use the app
22
+ over time. Implementing these in `bash` is painful. `bash` is not a very powerful language, and we'll quickly hit a wall.
23
+ Although `bash` is "close to the metal", we'll see that Ruby + Methadone can provide a *very* similar programming experience.
24
+
25
+ Now, let's think about how our app will work. We can summarize our app's interface as having two main features at this point:
26
+
27
+ * It should accept a required argument that is the URL of the repo to clone
28
+ * It should otherwise be a well-behaved and polished command-line app:
29
+ * It should have online help.
30
+ * Getting help is not an error and the app should exit zero when you get help.
31
+ * There should be a usage statement for the app's invocation syntax.
32
+ * The app should document what it does.
33
+ * The app should indicate what options and arguments it takes.
34
+
35
+ Aruba and Methadone provide all the steps we need to test for these aspects of our app's user interface. Here's the feature
36
+ we'll use to test this. We can replace the contents of `features/fullstop.feature` with this:
37
+
38
+ ```cucumber
39
+ Feature: Checkout dotfiles
40
+ In order to get my dotfiles onto a new computer
41
+ I want a one-command way to keep them up to date
42
+ So I don't have to do it myself
43
+
44
+ Scenario: Basic UI
45
+ When I get help for "fullstop"
46
+ Then the exit status should be 0
47
+ And the banner should be present
48
+ And there should be a one line summary of what the app does
49
+ And the banner should include the version
50
+ And the banner should document that this app takes options
51
+ And the banner should document that this app's arguments are:
52
+ |repo_url|which is required|
53
+ ```
54
+
55
+ This scenario describes getting help for our app and the basic user interface that we need. This is how we "test-drive" the
56
+ development of the user interface portion of our app. Let's run the scenario and see what happens.
57
+
58
+ ```sh
59
+ $ rake features
60
+ Feature: Checkout dotfiles
61
+ In order to get my dotfiles onto a new computer
62
+ I want a one-command way to keep them up to date
63
+ So I don't have to do it myself
64
+
65
+ Scenario: Basic UI
66
+ When I get help for "fullstop"
67
+ Then the exit status should be 0
68
+ And the banner should be present
69
+ And there should be a one line summary of what the app does
70
+ expected "v0.0.1" to match /^\w\w+\s+\w\w+/ (RSpec::Expectations::ExpectationNotMetError)
71
+ features/fullstop.feature:10:in `And there should be a one line summary of what the app does'
72
+ And the banner should include the version
73
+ And the banner should document that this app takes options
74
+ And the banner should document that this app's arguments are:
75
+ | repo_url | which is required |
76
+
77
+ Failing Scenarios:
78
+ cucumber features/fullstop.feature:6
79
+
80
+ 1 scenario (1 failed)
81
+ 7 steps (1 failed, 3 skipped, 3 passed)
82
+ 0m0.126s
83
+ rake aborted!
84
+ Cucumber failed
85
+
86
+ Tasks: TOP => features
87
+ (See full trace by running task with --trace)
88
+ ```
89
+
90
+ We have a failing test! Note that cucumber knows about all of these steps; between Aruba and Metahdone, they are all already
91
+ defined. We'll see some custom steps later, that are specific to our app, but for now, we haven't had to write any testing code,
92
+ which is great!
93
+
94
+ You'll also notice that some steps are already passing, despite the fact that we've done no coding. Also notice that
95
+ cucumber didn't complain about unknown steps. Methadone provides almost all of these cucumber steps for us. The rest are
96
+ provided by Aruba. Since Methadone generated an executable for us when we ran the `methadone` command, it already provides the
97
+ ability to get help, and exits with the correct exit status.
98
+
99
+ Let's fix things one step at a time, so we can see exactly what we need to do. The current scenario is failing because our app
100
+ doesn't have a one line summary. This summary is important so that we can remember what the app does later on (despite how
101
+ clever our name is, it's likely we'll forget a few months from now and the description will jog our memory).
102
+
103
+ Let's have a look at our executable. A Methadone app is made up of four parts: the setup where we require necessary libraries, a "main" block containing the primary logic of our code, a block of code that declares the app's UI, and a call to `go!`, which runs our app.
104
+
105
+ ```ruby
106
+ #!/usr/bin/env ruby
107
+
108
+ # setup
109
+ require 'optparse'
110
+ require 'methadone'
111
+ require 'fullstop'
112
+
113
+ class App
114
+ include Methadone::Main
115
+ include Methadone::CLILogging
116
+
117
+ # the main block
118
+ main do
119
+
120
+ end
121
+
122
+ # declare UI
123
+ version Fullstop::VERSION
124
+
125
+ use_log_level_option
126
+
127
+ # call go!
128
+ go!
129
+ end
130
+ ```
131
+
132
+ There's not much magic going on here; you could think of this code as being roughly equivalent to:
133
+
134
+ ```ruby
135
+ def main
136
+ end
137
+
138
+ opts = OptionParser.new
139
+ opts.banner = "usage: $0 [options]\n\nversion: #{Fullstop::VERSION}"
140
+ opts.parse!
141
+
142
+ main
143
+ ```
144
+
145
+ We'll see later that Methadone does a lot more than this, but this should help you understand the control flow. We now need to
146
+ add a one-line description for our app.
147
+
148
+ In a vanilla Ruby application, we'd use the `banner` method of an `OptionParser` to add this description (much as we do with the
149
+ version in the above, non-Methadone code). Methadone actually manages an
150
+ `OptionParser` instance that we can use, available via the `opts` method. We could call `banner` on that to set our description, but Methadone provides a convenience method to do it for us: `description`.
151
+
152
+ `description` takes a single argument: a one-line description of our app. Methadone will then include this in the online help
153
+ output of our app when the user uses `-h` or `--help`. We'll add a call to it in the "declare UI" portion of our code:
154
+
155
+ ```ruby
156
+ #!/usr/bin/env ruby
157
+
158
+ require 'optparse'
159
+ require 'methadone'
160
+ require 'fullstop'
161
+
162
+ class App
163
+ include Methadone::Main
164
+ include Methadone::CLILogging
165
+
166
+ main do
167
+ end
168
+
169
+ version Fullstop::VERSION
170
+
171
+ # vvv
172
+ description 'Manages dotfiles from a git repo'
173
+ # ^^^
174
+
175
+ use_log_level_option
176
+
177
+ go!
178
+ end
179
+ ```
180
+
181
+ Now, let's run our scenario again:
182
+
183
+ ```sh
184
+ $ rake features
185
+ Feature: Checkout dotfiles
186
+ In order to get my dotfiles onto a new computer
187
+ I want a one-command way to keep them up to date
188
+ So I don't have to do it myself
189
+
190
+ Scenario: Basic UI
191
+ When I get help for "fullstop"
192
+ Then the exit status should be 0
193
+ And the banner should be present
194
+ And there should be a one line summary of what the app does
195
+ And the banner should include the version
196
+ And the banner should document that this app takes options
197
+ And the banner should document that this app's arguments are:
198
+ | repo_url | which is required |
199
+ expected "Usage: fullstop [options]\n\nManages dotfiles from a git repo\n\nv0.0.1\n\nOptions:\n --version Show help/version info\n --log-level LEVEL Set the logging level (debug|info|warn|error|fatal)\n (Default: info)\n" to include "repo_url"
200
+ Diff:
201
+ @@ -1,2 +1,11 @@
202
+ -["repo_url"]
203
+ +Usage: fullstop [options]
204
+ +
205
+ +Manages dotfiles from a git repo
206
+ +
207
+ +v0.0.1
208
+ +
209
+ +Options:
210
+ + --version Show help/version info
211
+ + --log-level LEVEL Set the logging level (debug|info|warn|error|fatal)
212
+ + (Default: info)
213
+ (RSpec::Expectations::ExpectationNotMetError)
214
+ features/fullstop.feature:13:in `And the banner should document that this app's arguments are:'
215
+
216
+ Failing Scenarios:
217
+ cucumber features/fullstop.feature:6
218
+
219
+ 1 scenario (1 failed)
220
+ 7 steps (1 failed, 6 passed)
221
+ 0m0.132s
222
+ rake aborted!
223
+ Cucumber failed
224
+
225
+ Tasks: TOP => features
226
+ (See full trace by running task with --trace)
227
+ ```
228
+
229
+ We got farther this time. Our step for checking that we have a one-line summary is passing. Further, the next two following steps are also passing, despite the fact that we did nothing to explicitly make them pass. Like the preceding steps ("Then the exit status should be 0" and "And the banner should be present"), the two steps following the one we just fixed pass because Methadone has bootstrapped our app in a way that they are already passing.
230
+
231
+ The call to Methadone's `version` method ensures that the version of our app appears in the online help. The other step, "And the banner should document that this app takes options" passes because we are allowing Methadone to manage the banner. Methadone knows that our app takes options (namely `--version`), and inserts the string `"[options]"` into the usage statement.
232
+
233
+ The last step in our scenario is still failing, so let's fix that to finish up our user interface.
234
+ What Methadone is looking for is for the string `repo_url` (the name of our only,
235
+ required, argument) to be in the usage string, in other words, Methadone is expecting to see this:
236
+
237
+ ```
238
+ Usage: fullstop [option] repo_url
239
+ ```
240
+
241
+ Right now, our app's usage string looks like this:
242
+
243
+ ```
244
+ Usage: fullstop [option]
245
+ ```
246
+
247
+ Again, if we were using `OptionParser`, we would need to modify the argument given to `banner` to include this string. Methadone
248
+ provides a method, `arg` that will do this automatically for us. We'll add it right after the call to `description` in the
249
+ "declare UI" section of our app:
250
+
251
+ ```ruby
252
+ #!/usr/bin/env ruby
253
+
254
+ require 'optparse'
255
+ require 'methadone'
256
+ require 'fullstop'
257
+
258
+ class App
259
+ include Methadone::Main
260
+ include Methadone::CLILogging
261
+
262
+ main do
263
+ end
264
+
265
+ version Fullstop::VERSION
266
+
267
+ description 'Manages dotfiles from a git repo'
268
+
269
+ # vvv
270
+ arg :repo_url, "URL to the git repository containing your dotfiles"
271
+ # ^^^
272
+
273
+ use_log_level_option
274
+
275
+ go!
276
+ end
277
+ ```
278
+
279
+ Now, when we run our features again, we can see that everything passes:
280
+
281
+ ```sh
282
+ $ rake features
283
+ Feature: Checkout dotfiles
284
+ In order to get my dotfiles onto a new computer
285
+ I want a one-command way to keep them up to date
286
+ So I don't have to do it myself
287
+
288
+ Scenario: Basic UI
289
+ When I get help for "fullstop"
290
+ Then the exit status should be 0
291
+ And the banner should be present
292
+ And there should be a one line summary of what the app does
293
+ And the banner should include the version
294
+ And the banner should document that this app takes options
295
+ And the banner should document that this app's arguments are:
296
+ | repo_url | which is required |
297
+
298
+ 1 scenario (1 passed)
299
+ 7 steps (7 passed)
300
+ 0m0.129s
301
+ ```
302
+
303
+ Nice! If our UI should ever change, we'll notice the regression, and we also have an easy way to use TDD to enhance our
304
+ application's UI in the future. Let's take a look at it ourselves to see what it's like:
305
+
306
+ ```sh
307
+ $ bundle exec bin/fullstop --help
308
+ Usage: fullstop [options] repo_url
309
+
310
+ Manages dotfiles from a git repo
311
+
312
+ v0.0.1
313
+
314
+ Options:
315
+ --version Show help/version info
316
+ --log-level LEVEL Set the logging level (debug|info|warn|error|fatal)
317
+ (Default: info)
318
+ ```
319
+
320
+ Not to bad for having written two lines of code! We can also see that `fullstop` will error out if we omit our required
321
+ argument, `repo_url`:
322
+
323
+ ```sh
324
+ $ bundle exec bin/fullstop
325
+ parse error: 'repo_url' is required
326
+
327
+ Usage: fullstop [options] repo_url
328
+
329
+ Manages dotfiles from a git repo
330
+
331
+ v0.0.1
332
+
333
+ Options:
334
+ --version Show help/version info
335
+ --log-level LEVEL Set the logging level (debug|info|warn|error|fatal)
336
+ (Default: info)
337
+ $ echo $?
338
+ 64
339
+ ```
340
+
341
+ We see an error message, and exited nonzero (64 is a somewhat standard exit code for errors in command-line invocation).
342
+
343
+ It's also worth pointing out that Methadone is taking a very light touch. We could completely re-implement `bin/fullstop` using
344
+ `OptionParser` and still have our scenario pass. As we'll see, few of Methadone's parts really rely on each other, and many can
345
+ be used piecemeal, if that's what you want.
346
+
347
+ Now that we have our UI, the next order of business is to actually implement something.
348
+
349
+ [outsidein]: http://en.wikipedia.org/wiki/Outside%E2%80%93in_software_development