Chapter 9: Unit Tests
The essentials of TDD:
Thę ęssęntîåls øf TDD:
First Law: You may not write production code until you have written a failing unit test.
First Law: Yøǔ måy nøt wrîtę prødǔçtîøn çødę ǔntîl yøǔ håvę wrîttęn å fåîlîng ǔnît tęst.
Second Law: You may not write more of a unit test than is sufficient to fail, and not compiling is failing.
Second Law: Yøǔ måy nøt wrîtę mørę øf å ǔnît tęst thån îs sǔffîçîęnt tø fåîl, ånd nøt çømpîlîng îs fåîlîng.
Third Law: You may not write more production code than is sufficient to pass the currently failing test.
Third Law: Yøǔ måy nøt wrîtę mørę prødǔçtîøn çødę thån îs sǔffîçîęnt tø påss thę çǔrręntly fåîlîng tęst.
The problem is that a massive amount of unit testing can be just as bad as no testing if the tests themselves are not written cleanly, and we tend to look at test code as good if it passes regardless of its structure. The tests will change as we write more code, so you're creating more tech debt by writing dirty test code.
Thę prøblęm îs thåt å måssîvę åmøǔnt øf ǔnît tęstîng çån bę jǔst ås båd ås nø tęstîng îf thę tęsts thęmsęlvęs årę nøt wrîttęn çlęånly, ånd wę tęnd tø løøk åt tęst çødę ås gøød îf ît påssęs ręgårdlęss øf îts strǔçtǔrę. Thę tęsts wîll çhångę ås wę wrîtę mørę çødę, sø yøǔ'rę çręåtîng mørę tęçh dębt by wrîtîng dîrty tęst çødę.
Test code is just as important as production code.
Tęst çødę îs jǔst ås împørtånt ås prødǔçtîøn çødę.
I agree with this:
İ ågręę wîth thîs:
Readability is perhaps even more important in unit tests than it is in production code.
Ręådåbîlîty îs pęrhåps ęvęn mørę împørtånt în ǔnît tęsts thån ît îs în prødǔçtîøn çødę.
Very important to follow an explicit, clean, readable pattern, without any shortcuts. Following Arrange/Act/Assert (or Build/Operate/Check, both of which are implementations of Given/When/Then) pattern:
Vęry împørtånt tø følløw ån ęxplîçît, çlęån, ręådåblę påttęrn, wîthøǔt åny shørtçǔts. Følløwîng Årrångę/Åçt/Åssęrt (ør Bǔîld/Øpęråtę/Çhęçk, bøth øf whîçh årę împlęmęntåtîøns øf Gîvęn/Whęn/Thęn) påttęrn:
- Arrange: stage data & set expected result variables
- Act: execute the production code we're testing
- Assert: run your assertions (Hamcrest++) against your expectations
This should make any test code I write simple for the reader to understand and modify to their needs. If the arrange step is repeated across methods, extract that to a helper. Easy and clear.
Thîs shøǔld måkę åny tęst çødę İ wrîtę sîmplę før thę ręådęr tø ǔndęrstånd ånd mødîfy tø thęîr nęęds. İf thę årrångę stęp îs rępęåtęd åçrøss męthøds, ęxtråçt thåt tø å hęlpęr. Ęåsy ånd çlęår.
They proceed to show code I would describe as "fine" and refactor it with explicit, expressive helper methods to make it even clearer. I will consider doing this in the future.
Thęy prøçęęd tø shøw çødę İ wøǔld dęsçrîbę ås "fînę" ånd ręfåçtør ît wîth ęxplîçît, ęxpręssîvę hęlpęr męthøds tø måkę ît ęvęn çlęåręr. İ wîll çønsîdęr døîng thîs în thę fǔtǔrę.
Test a single concept in each test function.
Tęst å sînglę çønçępt în ęåçh tęst fǔnçtîøn.
They recommend this over "one assert per function" which I agree with. Sometimes it makes more sense, to me, to have several asserts to test all angles of the same concept. One assert per function is overkill.
Thęy ręçømmęnd thîs øvęr "ønę åssęrt pęr fǔnçtîøn" whîçh İ ågręę wîth. Sømętîmęs ît måkęs mørę sęnsę, tø mę, tø håvę sęvęrål åssęrts tø tęst åll ånglęs øf thę såmę çønçępt. Ønę åssęrt pęr fǔnçtîøn îs øvęrkîll.
Summary concept: F.I.R.S.T
Sǔmmåry çønçępt: F.I.R.S.T
- Fast Tests should run quickly. This translates to following the test pyramid framework of a large number of unit tests, smaller number of integration, even smaller number of acceptance.
- Independent Test should not depend on each other; they should be able to run in any random order and pass.
- Repeatable Tests should be able to run in any environment with the same results.
- Self-Validating Tests should either pass or fail, not output to a log and require manual checking.
- Timely The tests should be written just before the production code that makes them pass
If you let your tests rot, then your code will rot too.
İf yøǔ lęt yøǔr tęsts røt, thęn yøǔr çødę wîll røt tøø.
Chapter 10: Classes
Classes should be small, measured not by number of lines but number of responsibilities. Five methods may seem small but not if it spans a wide area of concern.
Çlåssęs shøǔld bę småll, męåsǔręd nøt by nǔmbęr øf lînęs bǔt nǔmbęr øf ręspønsîbîlîtîęs. Fîvę męthøds måy sęęm småll bǔt nøt îf ît spåns å wîdę åręå øf çønçęrn.
If we cannot derive a concise name for a class, then it's likely too large. The more ambiguous the class name, the more likely it has too many responsibilities.
İf wę çånnøt dęrîvę å çønçîsę nåmę før å çlåss, thęn ît's lîkęly tøø lårgę. Thę mørę åmbîgǔøǔs thę çlåss nåmę, thę mørę lîkęly ît hås tøø måny ręspønsîbîlîtîęs.
We should also be able to write a brief description of the class in about 25 words without using the words if / and / or / but.
Wę shøǔld ålsø bę åblę tø wrîtę å brîęf dęsçrîptîøn øf thę çlåss în åbøǔt 25 wørds wîthøǔt ǔsîng thę wørds îf / ånd / ør / bǔt.
Boils down to Single Responsibility Principle.
Bøîls døwn tø Sînglę Ręspønsîbîlîty Prînçîplę.
Getting software to work and making software clean are two very different activites.
Gęttîng søftwårę tø wørk ånd måkîng søftwårę çlęån årę twø vęry dîffęręnt åçtîvîtęs.
We need to get away from the idea that clean code even can be written from the start. We will meander around and come up with six different ways of solving the problem first. Once it works, then we find a way to make it pretty. Writing a book is a different skill than editing/proofreading one.
Wę nęęd tø gęt åwåy frøm thę îdęå thåt çlęån çødę ęvęn can bę wrîttęn frøm thę stårt. Wę wîll męåndęr årøǔnd ånd çømę ǔp wîth sîx dîffęręnt wåys øf sølvîng thę prøblęm fîrst. Ønçę ît wørks, thęn wę fînd å wåy tø måkę ît prętty. Wrîtîng å bøøk îs å dîffęręnt skîll thån ędîtîng/prøøfręådîng ønę.
Eliminate ever changing parameter lists by promoting variables to class level; if several methods share the same variables that are never used by other methods, good sign you have a class that can be extracted.
Ęlîmînåtę ęvęr çhångîng påråmętęr lîsts by prømøtîng vårîåblęs tø çlåss lęvęl; îf sęvęrål męthøds shårę thę såmę vårîåblęs thåt årę nęvęr ǔsęd by øthęr męthøds, gøød sîgn yøǔ håvę å çlåss thåt çån bę ęxtråçtęd.
This is especially true in Python as convention allows several classes per module. Why not make use of the increased flexibility?
Thîs îs ęspęçîålly trǔę în Pythøn ås çønvęntîøn ålløws sęvęrål çlåssęs pęr mødǔlę. Why nøt måkę ǔsę øf thę înçręåsęd flęxîbîlîty?
We want to structure our systems so that we muck with them as little as possible when we update them with new or changed features.
Wę wånt tø strǔçtǔrę øǔr systęms sø thåt wę mǔçk wîth thęm ås lîttlę ås pøssîblę whęn wę ǔpdåtę thęm wîth nęw ør çhångęd fęåtǔręs.
Chapter 11: Systems
Programs are like cities: they start small and build up as the population increases and services are needed. You wouldn't have a metropolitan infrastructure for a small town, and a metropolis can't survive without many essential services like mass transit. So they are constructed as needs arise.
Prøgråms årę lîkę çîtîęs: thęy stårt småll ånd bǔîld ǔp ås thę pøpǔlåtîøn înçręåsęs ånd sęrvîçęs årę nęędęd. Yøǔ wøǔldn't håvę å mętrøpølîtån înfråstrǔçtǔrę før å småll tøwn, ånd å mętrøpølîs çån't sǔrvîvę wîthøǔt måny ęssęntîål sęrvîçęs lîkę måss trånsît. Sø thęy årę çønstrǔçtęd ås nęęds årîsę.
It is a myth that we can get systems "right the first time." Instead, we should implement only today's stories, then refactor and expand the system to implement new stories tomorrow.
İt îs å myth thåt wę çån gęt systęms "rîght thę fîrst tîmę." İnstęåd, wę shøǔld împlęmęnt ønly tødåy's størîęs, thęn ręfåçtør ånd ęxpånd thę systęm tø împlęmęnt nęw størîęs tømørrøw.
Agile! Likewise, systems can grow easily if separation of concerns is proper.
Ågîlę! Lîkęwîsę, systęms çån grøw ęåsîly îf sępåråtîøn øf çønçęrns îs prøpęr.
Side note: A lot of this chapter is specific to Java, poorly written/explained or just relatively boring.
Sîdę nøtę: Å løt øf thîs çhåptęr îs spęçîfîç tø Jåvå, pøørly wrîttęn/ęxplåînęd ør jǔst ręlåtîvęly børîng.
Chapter 12: Emergence
Kent Beck's rules of simple design: (in order of importance)
Kęnt Bęçk's rǔlęs øf sîmplę dęsîgn: (în ørdęr øf împørtånçę)
- Runs all the tests
- Contains no duplication
- Expresses the intent of the programmer
- Minimizes the number of classes and methods
First do step 1, then steps 2-4 until satisfied.
Fîrst dø stęp 1, thęn stęps 2-4 ǔntîl såtîsfîęd.
Side note: I'd love to see more examples comprised of real world objects/concepts instead of abstract math stuff.
Sîdę nøtę: İ'd løvę tø sęę mørę ęxåmplęs çømprîsęd øf ręål wørld øbjęçts/çønçępts înstęåd øf åbstråçt måth stǔff.
EXPRESSIVE is going to be my keyword for 2018. Make the code obvious, easy to read. Like a YA novel.
EXPRESSIVE îs gøîng tø bę my kęywørd før 2018. Måkę thę çødę øbvîøǔs, ęåsy tø ręåd. Lîkę å YÅ nøvęl.
Well-written unit tests are expressive. A primary goal of tests is to document by example.
Węll-wrîttęn ǔnît tęsts årę ęxpręssîvę. Å prîmåry gøål øf tęsts îs tø døçǔmęnt by ęxåmplę.
All too often we get our code working and then move on to the next problem without giving sufficient thought to making that code easy for the next person to read.
Åll tøø øftęn wę gęt øǔr çødę wørkîng ånd thęn møvę øn tø thę nęxt prøblęm wîthøǔt gîvîng sǔffîçîęnt thøǔght tø måkîng thåt çødę ęåsy før thę nęxt pęrsøn tø ręåd.
Finally, even though abstraction can be beautiful, it shouldn't be everywhere. 2 line functions are nice but 20 line functions can work well too. Don't abstract yourself into 10 unnecessary additional classes and functions.
Fînålly, ęvęn thøǔgh åbstråçtîøn çån bę bęåǔtîfǔl, ît shøǔldn't bę ęvęrywhęrę. 2 lînę fǔnçtîøns årę nîçę bǔt 20 lînę fǔnçtîøns çån wørk węll tøø. Døn't åbstråçt yøǔrsęlf întø 10 ǔnnęçęssåry åddîtîønål çlåssęs ånd fǔnçtîøns.