-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathslides_issue.html
More file actions
533 lines (495 loc) · 37.4 KB
/
slides_issue.html
File metadata and controls
533 lines (495 loc) · 37.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Slides for
Issue Lifecycle — Ruby on Rails Guides
</title>
<link rel="stylesheet" type="text/css" href="stylesheets/style.css" data-turbo-track="reload">
<link rel="stylesheet" type="text/css" href="stylesheets/print.css" media="print">
<link rel="stylesheet" type="text/css" href="stylesheets/highlight.css" data-turbo-track="reload">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="stylesheets/reset.css">
<link rel="stylesheet" href="stylesheets/reveal.css">
<link rel="stylesheet" href="stylesheets/myslide.css" id="theme">
<link rel="stylesheet" href="stylesheets/code.css">
<script src="javascripts/clipboard.js" data-turbo-track="reload"></script>
<script src="javascripts/slides.js" data-turbo-track="reload"></script>
</head>
<body>
<div class="reveal">
<!-- Any section element inside of this container is displayed as a slide -->
<div class="slides">
<section>
<h1>Issue Lifecycle</h1><p>Software development in long-running projects is often organized around issues. After reading this you will...</p>
<ul>
<li>understand how issues, version control and testing interact</li>
<li>understand two levels of test driven development</li>
<li>understand browser driven development</li>
</ul>
<p><small>Slides - use arrow keys to navigate, esc to return to page view, f for fullscreen</small></p>
</section>
<section><a class='slide_break' href='issue.html#slide-0'>▻</a>
<h2 id="the-issue-lifecycle-beginning"><a class="anchorlink" href="#the-issue-lifecycle-beginning"><span>1</span> The Issue Lifecycle: Beginning</a></h2><p>The top level view just the issue:</p>
<ol>
<li>Define the Issue: The problem (bug) or new requirement (feature/story) is given a clear description</li>
<li>A developer picks up the issue</li>
<li>Somehow it is implemented (details unclear on this level)</li>
<li>Somehow it is prepared for manual testing (details unclear on this level)</li>
<li>Now the implementation can be tested in the browser, and compared to the issue</li>
<li>If the implmentation is accepted the issue is closed, otherwise return to step 2</li>
</ol>
<p><img src="images/issue-workflow-level-1.svg" alt=""></p></section>
<section><a class='slide_break' href='issue.html#slide-1'>▻</a>
<h3 id="example-issue"><a class="anchorlink" href="#example-issue"><span>1.1</span> Example Issue</a></h3><p>Let's look at a simple example issue from a Ruby on Rails project:</p><div class="interstitial code">
<pre><code class="highlight plaintext">Issue #3: For admin users, show a Dashboard at '/admin' and display the number of Stamps
</code></pre>
<button class="clipboard-button" data-clipboard-text="Issue #3: For admin users, show a Dashboard at '/admin' and display the number of Stamps
">Copy</button>
</div>
<p>It is very short and simple. Maybe it is the first Issue that other Issues are then based on? Maybe there are Issue #4, #5, #6 that add more Data to the dashboard?</p><p>Maybe the issue was created by developers, when they broke up a longer user story into smaller pieces.</p></section>
<section><a class='slide_break' href='issue.html#slide-2'>▻</a>
<h2 id="the-git-workflow-beginning"><a class="anchorlink" href="#the-git-workflow-beginning"><span>2</span> The Git Workflow: Beginning</a></h2><p>The next level concerns the git workflow:</p>
<ol>
<li>after picking the issue the developer creates a feature branch and a merge request. the merge request is set to "draft" status.</li>
<li>then the issue is implemented (details unclear on this level)</li>
<li> when the developer thinks the implementation is done, they remove the "draft" status</li>
<li>now another developer does a code review</li>
<li> if changes are needed we go back to step 2.</li>
<li>now the feature branch can be deployed to the staging server, where it is available for manual testing.</li>
<li>if problems are found in manual testing we go back to step 2.</li>
<li>if the implementation is accepted the feature branch kann be merged and deployed to production</li>
</ol>
<p><img src="images/issue-workflow-with-git.svg" alt=""></p></section>
<section><a class='slide_break' href='issue.html#slide-3'>▻</a>
<h3 id="creating-a-branch"><a class="anchorlink" href="#creating-a-branch"><span>2.1</span> Creating a branch</a></h3><p>To create the feature branch the developer picks a name, and uses git on the command line to create the branch:</p><div class="interstitial code">
<pre><code class="highlight plaintext">git checkout -b feature_dashboard_issue_3
</code></pre>
<button class="clipboard-button" data-clipboard-text="git checkout -b feature_dashboard_issue_3
">Copy</button>
</div>
<p>There might be a convention in place about naming branches.</p><p>The branch can be pushed to the git server immediatly:</p><div class="interstitial code">
<pre><code class="highlight plaintext">git push origin feature_dashboard_issue_3
</code></pre>
<button class="clipboard-button" data-clipboard-text="git push origin feature_dashboard_issue_3
">Copy</button>
</div>
</section>
<section><a class='slide_break' href='issue.html#slide-4'>▻</a>
<h3 id="creating-a-merge-request"><a class="anchorlink" href="#creating-a-merge-request"><span>2.2</span> Creating a merge request</a></h3><p>Creating the merge request can be done through the web interface. For example when pushing a new branch to gitlab, you will get a reply that includes a URL for that.</p><div class="interstitial code">
<pre><code class="highlight plaintext">remote: To create a merge request for feature_dashboard_issue_3, visit:
remote: https://gitlab.example.com/groupname/projectname/-/merge_requests/new?merge_request%5Bsource_branch%5D=feature_dashboard_issue_3
</code></pre>
<button class="clipboard-button" data-clipboard-text="remote: To create a merge request for feature_dashboard_issue_3, visit:
remote: https://gitlab.example.com/groupname/projectname/-/merge_requests/new?merge_request%5Bsource_branch%5D=feature_dashboard_issue_3
">Copy</button>
</div>
</section>
<section><a class='slide_break' href='issue.html#slide-5'>▻</a>
<h3 id="the-merge-request-in-the-browser"><a class="anchorlink" href="#the-merge-request-in-the-browser"><span>2.3</span> The merge request in the Browser</a></h3><p>This is what creating a merge request looks like. The screenshot shows two important details:</p>
<ul>
<li>be sure to activate the checkbox "Mark as draft"</li>
<li>in the description, you can reference Issues by their number</li>
</ul>
<p><img src="images/create-merge-request@2x.png" alt=""></p></section>
<section><a class='slide_break' href='issue.html#slide-6'>▻</a>
<h3 id="working-with-the-merge-requst"><a class="anchorlink" href="#working-with-the-merge-requst"><span>2.4</span> Working with the merge requst</a></h3><p>The merge request compares your feature branch to the main branch. You can add commits now or later on - the merge request will always show the latest version.</p></section>
<section><a class='slide_break' href='issue.html#slide-7'>▻</a>
<h2 id="test-driven-development"><a class="anchorlink" href="#test-driven-development"><span>3</span> Test Driven Development</a></h2><p>Zooming in to step 2 "the issue is implemented", we can see the cycle of test driven development. If we are implementing an issue that delivers value to the user, we should start with system tests:</p>
<ol>
<li>break the issue up into a list of possible system tests - but don't implment anything yet</li>
<li>implement the simplest system test from your list - if you run your test suite now, your test ist red</li>
<li>implement just enough to make the new test (and all the existing tests) pass (green)</li>
<li>commit and push</li>
<li>refactor if you think it's necessary. all the tests still pass.</li>
<li>commit and push</li>
<li>are you done with the whole issue? if not go back to step 1</li>
</ol>
<p><img src="images/tdd-web-system-test.drawio.svg" alt=""></p></section>
<section><a class='slide_break' href='issue.html#slide-8'>▻</a>
<h3 id="example-system-test"><a class="anchorlink" href="#example-system-test"><span>3.1</span> Example System Test</a></h3><p>Let's look at the example issue:</p><div class="interstitial code">
<pre><code class="highlight plaintext">Issue #3: For admin users, show a Dashboard at '/admin' and display the number of Stamps
</code></pre>
<button class="clipboard-button" data-clipboard-text="Issue #3: For admin users, show a Dashboard at '/admin' and display the number of Stamps
">Copy</button>
</div>
<p>There are several aspects that we could turn into system tests:</p>
<ol>
<li>Only admin users should have access to the url '/admin'</li>
<li>i expect the heading "Dashboard" to be on the page</li>
<li>i expect the number of stamps to be on the page</li>
</ol>
<p>Which one is the most simple to implement? Probably number 2.</p></section>
<section><a class='slide_break' href='issue.html#slide-9'>▻</a>
<h3 id="implementing-a-system-test-in-rails"><a class="anchorlink" href="#implementing-a-system-test-in-rails"><span>3.2</span> Implementing a System Test in Rails</a></h3><p>Let's look for a place to put the system test. Take a look at the files <code>tests/system/*_test.rb</code>.</p><p>Maybe we should create a new test file for the Dashboard. There is a generator that could do that:</p><div class="interstitial code">
<pre><code class="highlight plaintext">rails generate system_test AdminDashboard
</code></pre>
<button class="clipboard-button" data-clipboard-text="rails generate system_test AdminDashboard
">Copy</button>
</div>
<p>It will create just one file:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="nb">require</span> <span class="s2">"application_system_test_case"</span>
<span class="k">class</span> <span class="nc">AdminDashboardsTest</span> <span class="o"><</span> <span class="no">ApplicationSystemTestCase</span>
<span class="c1"># test "visiting the index" do</span>
<span class="c1"># visit admin_dashboards_url</span>
<span class="c1">#</span>
<span class="c1"># assert_selector "h1", text: "AdminDashboard"</span>
<span class="c1"># end</span>
<span class="k">end</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="require "application_system_test_case"
class AdminDashboardsTest < ApplicationSystemTestCase
# test "visiting the index" do
# visit admin_dashboards_url
#
# assert_selector "h1", text: "AdminDashboard"
# end
end
">Copy</button>
</div>
<p>Or you could copy an existing file with system tests, and take some inspiration from existing code.
The first system test could look like this:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="k">class</span> <span class="nc">AdminDashboardsTest</span> <span class="o"><</span> <span class="no">ApplicationSystemTestCase</span>
<span class="nb">test</span> <span class="s1">'visit the dashboard'</span> <span class="k">do</span>
<span class="n">visit</span> <span class="s1">'/admin'</span>
<span class="n">assert_selector</span> <span class="s1">'h1'</span><span class="p">,</span> <span class="ss">text: </span><span class="s1">'Admin Dashboard'</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="class AdminDashboardsTest < ApplicationSystemTestCase
test 'visit the dashboard' do
visit '/admin'
assert_selector 'h1', text: 'Admin Dashboard'
end
end
">Copy</button>
</div>
</section>
<section><a class='slide_break' href='issue.html#slide-10'>▻</a>
<h3 id="system-test-red"><a class="anchorlink" href="#system-test-red"><span>3.3</span> System Test - Red</a></h3><p>When running the system tests in the terminal:</p><div class="interstitial code">
<pre><code class="highlight plaintext">rails test:system
</code></pre>
<button class="clipboard-button" data-clipboard-text="rails test:system
">Copy</button>
</div>
<p>the new test will fail:</p><div class="interstitial code">
<pre><code class="highlight plaintext">[Screenshot Image]: ...project/tmp/screenshots/failures_test_visit_the_dashboard.png
F
Failure:
AdminDashboardsTest#test_visit_the_dashboard [test/system/admin_dashboards_test.rb:13]:
expected to find text "Admin Dashboard" in "Routing Error\nNo route matches [GET] "/admin"
</code></pre>
<button class="clipboard-button" data-clipboard-text="[Screenshot Image]: ...project/tmp/screenshots/failures_test_visit_the_dashboard.png
F
Failure:
AdminDashboardsTest#test_visit_the_dashboard [test/system/admin_dashboards_test.rb:13]:
expected to find text "Admin Dashboard" in "Routing Error\nNo route matches [GET] "/admin"
">Copy</button>
</div>
<p>For implementing this feature we can use different methods. In this simple case, "Browser Driven Development" might be good.</p></section>
<section><a class='slide_break' href='issue.html#slide-11'>▻</a>
<h2 id="browser-driven-development"><a class="anchorlink" href="#browser-driven-development"><span>4</span> Browser Driven Development</a></h2><p>To get a system test to pass we sometimes need to build up a new webpage, or a new view in an MVC system. Then it makes sense to switch to "browser driven development" for the implementation.</p><p>By using the browser in every step we make sure that all the parts of the system fit together. finally we reach a stage where the system test passes.</p><p><img src="images/browser-driven-development.svg" alt=""></p><p>This also works for creating new REST API endpoints, just that the goal is seeing the right JSON output in the browser, not a webpage.</p></section>
<section><a class='slide_break' href='issue.html#slide-12'>▻</a>
<h3 id="example-missing-route"><a class="anchorlink" href="#example-missing-route"><span>4.1</span> Example: Missing route</a></h3><p>In Rails this could look like this:</p><p>Open the browser to the (new) route '/admin' and see the first error:</p><p><img src="images/browser-driven-routing-error@2x.png" alt=""></p><p>To define the missing route, we edit the file <code>config/routes.rb</code>. Read the already existing routes and find a good place to fit in the new route.</p><p>In this case there is already a <code>PagesController</code> for pages that are not connected to one specific model. This is where the new route can fit in:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="c1"># file config/routes.rb</span>
<span class="c1"># ...</span>
<span class="n">get</span> <span class="s1">'/about'</span><span class="p">,</span> <span class="ss">to: </span><span class="s1">'pages#index'</span>
<span class="n">get</span> <span class="s1">'/calendar_subscription'</span><span class="p">,</span> <span class="ss">to: </span><span class="s1">'pages#calendar_subscription'</span>
<span class="n">get</span> <span class="s1">'/admin'</span><span class="p">,</span> <span class="ss">to: </span><span class="s1">'pages#admin'</span> <span class="c1"># new route</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="# file config/routes.rb
# ...
get '/about', to: 'pages#index'
get '/calendar_subscription', to: 'pages#calendar_subscription'
get '/admin', to: 'pages#admin' # new route
">Copy</button>
</div>
</section>
<section><a class='slide_break' href='issue.html#slide-13'>▻</a>
<h3 id="example-missing-controller-action"><a class="anchorlink" href="#example-missing-controller-action"><span>4.2</span> Example: Missing Controller Action</a></h3><p>Now reload the page '/admin' to see the new error:</p><p><img src="images/browser-driven-action-error@2x.png" alt=""></p><p>So the route and the PagesController exist, but there is not action 'admin' there.</p><p>Let's go to <code>app/controllers/pages_controller.rb</code> and define a new method <code>admin</code>:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="k">class</span> <span class="nc">PagesController</span> <span class="o"><</span> <span class="no">ApplicationController</span>
<span class="k">def</span> <span class="nf">admin</span><span class="p">;</span> <span class="k">end</span>
<span class="o">...</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="class PagesController < ApplicationController
def admin; end
...
">Copy</button>
</div>
</section>
<section><a class='slide_break' href='issue.html#slide-14'>▻</a>
<h3 id="example-missing-view-error"><a class="anchorlink" href="#example-missing-view-error"><span>4.3</span> Example: Missing View Error</a></h3><p>Now reload the page '/admin' again to see the new error:</p><p><img src="images/browser-driven-view-error@2x.png" alt=""></p><p>The error message is very specific, it tells us which view file to create.</p><p>Let's create this file:</p><div class="interstitial code">
<pre><code class="highlight html"><span class="nt"><h1></span>Admin Dashboard<span class="nt"></h1></span>
...nothing here yet...
</code></pre>
<button class="clipboard-button" data-clipboard-text="<h1>Admin Dashboard</h1>
...nothing here yet...
">Copy</button>
</div>
</section>
<section><a class='slide_break' href='issue.html#slide-15'>▻</a>
<h3 id="example-system-test-green"><a class="anchorlink" href="#example-system-test-green"><span>4.4</span> Example: System Test Green</a></h3><p>This version of the code already fulfilles the system test we wrote.
So we are done with "Browser Driven Developmernt".</p><p>Now is a good time to commit and push!</p></section>
<section><a class='slide_break' href='issue.html#slide-16'>▻</a>
<h2 id="tdd-inner-cycle"><a class="anchorlink" href="#tdd-inner-cycle"><span>5</span> TDD Inner Cycle</a></h2><p>To get a system test to pass we sometimes need to add code to different classes. In this case it makes sense to do an "inner cycle" of TDD. This will sound very similar to the TDD Cycle we used with <strong>system tests</strong>, but this time it is concerned with <strong>unit tests</strong>:</p>
<ol>
<li>Think of all the parts you might need to fulfill the system test, and how you would test each of them - but don't implement anything yet</li>
<li>pick the simplest part, and the simplest unit test for it, and write that unit test. (red)</li>
<li>implement it (green)</li>
<li>commit and push</li>
<li>refactor if you think it's necessary. all the tests still pass.</li>
<li>do you have all the pieces neded to get the system test to pass? if not, go back to 1</li>
</ol>
<p>While doing this "inner cycle" of TDD you build up the pieces and finally the whole functionality necessary to get the system test to pass.</p><p><img src="images/tdd-web-unit-test.svg" alt=""></p></section>
<section><a class='slide_break' href='issue.html#slide-17'>▻</a>
<h3 id="example-display-number-of-stamps"><a class="anchorlink" href="#example-display-number-of-stamps"><span>5.1</span> Example: Display Number of Stamps</a></h3><p>Let's go back to the "list of possible system tests" in our example:</p>
<ol>
<li>Only admin users should have access to the url '/admin'</li>
<li>i expect the heading "Dashboard" to be on the page</li>
<li>i expect the number of stamps to be on the page</li>
</ol>
<p>Number 2 is already implemented.</p><p>Let's pick Number 3, write a system test for it, and then think about what we need to implment it.</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="k">class</span> <span class="nc">AdminDashboardTest</span> <span class="o"><</span> <span class="no">ApplicationSystemTestCase</span>
<span class="c1"># ...</span>
<span class="nb">test</span> <span class="s1">'displays the number of Stamps'</span> <span class="k">do</span>
<span class="n">visit</span> <span class="s1">'/admin'</span>
<span class="n">assert_text</span> <span class="sr">/\d+ stamps in total/</span>
<span class="k">end</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="class AdminDashboardTest < ApplicationSystemTestCase
# ...
test 'displays the number of Stamps' do
visit '/admin'
assert_text /\d+ stamps in total/
end
">Copy</button>
</div>
</section>
<section><a class='slide_break' href='issue.html#slide-18'>▻</a>
<h3 id="example-where-to-put-the-code-questionmark"><a class="anchorlink" href="#example-where-to-put-the-code-questionmark"><span>5.2</span> Example: Where to put the code?</a></h3><p>The projects contains a model <code>Stamp</code>. So we could use ActiveRecord Methods
to count the number of Stamps: <code>Stamp.all.count</code>.</p><p>We could put this code into the view:</p><div class="interstitial code">
<pre><code class="highlight plaintext"><h1>Admin Dashboard</h1>
<p><%= Stamp.all.count %> stamps in total.</p>
</code></pre>
<button class="clipboard-button" data-clipboard-text="<h1>Admin Dashboard</h1>
<p><%= Stamp.all.count %> stamps in total.</p>
">Copy</button>
</div>
<p>Or we could put the code in the controller, and then disply it in the view:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="k">class</span> <span class="nc">PagesController</span> <span class="o"><</span> <span class="no">ApplicationController</span>
<span class="k">def</span> <span class="nf">admin</span>
<span class="vi">@number_of_stamps</span> <span class="o">=</span> <span class="no">Stamp</span><span class="p">.</span><span class="nf">all</span><span class="p">.</span><span class="nf">count</span>
<span class="k">end</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="class PagesController < ApplicationController
def admin
@number_of_stamps = Stamp.all.count
end
">Copy</button>
</div>
<p>or we could put the code in the Stamp model, and just call a method in the controller:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="k">class</span> <span class="nc">PagesController</span> <span class="o"><</span> <span class="no">ApplicationController</span>
<span class="k">def</span> <span class="nf">admin</span>
<span class="vi">@number_of_stamps</span> <span class="o">=</span> <span class="no">Stamp</span><span class="p">.</span><span class="nf">total_number</span>
<span class="k">end</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="class PagesController < ApplicationController
def admin
@number_of_stamps = Stamp.total_number
end
">Copy</button>
</div>
<p>The last version is best: the Stamp model should contain the business logic of stamps. In this case the logic is (still) simple. But there could be stamps that are not approved yet, and do not count. Or there could be stamps that are invalid, or outdated. This is business logic.</p></section>
<section><a class='slide_break' href='issue.html#slide-19'>▻</a>
<h3 id="example-unit-test-red"><a class="anchorlink" href="#example-unit-test-red"><span>5.3</span> Example: Unit Test - Red</a></h3><p>Let's write a unit test for the method of the Stamp model. <code>test/model/stamp_test.rb</code> already exists, we can just add a new test case.</p><div class="interstitial code">
<pre><code class="highlight ruby"> <span class="nb">test</span> <span class="s1">'the Stamp class knows how many stamps there are in total'</span> <span class="k">do</span>
<span class="no">Stamp</span><span class="p">.</span><span class="nf">delete_all</span>
<span class="n">assert_equal</span> <span class="mi">0</span><span class="p">,</span> <span class="no">Stamp</span><span class="p">.</span><span class="nf">total_number</span>
<span class="k">end</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text=" test 'the Stamp class knows how many stamps there are in total' do
Stamp.delete_all
assert_equal 0, Stamp.total_number
end
">Copy</button>
</div>
<p>This test seems radical: it deletes all the stamps from the database.</p><p>But this is not a problem: the test database is recreated for every single test. So if we delete something in the first test, it will be back for the second test. This is done using transactional rollback.</p><p>We can read the other tests in <code>stamp_test.rb</code> to find out how stamps can be created. We can use that knowledge to improve our test by haveing it create a new stamp and make sure it is counted:</p><div class="interstitial code">
<pre><code class="highlight ruby"> <span class="nb">test</span> <span class="s1">'the Stamp class knows how many stamps there are in total'</span> <span class="k">do</span>
<span class="no">Stamp</span><span class="p">.</span><span class="nf">delete_all</span>
<span class="n">assert_equal</span> <span class="mi">0</span><span class="p">,</span> <span class="no">Stamp</span><span class="p">.</span><span class="nf">total_number</span>
<span class="n">student</span> <span class="o">=</span> <span class="no">User</span><span class="p">.</span><span class="nf">first</span>
<span class="n">some_event</span> <span class="o">=</span> <span class="no">Event</span><span class="p">.</span><span class="nf">first</span>
<span class="n">s</span> <span class="o">=</span> <span class="no">Stamp</span><span class="p">.</span><span class="nf">create!</span><span class="p">(</span><span class="ss">user: </span><span class="n">student</span><span class="p">,</span> <span class="ss">event: </span><span class="n">some_event</span><span class="p">)</span>
<span class="n">assert_equal</span> <span class="mi">1</span><span class="p">,</span> <span class="no">Stamp</span><span class="p">.</span><span class="nf">total_number</span>
<span class="k">end</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text=" test 'the Stamp class knows how many stamps there are in total' do
Stamp.delete_all
assert_equal 0, Stamp.total_number
student = User.first
some_event = Event.first
s = Stamp.create!(user: student, event: some_event)
assert_equal 1, Stamp.total_number
end
">Copy</button>
</div>
<p>When we run this test now in the termine it is red:</p><div class="interstitial code">
<pre><code class="highlight plaintext">Error:
StampTest#test_the_Stamp_class_knows_how_many_stamps_there_are_in_total:
NoMethodError: undefined method 'total_number' for class Stamp
test/models/stamp_test.rb:66:in 'block in <class:StampTest>'
</code></pre>
<button class="clipboard-button" data-clipboard-text="Error:
StampTest#test_the_Stamp_class_knows_how_many_stamps_there_are_in_total:
NoMethodError: undefined method 'total_number' for class Stamp
test/models/stamp_test.rb:66:in 'block in <class:StampTest>'
">Copy</button>
</div>
</section>
<section><a class='slide_break' href='issue.html#slide-20'>▻</a>
<h3 id="example-unit-test-green"><a class="anchorlink" href="#example-unit-test-green"><span>5.4</span> Example: Unit Test - Green</a></h3><p>Now it's time to edit the stamp model <code>app/models/stamp.rb</code>:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="k">class</span> <span class="nc">Stamp</span> <span class="o"><</span> <span class="no">ApplicationRecord</span>
<span class="n">belongs_to</span> <span class="ss">:user</span>
<span class="n">belongs_to</span> <span class="ss">:event</span>
<span class="c1"># define the class method Stamp.total_number</span>
<span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">total_number</span>
<span class="no">Stamp</span><span class="p">.</span><span class="nf">all</span><span class="p">.</span><span class="nf">count</span>
<span class="k">end</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="class Stamp < ApplicationRecord
belongs_to :user
belongs_to :event
# define the class method Stamp.total_number
def self.total_number
Stamp.all.count
end
">Copy</button>
</div>
<p>Now the unit test is green, there is no need to refactor,
and we are done on the level of unit tests.</p></section>
<section><a class='slide_break' href='issue.html#slide-21'>▻</a>
<h3 id="example-back-to-the-system-test-cycle"><a class="anchorlink" href="#example-back-to-the-system-test-cycle"><span>5.5</span> Example: Back to the System Test Cycle</a></h3><p>We pop back up to the system test level, where the
test is still red. To display the result of our method,
we still have to call the method in the controller, set an instance variable
that is available in the view, and then display the value in the view:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="k">class</span> <span class="nc">PagesController</span> <span class="o"><</span> <span class="no">ApplicationController</span>
<span class="k">def</span> <span class="nf">admin</span>
<span class="vi">@number_of_stamps</span> <span class="o">=</span> <span class="no">Stamp</span><span class="p">.</span><span class="nf">total_number</span>
<span class="k">end</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="class PagesController < ApplicationController
def admin
@number_of_stamps = Stamp.total_number
end
">Copy</button>
</div>
<div class="interstitial code">
<pre><code class="highlight plaintext"><h1>Admin Dashboard</h1>
<p><%= @number_of_stamps %> stamps in total.</p>
</code></pre>
<button class="clipboard-button" data-clipboard-text="<h1>Admin Dashboard</h1>
<p><%= @number_of_stamps %> stamps in total.</p>
">Copy</button>
</div>
<p>Now the system test is green.</p></section>
<section><a class='slide_break' href='issue.html#slide-22'>▻</a>
<h2 id="git-workflow-end"><a class="anchorlink" href="#git-workflow-end"><span>6</span> Git Workflow: End</a></h2><p>Let's have a look what happens after we are done with Test Driven Development: all systems test pass, all commits have been pushed to the merge request - wo we have no reached step 3:</p>
<ol>
<li>after picking the issue the developer creates a feature branch and a merge request. the merge request is set to "draft" status.</li>
<li>then the issue is implemented (details unclear on this level)</li>
<li> when the developer thinks the implementation is done, they remove the "draft" status</li>
<li>now another developer does a code review</li>
<li> if changes are needed we go back to step 2.</li>
<li>now the feature branch can be deployed to the staging server, where it is available for manual testing.</li>
<li>if problems are found in manual testing we go back to step 2.</li>
<li>if the implementation is accepted the feature branch kann be merged and deployed to production</li>
</ol>
<p><img src="images/issue-workflow-with-git.svg" alt=""></p></section>
<section><a class='slide_break' href='issue.html#slide-23'>▻</a>
<h3 id="example-rebase"><a class="anchorlink" href="#example-rebase"><span>6.1</span> Example: Rebase</a></h3><p>In a large team many feature branches are open in parallell. When you first start your feature branch (called "a3" in this diagram), you are up-to-date with main. You start working, and add commits to your own branch:</p><p><img src="images/git_branch.svg" alt=""></p><p>When you are ready to merge, it might be that main has moved on: Other features were finished and merged into main, and now main has new commits on it:</p><p><img src="images/git_branches.svg" alt=""></p><p>In this case you need to <code>rebase</code> your branch:</p><div class="interstitial code">
<pre><code class="highlight shell"><span class="nb">git </span>checkout a3
<span class="nb">git </span>pull origin a3 <span class="c"># most current version of your feature branch</span>
<span class="nb">git </span>rebase main
<span class="c"># fix problems, run test, fix problems again</span>
<span class="nb">git </span>push <span class="nt">-force-with-lease</span> origin a3 <span class="c"># overwrite branch with rebased branch</span>
<span class="c"># work on your merge request</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="git checkout a3
git pull origin a3 # most current version of your feature branch
git rebase main
# fix problems, run test, fix problems again
git push -force-with-lease origin a3 # overwrite branch with rebased branch
# work on your merge request
">Copy</button>
</div>
<p>The result is that your commits are modified, so that the happend after the commits on main:</p><p><img src="images/git_rebase.svg" alt=""></p><p>Now your branch can be merged without problems.</p></section>
<section><a class='slide_break' href='issue.html#slide-24'>▻</a>
<h3 id="example-remove-draft-status"><a class="anchorlink" href="#example-remove-draft-status"><span>6.2</span> Example: Remove Draft Status</a></h3><p>After the implementation, it's a good idea to have a look at the issue again, and to make sure we covered everything described there.</p><p>We should also look at the changes in our merge request, and make sure that we did not include unneccessary changes, like these extra empty lines:</p><p><img src="images/merge-request-whitespace@2x.png" alt=""></p><p>This is easy to fix: just remove them in your local code, commit and push again.</p><p>Then we can remove the "draft" checkbox on the merge request.</p></section>
<section><a class='slide_break' href='issue.html#slide-25'>▻</a>
<h3 id="example-code-review"><a class="anchorlink" href="#example-code-review"><span>6.3</span> Example: Code Review</a></h3><p>Now a differente developer in our team will do a code review of our code. Using git makes this very easy: They can see just the changes we made to the code, and don't have to search around in the whole codebase.</p><p>They can add comments directly in the code:</p><p><img src="images/code_review_comment@2x.png" alt=""></p><p>It appears we neglected to implement the third system test on the list. Good that it was caught in the code review! Now we go back to implementation again to fix this.</p><p>(this part is not included in the writeup here)</p></section>
<section><a class='slide_break' href='issue.html#slide-26'>▻</a>
<h3 id="example-deploy-to-staging"><a class="anchorlink" href="#example-deploy-to-staging"><span>6.4</span> Example: Deploy to Staging</a></h3><p>After the successfull Code Review we are ready to deploy the feature branch to staging.
When using dokku, this is done with git on the command line:</p><div class="interstitial code">
<pre><code class="highlight plaintext">git push dokku-staging feature_dashboard_issue_3
</code></pre>
<button class="clipboard-button" data-clipboard-text="git push dokku-staging feature_dashboard_issue_3
">Copy</button>
</div>
<p>In the staging environment the feature can now be tested not just by developers, but also by other team members, like designers, product owners, or the customers.</p></section>
<section><a class='slide_break' href='issue.html#slide-27'>▻</a>
<h2 id="the-issue-lifecycle-end"><a class="anchorlink" href="#the-issue-lifecycle-end"><span>7</span> The Issue Lifecycle: End</a></h2><p>We have now reached step 5 in the Issue Lifecycle: in the staging environment, the implementation can now be tested in the browser.</p>
<ol>
<li>Define the Issue: The problem (bug) or new requirement (feature/story) is given a clear description</li>
<li>A developer picks up the issue</li>
<li>Somhow it is implmented (details unclear on this level)</li>
<li>Somehow it is prepared for manual testing (details unclear on this level)</li>
<li>Now the implementation can be tested in the browser, and compared to the issue</li>
<li>If the implmentation is accepted the issue is closed, otherwise it's back to step 2</li>
</ol>
<p><img src="images/issue-workflow-level-1.svg" alt=""></p><p>If the implementation is accepted as a good solution to the issue, it can be deployed to production.</p></section>
<section><a class='slide_break' href='issue.html#slide-28'>▻</a>
<h2 id="cycles-within-cycles"><a class="anchorlink" href="#cycles-within-cycles"><span>8</span> Cycles within Cycles</a></h2><p>This is a simple version of an iterative software development process.</p><p>When you work in existing code bases, in a project that regularly deploys new features, you will use a variation of this process.</p><p>Each team, each company will also have their own conventions and standards. There might be a conventions for branch names, for commit messages, for the test coverage needed for a feature to be finished, and so on.</p></div></section>
</div>
</div>
<!-- End slides. -->
<!-- Required JS files. -->
<script src="javascripts/reveal.js"></script>
<script src="javascripts/search.js"></script>
<script src="javascripts/markdown.js"></script>
<script>
// Also available as an ES module, see:
// https://revealjs.com/initialization/
Reveal.initialize({
controls: false,
progress: true,
center: false,
hash: true,
// The "normal" size of the presentation, aspect ratio will
// be preserved when the presentation is scaled to fit different
// resolutions. Can be specified using percentage units.
width: 1000,
height: 600,
disableLayout: false,
// Factor of the display size that should remain empty around
// the content
margin: 0.05,
// Bounds for smallest/largest possible scale to apply to content
minScale: 0.2,
maxScale: 10.0,
keyboard: {
27: () => {
// do something custom when ESC is pressed
var new_url = window.location.pathname.replace('slides_', '') + window.location.hash.replace('/','slide-');
window.location = new_url;
},
191: 'toggleHelp',
13: 'next', // go to the next slide when the ENTER key is pressed
},
// Learn about plugins: https://revealjs.com/plugins/
plugins: [ RevealSearch, RevealMarkdown ]
});
</script>
</body>
</html>