• Post Reply Bookmark Topic Watch Topic
  • New Topic
programming forums Java Mobile Certification Databases Caching Books Engineering Micro Controllers OS Languages Paradigms IDEs Build Tools Frameworks Application Servers Open Source This Site Careers Other Pie Elite all forums
this forum made possible by our volunteer staff, including ...
Marshals:
  • Campbell Ritchie
  • Jeanne Boyarsky
  • Ron McLeod
  • Paul Clapham
  • Liutauras Vilda
Sheriffs:
  • paul wheaton
  • Rob Spoor
  • Devaka Cooray
Saloon Keepers:
  • Stephan van Hulst
  • Tim Holloway
  • Carey Brown
  • Frits Walraven
  • Tim Moores
Bartenders:
  • Mikalai Zaikin

TDD heuristics

 
Sheriff
Posts: 17644
300
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Steven, do you have any rules of thumb or heuristics for doing TDD effectively?

One of mine is to always start with a rough idea of the direction you want to go. Do at least some high level design but avoid doing detailed design up front.

My analogy is if you're taking a trip, you don't just open the garage, back out, and then start driving wherever. If I'm taking a long trip, I'll normally have a general plan in my head. Say if I were driving from Columbus OH to Orlando FL, I'll have a good idea of which major highways I'm taking (I-71S, US-33S, I-77S, I-26E, I-95S, etc.).

Likewise, when doing TDD, I'll normally start out with a general idea of the main behaviors, entities, and interactions involved in the functionality I'm trying to create.
 
Author
Posts: 25
7
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Junilu,

Great question! I want to start by echoing your pattern. The first step is to have a goal. We need to understand what we are trying to build, and if we are going to implement this functionality through an existing path or carve out a new one. I like to start by whiteboarding how we envision the interaction between the parts of the system. This could be either a class diagram, a sequence diagram, or a rough architecture. Once we are aligned on what we are going to build. I keep a task list for the pairing session. These items are sorted in the order we are going to accomplish them.

As the pairing session continues, we stay on track by placing tasks that could lead us to a distraction at the end of the task list. I also try to break down tasks into granular steps that are easy to accomplish. For instance, build the UI is too fuzzy as a task list item, but Create form and Animate button click are better because it is easier to tell when they are complete. And the more we check off items, the more progress it feels like we are making.

The second rule of thumb I have is: refactor often. If you see a variable name that doesn't make sense, then don't hesitate to change it. Same goes for functions and classes.

The third rule of thumb I have is: start small and generalize. For those who have programmed before TDD, it is easy to just write the complete code you even though the tests don't require it. I like to write tests in an order starting from handling one element to many.



Writing tests in this order helps to keep things moving and that leads to my final piece of advice. Take small frequent steps. This will establish a rhythm to your work. When you feel like you are making progress, you will be able to focus longer and leave your day with a sense of accomplishment.
 
Junilu Lacar
Sheriff
Posts: 17644
300
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
One of hardest things to learn/coach about TDD is the thought process and the stringing together various concerns into one coherent driving force that keeps you moving in a good direction. All the things that have been mentioned so far are important but I find what is not captured very well is the conversation that happens when you're doing TDD.

I often cite Bob Martin's Bowling Game kata article where he pairs with Bob Koss. Another book I really like that captures a lot of the thought processes involved in TDD is Corey Haines' "Understanding the Four Rules of Simple Design" where he goes into great detail in describing his thought process for each design decision he makes while TDDing Conway's Game of Life.

Do you discuss design decisions in detail in your material?

For example, it's easy enough to give example test code like what you have above. Having a live project that people actually have to go do this themselves could certainly help people learn better but besides the mechanical typing out of the code, do you also discuss what went into the decision to write a test like sumIsZero_forFreeProducts() in the first place? It's one thing to have the finished product to review but it's a whole 'nother thing to start with a blank page and come up with the code yourself.
 
Steven Solomon
Author
Posts: 25
7
  • Likes 1
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

One of hardest things to learn/coach about TDD is the thought process and the stringing together various concerns into one coherent driving force that keeps you moving in a good direction. All the things that have been mentioned so far are important but I find what is not captured very well is the conversation that happens when you're doing TDD.



That is fair feedback. I didn't intend to provide those tests as an example of TDD done right but rather an example of how tests are written from smallest cardinality to largest. There are tons of micro-decisions that are visible in even the simplest test:



For instance the name of totalIsZero_forEmptyList is constructed that why due to my desire to express both the pre- and postcondition in the test name, the Cart.total method is a static method on a class in order to limit the introduction of state. Tiny decisions like this are made once, and replicated until they are found not to work, just like riding a bike you only have to think about pedaling the first few times.

Another piece of advice that is hidden here—and I am glad you highlighted this—is to not worry about making the Right Decision TM when you are writing code with TDD. Just make a decision about the structure of the code, and lean on refactoring to correct decisions that feel wonky.

This goes into your second question "Do you discuss design decisions in detail in your material?" Yes, I have several resources in the project that ask you to think through test naming, structure, and what to test at different times. My goal is that the learner is able to learn one skill at a time. Once the learner has tried their hand at an exercise, becomes stuck, or just wants feedback, they can reach out to the mentor—me—by submitting their code and opening a Pull Request in GitHub. Inside the PR, I can give them advice and have a conversation about what they should try next or which resource in the liveProject they should take another look at.

