--- disqus: pierodibello --- ###### tags: `clean code` `TDD` # My personal notes on Kent Beck's TDD course In 2010 Kent Beck, the inventor of Test-Driven Development, recorded a course in the "Pragmatic Screencasts" series. After ten years, I had the chance to follow the four-set videos, finally! In the 2-hours session, Kent develops a small Java client for the Tyrant DB (a sort of very efficient and simple key-value store). While watching Kent's screencasts, I took some notes and made a couple of reflections, that I want to share with you. I tried to put every Kent sentence as a quote, to be more understandable. Otherwise, I tried to express my understanding of his recommendations. ### The test list :ballot_box_with_check: > First step in TDD for me is always **a list of the tests** that I want to have passing. Keep a TODO list, put things there as new ideas or issues arise. ![](https://i.imgur.com/sDi1VqJ.png) ### Looking for the fastest feedback possible :fast_forward: In the beginning especially, look for the fastest feedback going end to end, as quickly as possible. ### Tests should tell stories :astonished: > **Test names** should tell little stories. A test should tell a story! ### How big is the step we are willing to take? Small steps, small tests. > If I make a long step and I fail, I have a cascade of decisions to review: which one was wrong? ### *TDD as if you meant it...* He writes the first implementation in the very same test, like the "TDD as if you meant it" approach (https://cumulative-hypotheses.org/2011/08/30/tdd-as-if-you-meant-it/)... ![](https://i.imgur.com/zjCu3Ph.png) ...then extract methods to have a shorter and clearer test, then move the methods to an actual application class. ![](https://i.imgur.com/C4nWdG5.jpg) Sometimes he puts a TODO comment in the code to not forget an idea. ### Isolated Tests :football: > Isolated tests: tests that leave the world the same way that they found it. :::warning "isolated test" means something very different to different people! ::: See https://medium.com/@kentbeck_7670/test-desiderata-94150638a4b3 ![](https://i.imgur.com/Uyszs1Q.png) > **Isolated** — tests should return the same results regardless of the order in which they are run. {%youtube HApI2cspQus %} Saying "isolated tests" is like saying that they should be **deterministic**: I can execute the single test and it should pass, I can execute it in a group and it should still pass (and also the other should pass), and I can run them in random order and they should always pass. ### Each test should clean up at the end :green_heart: > Each test is responsible to leave the world in the same exact state as it found it. Each test should clean after its execution, bringing the world to the state it was before the test was run. Cleaning *after* and also *before* is not the right thing to do: every test should just clean *after*. He also writes tests that are already green, e.g. when he writes the test for the `Tyrant#clear` method. In the name of the test he puts the name of the operation under test, e.g. `removeRemovesKey` ### Guard clauses :guardsman: Kent explains this style in "Implementation Patterns"... ![](https://i.imgur.com/3ESKtga.png) ### Design :frame_with_picture: > I'm glad to do *a lot of design after the tests pass*. ### Fake it 'till you make it :cactus: > Sometimes I write a second test after the fake implementation, to "force" me to have a real implementation, other times I just slowly replace the fake implementation with a good one that keeps the tests passing. He seems to tolerate long methods more than I would... Also, he seems to delay some kind of refactorings * does not immediately extract all the constants for the Tyrant operation codes * keeps copying and pasting from test to test > Copy and paste is suspicious... but I want the green bar! ### How many test cases? > Should we write a test to verify that `clear` can clear a map even with two objects inside? We already have a test that verifies that clear works with just one element in the map... is it enough? > I make this kind of decision every single moment when I do TDD... > [...] I'm not testing Tyrant DB, I'm testing my client to Tyrant, so I don't feel I need this new test. ### Test case ordering :one: :two: :three: Reorder tests to move closer tests that belong to the same behavior. > ...a test class should read like a story ### Symmetry :crossed_swords: Symmetry is an important driver: Kent avoids to extract a common snippet of code because that would introduce asymmetry in some of the methods where the extraction would not be applicable! ### Look for frequent feedback :heartbeat: > When tests are red I want to do the least to have them green again, then do the design to make it clean! > Frequent feedback means I catch errors much sooner! Break down a big problem to have feedback sooner. ### Calling the shot > Before running tests, I call my shots: is this one gonna pass? ### Comparing TDD styles :us: :uk: In Kent's TDD style, you make the most of the design in the refactoring step, when your new test is green and so are the other tests. In other styles of TDD, for example Sandro Mancuso's, where there's a greater use of mocking, I noticed that you actually do the design (and take many design decisions) when writing the failing test, while you spend a little effort in designing the code while in the refactoring step. ### Slicing! :scissors: > Making a "big" failing test pass gives me lots of opportunities to make mistakes! The problem is too big to be solved quickly, to reach quickly the green bar. So, **how can we slice the problem to have faster feedback?** #### Botton-up :arrow_heading_up: I can adopt a **bottom-up style**, where I think of smaller operations that will be needed in the "complete" solution, and so I start testing those small operations, and then compose the solution out of these small components from the bottom-up. He adds a test for a more complex case (two elements in the map) and decides to delete the previous simpler case with just one element in the map. #### On testing private methods... :building_construction: :::warning He tests `#reset` e `#getNextKey`! But those methods should be **private**!! ::: After implementing the "complete" solution from the bottom-up, he **deletes** the tests of the (now) private methods (`#reset` and `#getNextKey`), which were useful to drive the implementation from the ground up, but now are no more useful. > ...these tests are no longer necessary...they're like the **scaffolding around a building**: you don't need them where you've finished. ### TDD error conditions and happy path Question: with TDD you only test the happy path? **No**, it's a choice: you can choose to only test happy path, but you can also choose to test error conditions (*"what if..."* conditions) Can I receive a `null` for this parameter? > If I was using this code internally and can be sure that `null` can never be passed, I would not write a null-check test. > If my code was going to be public though, and I open my code to callers that I cannot control, I cannot be sure a `null` could be passed, adding additional complexity makes sense! ### Incremental design > Gradually let the design get more and more differentiated See how he starts with an implementation in the test itself! Take a big task and break it into smaller tasks, to get intermediate and faster feedback. **Permutation of task order is relevant**: which tasks you do first makes a difference! - may give you feedback quicker - may allow to change the direction of design sooner > Be aware of when you're making the design decisions and how you make those decisions ### Kent Beck's TDD game * try to write some code with an order of design decisions, * then erase and start again * try with another order of design decisions * ... * until I find an order that is efficient and gives me lots of feedback ### Rhythm Rhythm is important for a developer. **TDD creates a rhythm**, at micro-scale, e.g. * write a test, make it pass * write a test, refactor, and make it pass * write a test, make it pass, and refactor * ... At macro-scale there's a rhythm too: * problem statement * development * cleanup This "shaping of time" happens at various scales: * every two hours * at the end of the day (take the last hour/half-hour to finish things) * at the end of the week * at the end of the month ... * ... like in storytelling. ### Do I want to write a test? _Should I write a test for this case?_ **Every test that you write is an expense** (both short-term and long-term) and needs to pay for itself in the feedback that you get from the test: - informational feedback - emotional feedback (_will it work?_) If you find those feedbacks valuable, go write the test! **Every test that you don't write is a risk** (both short-term and long-term) you are creating (a feedback you're not getting) Are you sure that the test will pass? The risk is then tiny! What if someone changes the implementation and accidentally breaks something that you decided not to test? ### TDD needs balance Every time you're writing tests you have * costs involved (both short-term and long-term) * benefits involved (both short-term and long-term) you need to be balancing between all that. * **Management tasks**: choosing which tests to write and in which order * **Design tasks**: choosing which design decisions to make and in which order is what makes TDD so difficult, needs a maturation process. ## Other reviews of the same course * https://www.dapengli.com/2015/01/03/kent-beck-tdd/ * https://markhneedham.com/blog/2010/07/28/kent-becks-test-driven-development-screencasts/