You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: presentation/slides.md
+174-5Lines changed: 174 additions & 5 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -14,7 +14,7 @@ type: Theoretical
14
14
15
15

16
16
17
-
<!-- In a poorly designed system, making a change feels like jumping off a cliff to avoid a tiger. -->
17
+
<!--Quote from "Working Effectively with Legacy Code": In a poorly designed system, making a change feels like jumping off a cliff to avoid a tiger. -->
18
18
19
19
---
20
20
layout: agenda
@@ -29,6 +29,8 @@ items:
29
29
- TDD
30
30
---
31
31
32
+
<!-- What – A Definition: Unit testing is the process of writing code to test the behavior and functionality of your system. -->
33
+
32
34
---
33
35
layout: quote-image
34
36
---
@@ -51,6 +53,8 @@ layout: quote-image
51
53
52
54

53
55
56
+
<!-- For example: Eiffel, Rust, Elixir -->
57
+
54
58
---
55
59
layout: section
56
60
---
@@ -78,6 +82,12 @@ layout: default
78
82
79
83
</v-clicks>
80
84
85
+
<!--
86
+
Setup Testing: Setup the project, add the dependencies (xUnit, Mockito, ...), and have at least one working UnitTest even if it's a dummy one. If the framework for UnitTesting is already there, it's so much easier for the developers to actually write some tests.
87
+
Configure CI: If the tests do not run on the CI and block the CI in case of issues - it's basically the same as not having a UnitTest suite at all.
88
+
Teach/Help: Many developers still don't have (a lot) of experience with UnitTesting. They may need help writing a test for a tricky part of code.
89
+
-->
90
+
81
91
---
82
92
layout: section
83
93
---
@@ -102,6 +112,16 @@ layout: default
102
112
103
113
</v-clicks>
104
114
115
+
<!--
116
+
Small continuous steps forward: When the going gets so tough that you are not making progress at all. One of the advantages of working in a TDD style.
117
+
Avoiding Regressions: After every change to the code, the test suite is run. You can also refactor without fear. (Working Effectively with Legacy Code)
118
+
Living Documentation: Weird rules are defined in the code. Also a way for new developers to get acquainted with the API surface. This is also a case AGAINST parameterized tests - they typically leave you hanging when they fail.
119
+
Quick Feedback Loop: If it takes 30min to run the test suite, developers will not bother running it locally. Avoid I/O: network, FileSystem, DB, ...
120
+
Fixing Bugs: Found a bug? Write tests for it and the test suite will catch regressions.
121
+
Thinking About Design: Adding UnitTests forces the developer to think about Design.
122
+
Pay More Later: Writing tests takes time. How much time is wasted? YES - Google was held captive by fear of change, until they made UnitTesting mandatory. The team needs to get over "the hump".
123
+
-->
124
+
105
125
---
106
126
layout: default
107
127
---
@@ -119,6 +139,14 @@ layout: default
119
139
120
140
</v-clicks>
121
141
142
+
<!--
143
+
Fast: 1/10th of a second
144
+
Independent or Isolated: Test sequence should not be important. Avoid tests that need to run after a certain test in order to setup the inputs correctly.
145
+
Repeatable: Do not depend on things that can change: the records in a database, relying on the current time, a certain file being on the FileSystem, ...
146
+
Self-Validating: Tests should succeed or fail without human interaction. Do not check console.logs manually.
147
+
Timely: Writing the tests later is more expensive because we're already less familiar with the problem and the code
148
+
-->
149
+
122
150
---
123
151
layout: default
124
152
---
@@ -135,18 +163,32 @@ layout: default
135
163
136
164
</v-clicks>
137
165
166
+
<!--
167
+
Business Logic: UnitTests can be your documentation for that Illogical Business Logic.
168
+
Legacy Code: Fixing one thing breaks another thing? All the time? UnitTests can be your friend.
169
+
Technical Frameworks: If you are introducing design to eliminate duplication. These "small frameworks" should be tested thoroughly.
170
+
"select isn't broken": Pragmatic Programmers tip: It is rare to find a bug in the OS, Compiler, Language framework libraries. Do NOT write tests for these things.
171
+
-->
172
+
138
173
---
139
174
layout: section
140
175
---
141
176
142
177
# 100% Coverage?
143
178
144
179
---
145
-
layout: content-image
180
+
layout: image-content
181
+
size: md
146
182
---
147
183
148
184
# 100% Coverage?
149
185
186
+
::image::
187
+
188
+

189
+
190
+
::content::
191
+
150
192
<v-clicks>
151
193
152
194
- Startup Code?
@@ -158,9 +200,14 @@ layout: content-image
158
200
159
201
</v-clicks>
160
202
161
-
::image::
162
-
163
-

