TDD in the eyes of a simpleminded: Hold. Rethink. Move on.

After talking about testing inner behavior inside a method, it is time to make some observations about the code and answer a lot of *why* questions. I must give credit to Shani, one of a great bunch of people I worked with during my service and a very good friend of mine. He asked me some hard questions that bother every TDD newbie or a TDD wannabee as myself (raise your hand if you’re one of them.. higher!). Now I’ll take my shot and reply in detail.


Question 1: Is it worth spending time on tests for simple methods ?


I’ve talked about “code little, think and code little” and why we need it in my last post “TDD in the eyes of a simpleminded: Part 1 – The NameResolver”. One of the great comments I received was “You spent more time on the tests than on the (simple)method itself. Isn’t it time consuming? You’ve just talked about focus on deadlines in your preface..”. From my experience, the easy, 5 minutes, methods are the one you spend most the time thinking about(relatively). You keep saying to yourself “this is so easy… such a shame” and spend a full hour thinking about a various of problems that can happen and various solution to handle them: ” Maybe I’ll use array? no… maybe List<int>? no… Oh! Yes! I’ll use an interface! I’ll call it IMyCollection and make a nice method in it named “AISolveProblem(params object[] anyThing)” “. This all process take place before you even write your first line of code! It’s a 5 minutes, even-a-lame-monkey-can-do-it method. You don’t want to look stupid and leave “open holes” right? So you waste a great deal of time over-thinking the irrelevant. Classic.


Sure, TDD can be costly (depends on a few variables), but it makes you code along the challenges(tests) and always challenge you to come up with the simplest solution. minimum effort. It prevents you from thinking too much upfront; You don’t have to think too much about avoiding potential bugs, just write test cases and make them green. Don’t have red tests ? Your code is ready for production. Again, I’m not going to address the big questions like “when, how and how much?” at the moment and I’m not saying that you should TDD the entire application. I’m just stating a point here. Keep your head open for a new way of coding. That’s the best I can request from you and that is what I’m doing now, during the learning process with Roy.


Question 2: Changing the class because of our tests, is that a good thing ?


I think that if the time comes and you need to change the way your class looks or behaves just because your tests become hard to write, you should lay back and smile. That means that the process of TDD just helped you design your code better. If the test was too hard to write, you must be doing some uncomfortable API (usability warning) or maybe you’ve put too much knowledge in one place and created a GOD(do-it-all) method\class. Having 5+ dependencies in one class should make you stop and rethink about the way you work right? testing actually makes you write the API before you actually write the implementation itself. If the early API sketching is too hard, just think about the poor programmer which will have to work with your API, about yourself (if it’s hard to test it must be hard to write) and even better – the miserable programmer which will have to take over your code and maintain it later on.


Question 3: Injecting concrete implementation can lead to incorrect usages! Why do we need it?


Let me start by relaxing you – injecting concrete implementation of an interface *can* lead to serious bugs and malfunctions in your application. Let me repeat it. By providing the programmer the ability to “inject” his implementation for the class dependencies he can cause the application to die. It might even cost you your job.


Relaxed?


No? But you should be! I’ve just demonstrated the worst case scenario and if we’ll think about it together, the world is not over and you and I still got our jobs. Programmers can make mistakes while using any API. Damn it, I’ve asked my boss for hiring machines but he keeps bringing people who actually use their head. You see, I don’t trust the “next guy” no more than you do, but I realize that I can take care of the low-level (in Hebrew: BARZELIM) API and still provide the high-level API which will help the idiot(brighter than me) next guy a simple method for any complex scenario. We need an option to inject our dependencies as we don’t want to couple our classes together.


We want the option to replace one concrete implementation with another (fake one, for example). This doesn’t prevent me from developing some nice & simple API that will wrap the all thing up and inject the dependencies I want.


Question 4: Writing tests can lead to bugs in those tests? should I write tests for my test?


Sure you do! you have to test your tests, and then test those tests and than test the latter one just until you will run out of RAM, space on your HD or energy.


Writing code can lead to additional bugs to your system just as living can cause death(No research shows it does by the way). We can minimize the chance of bugs in our tests by following some simple rules:



  1. The test should be obvious by it’s name (I follows Roy’s tip: MethodName_State_ExpectedResult).
  2. A test should check one method only.
  3. A test should Assert only once.
  4. Writing new tests should be easy and take no more than a few lines (few != 50).

I wrote “should” and not “must” as we should eat healthy food, it’s not a must (unless you eat burgers 24-7, in that case, you are a goner). You should come up with the rules that fit you and your team. You can enforce them to keep a follow a few rules just to create a standard you all(or just you, no one said we are living in a democracy) agree on.

 

Oren Ellenbogen

 

2 thoughts on “TDD in the eyes of a simpleminded: Hold. Rethink. Move on.

  1. Oren,
    The truth of the matter is that for Q2 and Q3 using an interface is just one way to ISOLATE your code to become testable.

    And on some occasions it can lead to code that has many interfaces that are 1:1 copies of their classes, leading to double maintainance and hard to follow code. (Not really YAGNI http://en.wikipedia.org/wiki/YAGNI)
    see discussion at http://www.blogitek.com/timhaughton/archives/2006/02/pondering_mocks.html

    The other solution is to define what interfaces you need and to refactor your code to use interfaces when required, and use other ways to isolate your code.

  2. Hey John,

    You are right, isolate a class and create an interface _can_ cause to 1:1 interfaces (copies of their classes) but if you’re doing that as part of TDD, it’s something you should still consider as you would like to inject a dummy class that implement this interface (therfore, 1:1 will not exist. Minimum of 2:1 is garunteed).

    If this class, that you should "interface-out", has only one method at the moment – create a virtual method inside the tested class and create a "testable" class that inherit from this class and override the method to return the required result. If additional method is required, it will be a good time to refactor it out and use interface.

    You point is really important and I’m going to address it very soon so thanks for your comment.

Comments are closed.