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: chapter_27_hot_lava.asciidoc
+45-6Lines changed: 45 additions & 6 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -11,7 +11,8 @@ and the end of our journey with this to-do app and its tests.
11
11
Let's recap our test structure so far:
12
12
13
13
* We have a suite of functional tests that use Selenium to test that the whole app really works.
14
-
On several occasions, the FTs have saved us from shipping broken code—whether it was broken CSS, a broken database due to filesystem permissions, or broken email integration.
14
+
On several occasions, the FTs have saved us from shipping broken code--whether it was broken CSS,
15
+
a broken database due to filesystem permissions, or broken email integration.
15
16
16
17
* And we have a suite of unit tests that use Django test client, enabling us to test-drive our code for models, forms, views, URLs, and even (to some extent) templates.
17
18
They've enabled us to build the app incrementally, to refactor with confidence,
including all dependencies and connected external systems.
251
252
An FT is the ultimate test that it all hangs together,
252
253
and that things are "really" going to work.
253
-
254
+
// CSANAD: I find the expression 'things are "really" going to work' too vague.
255
+
// I would rather mention User Stories here, since they very often can be turned into
256
+
// functional/end-to-end tests: they are worded similarly and they both cover specific
257
+
// functionalities that are valuable for a given user (edit: well, you talk
258
+
// about this below, under "On Acceptance Tests").
259
+
// Furthermore, I would maybe give an example for each:
260
+
// "Francis starts a new list by entering a new item."
254
261
255
262
Integration tests::
256
263
The purpose of an integration test should be to check that the code
257
264
you write is integrated correctly with some "external" system or dependency.((("integration tests")))
265
+
// CSANAD: this one is more tricky to find integration tests for, since we
266
+
// didn't create separate 'integration tests'. Maybe an example could be
267
+
// checking whether Bootstrap is loaded correctly, or perhaps the email.
268
+
// Something like that would be helpful in my opinion, especially because
269
+
// we promised in Chapter 05 ("Unit Tests Versus Integration Tests, and the Database")
270
+
// that we would further clarify the difference.
258
271
259
272
260
273
(True) unit tests::
@@ -263,10 +276,16 @@ Integration tests::
263
276
The ideal unit test is fully isolated((("unit tests")))
264
277
from everything external to the unit under test,
265
278
such that changes to things outside cannot break the test.
279
+
// CSANAD: I was trying to find an example of a pure unit test. I recall
280
+
// we may have had some helper function at some point, for which there was
281
+
// no need to use Django's TestCase but I can't find it. Maybe I'm
282
+
// remembering wrong.
266
283
267
284
The canonical advice is that you should aim to have the majority of your tests
268
285
be unit tests, with a smaller number of integration tests,
269
286
and an even smaller number of functional tests—as in the classic "test pyramid" of <<test_pyramid>>.
287
+
// CSANAD: in the HTML, it read: "as in the classic 'Test Pyramid' of The Test Pyramid".
288
+
270
289
271
290
[[test_pyramid]]
272
291
.The test pyramid
@@ -291,6 +310,10 @@ Top layer: a minimal set of functional/end-to-end tests::
291
310
But because they are the slowest and most brittle,
292
311
we want as few of them as possible.
293
312
313
+
// CSANAD: I think explaining the layers after having explained the types of
314
+
// tests just above it, seems a little redundant. I wonder if we should combine
315
+
// them.
316
+
294
317
295
318
[[acceptance_tests]]
296
319
.On Acceptance Tests
@@ -338,6 +361,8 @@ Ed Jung calls this https://oreil.ly/sm16H[Mock Hell].
338
361
This isn't to say that mocks are always bad!
339
362
But just that, from experience,
340
363
attempting to use them as your primary tool for decoupling
364
+
// CSANAD: I think we could actually argue that by using mocks, we
365
+
// accept that the code is tightly coupled with its dependencies.
341
366
your tests from external dependencies is not a viable solution;
342
367
it carries costs that often outweigh the benefits.
343
368
@@ -350,8 +375,9 @@ NOTE: I'm glossing over the use of mocks in a London-school
350
375
351
376
The actual solution to the problem isn't obvious from where we're standing. It lies in rethinking the architecture of our application.((("architectures of applications")))
352
377
In brief, if we can _decouple_ the core business logic of our application
353
-
from its dependencies, then we can write true unit tests for it
354
-
that do not depend on those, um, dependencies.((("business logic, decoupling from dependencies")))((("dependencies", "decoupling business logic from")))
378
+
from its dependencies, then we can write true, isolated unit tests for it
379
+
that do not depend on those, um, dependencies.
380
+
((("business logic, decoupling from dependencies")))((("dependencies", "decoupling business logic from")))
355
381
356
382
Integration tests are most necessary at the _boundaries_ of a system--at
357
383
the points where our code integrates with external systems—like the database, filesystem, network, or a UI.((("boundaries between system components", "integration tests and")))
@@ -389,7 +415,14 @@ call this approach "Ports and Adapters" (see <<ports-and-adapters>>).
389
415
.Ports and Adapters (diagram by Nat Pryce)
390
416
image::images/tdd3_2702.png["Illustration of ports and adapaters architecture, with isolated core and integration points"]
391
417
392
-
This pattern, or variations of it, are known as
418
+
419
+
// CSANAD: I haven't found the original diagram by Nat Pryce. I would recommend
420
+
// maybe a making the next header "Functional Core, Imperative Shell" formatted
421
+
// differently, making it more obvious that it's an explanation of the diagram.
422
+
// Or, we could just add a "Legend" under the diagram, explaining what the
423
+
// nodes, arrows and different shades of the layers depict.
424
+
425
+
This pattern, or variations on it, are known as
393
426
"Hexagonal Architecture" (by Alistair Cockburn),
394
427
"Clean Architecture" (by Robert C. Martin, aka Uncle Bob),
395
428
or "Onion Architecture" (by Jeffrey Palermo).
@@ -535,7 +568,7 @@ What that means is that some of the more advanced uses of TDD,
535
568
particularly the interplay between testing and architecture,
536
569
have been beyond the scope of this book.
537
570
538
-
But I hope that this chapter has been a bit a guide to find your way
571
+
But I hope that this chapter has been a bit of a guide to find your way
539
572
around that topic as your career progresses.
540
573
541
574
[role="pagebreak-before less_space"]
@@ -589,3 +622,9 @@ A take from the world of functional programming::
589
622
_Grokking Simplicity_ by Eric Normand
590
623
explores the idea of "Functional Core, Imperative Shell".
591
624
Don't worry; you don't need a crazy functional programming language like Haskell or Clojure to understand it—it's written in perfectly sensible JavaScript.
625
+
// CSANAD: Shouldn't we provide a link to this book too?
0 commit comments