sumIsZero_forFreeProducts() is a bit of an odd case because it is a typo. The code I wrote above isn't from the project but just some sample code I drafted to illustrate a concept. I chose the cart total logic as the example because of the Shopping Basket kata that you shared.
 
Junilu Lacar
Sheriff
Posts: 17644
300
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Likes 1
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Steven Solomon wrote:tests are written from smallest cardinality to largest.

Yes; this is the same strategy  ZOMBIES follows (Zero One Many Boundaries Interfaces Exceptions Simple Scenarios).

There are tons of micro-decisions that are visible in even the simplest test:



For instance the name of totalIsZero_forEmptyList is constructed that why due to my desire to express both the pre- and postcondition in the test name, the Cart.total method is a static method on a class in order to limit the introduction of state. Tiny decisions like this are made once,


I tend to use underscores in lieu of spaces which is the style targeted by the JUnit 5 annotation ReplaceUnderscores display name generator.

As for the name choice, I avoid implementation details like List in the test name, preferring something that speaks to intent more. So I would refactor rename that to total_is_zero_for_empty_cart
 
Steven Solomon
Author
Posts: 25
7
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
I based this decision on cardinality of tests on proof by induction. I'll have to check out this Zombies approach  

With this style of test, I am attempting to highlight the postcondition as I think ti is the most important thing for the reader to see. In this case the postcondition is totalIsZero. If I am scanning a list of tests, I want to be able to quickly see at a glance whether I have a test for all of the scenarios I would expect. By shrinking the surface area of what readers need to comprehend, I can allow them to focus on what I want them to read more easily.


To your point about implementation details, I think this is a great conversation to have. In my opinion, names depend on the domain of the system and whether the design is more FP or OO. For this test I happened to write it in more of an FP way. Here there is no concept of a cart, it is just a list of products. In fact, I think the Cart is the thing that is misnamed, rather than the test. The total method could take any list of any Products and total them. The list could represent a wishlist, a set of products in a customer's cart, or a list of products in a bundle that we want the customer to buy together. This is the kind of discussion I want to bring to our pairing session.
 
Junilu Lacar
Sheriff
Posts: 17644
300
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Steven Solomon wrote:I based this decision on cardinality of tests on proof by induction. I'll have to check out this Zombies approach  


James Grenning ZOMBIES

If I am scanning a list of tests, I want to be able to quickly see at a glance whether I have a test for all of the scenarios I would expect. By shrinking the surface area of what readers need to comprehend, I can allow them to focus on what I want them to read more easily.


Yes, I take the same approach

To your point about implementation details, I think this is a great conversation to have... This is the kind of discussion I want to bring to our pairing session.


And exactly what I was hoping to get, too. Looking forward to it even more now.

In my opinion, names depend on the domain of the system and whether the design is more FP or OO. For this test I happened to write it in more of an FP way. Here there is no concept of a cart, it is just a list of products. In fact, I think the Cart is the thing that is misnamed, rather than the test. The total method could take any list of any Products and total them. The list could represent a wishlist, a set of products in a customer's cart, or a list of products in a bundle that we want the customer to buy together.


I find myself constantly repeating this question: "What story do you want your tests to tell?" How you write your tests depends on the answer to this question. For me, the story needs to be coherent and consistent. These are things I picked up from reading Corey Haines' "Understanding the Four Rules of Simple Design" referring to Kent Beck's Four Rules of Simple Design
 
Junilu Lacar
Sheriff
Posts: 17644
300
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Steven Solomon wrote:
Another piece of advice... is to not worry about making the Right Decision TM when you are writing code with TDD. Just make a decision about the structure of the code, and lean on refactoring to correct decisions that feel wonky.


Yes. I have some heuristics for detecting "decisions that feel wonky," too.

Basically, I go by other senses, because you can't literally smell code:

1. Temporal - wonkiness based on time: how long it takes to understand something, how long it's been since the tests ran, how long it takes to make the test pass, etc.

2. Auditory - wonkiness you can hear as you have a conversation and when you read the code out loud. If you hear yourself or others say things like "That doesn't sound right" then something is wonky.  If they try to tell you the story in a way that changes the meaning you had in your head, then something is wonky. This is where having conversations while doing TDD is extremely important. Many practitioners overlook this aspect of the technique and get caught up in the mechanics of Red-Green-Refactor.

Also, there's the things you don't hear. These are the most insidious causes of bugs and issues because they're often the assumptions that people tacitly use to add meaning to the code. If everyone has that tacit information, it can be a problem for newcomers to the code because now they have missing context that impedes their understanding. On the other hand,  if there are discrepancies in each person's assumptions and these are not brought out, then even more insidious bug/issues can arise from them.

3. Visual - wonkiness you can see, like too long, too many, too far apart. These are probably the easiest to detect but you have to know what you're looking for. Arrow code, Long method, Long parameter list, etc.
 
Marshal
Posts: 79177
377
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
This thread has been quoted in the JavaRanch Journal (May 2022 edition), which earns the OP a cow.
 
reply
    Bookmark Topic Watch Topic
  • New Topic