203
+
<!--
204
+
Personal Choice - however is there much value in testing the following:
205
+
Startup Code: Do you want to test setting up your IOC container?
206
+
Trivial Code: Do you want to test constructors? Getters/Setters?
207
+
Branchless Code: If there are no if/switch branches - do you still want to test it?
208
+
Technical Code: Do you want to test your implementation of ILogger? EntityFramework EntityConfiguration?
209
+
One-time migrations: Do you want tests for a migration that will run only once?
<!-- Note that we are only talking about UnitTesting here. Other tests, like integration tests are also needed! -->
225
+
177
226
---
178
227
layout: default
179
228
---
@@ -200,6 +249,14 @@ layout: default
200
249
201
250
</v-clicks>
202
251
252
+
<!--
253
+
Happy Path: Have at least one test to cover the happy path where everything works entirely as expected.
254
+
Branches: If you have an "if": make sure there is a test covering all if/else statements.
255
+
Unhappy Paths: Also test that the software behaves as expected when things do go wrong. Validation failure, unexpected exceptions, short circuiting guard clauses, ...
256
+
Scenarios: If you know the test data / scenarios the Tester/FA is going to use, you can write those tests.
257
+
Boundaries: Boundary Value Analysis and Equivalence Partitioning.
Example 1: For 100% Code Coverage, 2 tests are needed. For 100% Branch Coverage, 4 tests are needed.
282
+
Example 2: Code Coverage: 1 test. Branch Coverage: 4 tests.
283
+
-->
284
+
222
285
---
223
286
layout: default
224
287
---
@@ -235,6 +298,12 @@ layout: default
235
298
236
299
</v-clicks>
237
300
301
+
<!--
302
+
Equivalence Partitioning: Example: we expect a Percentage between 0 and 100. An invalid low value (ex: -10), a correct value (ex: 20), an invalid high value (ex: 200).
303
+
Boundary Value Analysis: Instead of using semi-random values, we use values at the boundaries. The values -1 and 0, the values 100 and 101.
304
+
Edge Case Testing: Add tests for NULL, PositiveInfinity, NaN, ...
305
+
-->
306
+
238
307
---
239
308
layout: section
240
309
---
@@ -262,6 +331,13 @@ layout: default
262
331
263
332
</v-clicks>
264
333
334
+
<!--
335
+
Database: If you use a Db in a "UnitTest", you need to setup this Db before the test so that it is in a predictable state. If multiple tests are using the same db, they could interfere with each other.
336
+
Network Access: Some other service, endpoint, dns, ...
337
+
Rest Calls: Talk to some third party service to send email(s)
338
+
=> MOCKING
339
+
-->
340
+
265
341
---
266
342
layout: default
267
343
---
@@ -302,6 +378,11 @@ layout: default
302
378
303
379
</v-clicks>
304
380
381
+
<!--
382
+
State: When updating an entity, the audit fields LastModifiedBy and LastModifiedOn are properly updated. When doing a calculation, assert that the result returned is as expected.
383
+
Behavior: Verify that a method was (not) called, or called with specific arguments. Example: verify that an email is (not) sent, or that Repository.Save() is called.
384
+
-->
385
+
305
386
---
306
387
layout: default
307
388
size: size-sm
@@ -319,6 +400,15 @@ size: size-sm
319
400
320
401
</v-clicks>
321
402
403
+
<!--
404
+
Which one to use? WHO CARES? Use whatever makes most sense: do not use a mock for a DTO, just instantiate it.
405
+
Dummy: Could be "null" or a NullObject or a default value.
406
+
Fake: Example InMemoryDb.
407
+
Spy: How many times was the EmailService invoked?
408
+
Mock: Typically with a mocking framework (Mockito/Moq).
409
+
Sometimes also handy OUTSIDE of testing: the real implementation is not available yet, or the real implementation costs the company money.
410
+
-->
411
+
322
412
---
323
413
layout: default
324
414
---
@@ -329,6 +419,13 @@ layout: default
329
419
330
420
## Program against an interface, not an implementation
331
421
422
+
<!--
423
+
Inject interfaces for things that need to be mocked. Dependency Injection is your friend here.
424
+
Also: DateTimeProvider - writing UnitTests for code that does a GetCurrentDate() is hard, so we provide an interface so we can return a canned date value.
425
+
Strict Mock vs Non-Strict Mock: Strict will fail for anything that was not explicitly setup.
426
+
Messy Setup Code: If you're having a lot of mock setup, does everything need to be a mock, really?
427
+
-->
428
+
332
429
---
333
430
layout: default
334
431
size: size-xs
@@ -359,6 +456,13 @@ size: size-xs
359
456
</div>
360
457
</div>
361
458
459
+
<!--
460
+
Mockist: Watch out for "Tautological Tests". You want to test BEHAVIOR, not IMPLEMENTATION.
Naming Convention: One possibility is MethodName_StateUnderTest/Scenario_ExpectedBehavior. Ex: "IsValidFileName_validFile_returnsTrue"
526
+
Close to the code: If the UnitTests are "far" away from the code, developers are less inclined to write them. If the tests are right next to the code itself, the dev will be much more likely to add them. But expect strong push-back when you want to introduce this practice.
527
+
-->
528
+
413
529
---
414
530
layout: comparison
415
531
---
@@ -441,6 +557,13 @@ layout: comparison
441
557
Please don't add these three as a comment in each test
442
558
</div>
443
559
560
+
<!--
561
+
Arrange: setup the SUT (System Under Test), CUT (Code Under Test) by creating and setting up objects.
562
+
Act: act on an object - Invoke the method.
563
+
Assert: (and/or verify) that everything went as expected.
564
+
There was also "Record-And-Replay" but no one seems to be using that anymore.
Do not test things that do not happen. Do not test scenarios that are "illegal" for the business. Do not write branches that are only hit during UnitTesting.
591
+
Defect Insertion: Your test must be able to fail by changing the production code. If you cannot make the test fail by changing the code, it's not testing anything.
592
+
-->
593
+
466
594
---
467
595
layout: default
468
596
---
@@ -477,6 +605,12 @@ Are you testing what you think you are testing?
477
605
478
606
</div>
479
607
608
+
<!--
609
+
If you've only ever seen a test be "Green" - are you sure you are testing the thing you think you are testing?
610
+
Or are you falling back due to a GuardClause short circuit which accidentally results in the same Assertions being true?
611
+
Example: Testing a "RecordNotFound" results in an Exception but we don't actually get so far into the test because it crashes because the FeatureFlags object is null.
612
+
-->
613
+
480
614
---
481
615
layout: default
482
616
---
@@ -499,6 +633,13 @@ Avoid brittle tests
499
633
500
634
</div>
501
635
636
+
<!--
637
+
Are all your tests failing after any change made to the code? Are you validating too much? Only validate what you are testing.
638
+
When doing multiple assertions: consider SoftAssertions.
639
+
Is your API too volatile? Think about your API / Design. Perhaps you can test on a higher level where there is a more stable API? For example at a "Pinch Point" - a place where we can detect ALL effects of a code change.
640
+
A test should not have logic in itself: switch, if, else statements, foreach, for, while loops.
If you are comparing 2 (big) collections and the test fails because one collection contains 182 items and the other one 200 items - what does this mean?
663
+
Items 65 in the actual/expected collections differ - what does this mean?
664
+
-->
665
+
520
666
---
521
667
layout: section
522
668
---
@@ -543,6 +689,11 @@ To test code, we need to change it
543
689
544
690
</div>
545
691
692
+
<!--
693
+
Seams: Change the behavior of a program without changing the program. Virtual methods & Polymorphism. Inject different implementations of an interface. Preprocessing Seams (ex: ConditionalAttributes, Compiler Directives).
694
+
Sensing Variable: Introduce a variable that can be tested against.
The Refactor step is often indicated as "Remove Duplication". Logically TDD results in 100% coverage.
759
+
TDD can be used for the entire system OR take advantage of continuous small improvements when you are stuck on a difficult piece of code.
760
+
-->
761
+
604
762
---
605
763
layout: default
606
764
---
@@ -618,6 +776,10 @@ layout: default
618
776
619
777
</v-clicks>
620
778
779
+
<!--
780
+
Useless Tests: Personal opinion: if you like working TDD, go for it. If you don't like it: still consider using it when you are stuck and can't seem to make progress. But most importantly: not doing TDD does not mean you can skip the UnitTest suite entirely.
781
+
-->
782
+
621
783
---
622
784
layout: content-image
623
785
---
@@ -630,6 +792,11 @@ layout: content-image
630
792
631
793

632
794
795
+
<!--
796
+
The Cycle of Fear: The more stress you feel, the less testing you will do. The less testing you do, the more errors you'll make. The more errors you make, the more stress you feel...
797
+
Write tests until fear is transformed into boredom.
798
+
-->
799
+
633
800
---
634
801
layout: default
635
802
---
@@ -661,6 +828,8 @@ layout: quote
661
828
662
829

663
830
831
+
<!-- No matter how much testing is done on each level of the testing pyramid, no system is entirely bug free. -->
0 commit comments