Question

I like working in languages with static types, because I like using types as a tool for designing an API before I start coding it.

I also like TDD, because it helps me concentrate on working in small steps to ensure I get consistent results.

But when I combine the two approaches, I often have this problem: I design the type of an API, but before I write unit tests for part of the functionality I find I must implement it because otherwise the compiler complains about the methods being incorrectly typed. For example, in a Java project, I have the following class:

 public class TransformedModelObserver<O,S>
 {
       private O sourceModel;
       private Function<O,S> transform;
       // note: a ChangeNotification<S> is a class that can only be constructed with a non-null instance of S
       private Consumer<ChangeNotification<S>> receiver;

       // ....

       /** Should call the receiver if and only if the source model change
        *  is visible in the transformed model.
        */
       public void notifySourceModelChanged ()
       {

       }
 }

I can simplify the test by using an identity function for the transform, which would allow for an easy first step, but the compiler complains if I don't call it anyway. So how would I work to implement this method in small test-driven steps in this scenario?

Was it helpful?

Solution

Assuming that notifySourceModelChanged only gets called if sourceModel has actually changed (so, no filtering of unchanged model prior to transformation needed), then I can see these small steps to implement the function.

  1. Test that a changed model (with a transformation that doesn't hide any changes) always notifies receiver: Call receiver with a newly created S.
  2. Test that the model passed to receiver corresponds to the sourceModel after transformation: Replace the newly created S by one obtained from transform. Testcase uses an identity transformation for easy verification.
  3. Test that the receiver does not get notified if the transformation hides the change.

As you can see, I work around the type difference between O and S by breaking the correspondence between the models in the first (few?) testcase(s).

OTHER TIPS

I often take a similar approach when developing for .Net, which provides a NotImplementedException. I just throw it from any method that TDD hasn't forced me to implement yet. This keeps my tests failing after I get it compiling. I'm not sure if Java provides a similar exception class, but it certainly wouldn't be hard to create one.

Just a note about taking this approach to development: Be careful not to let your preconceived design take precedence over what your tests & code are telling you. If you feel like you're fighting the predesigned API, change it accordingly. The main benefit to your approach is that you get to think about your design more than either an up front design or TDD on their own. Don't lose that benefit because you're being stubborn.

Don't start with types. Start with the test. Even write the Assert first, if you feel it allows you to hold back implementation a little longer.

The compiler will only complain at the Act step, when you begin using the System Under Test and it doesn't exist yet. Then most IDEs allow you to create class and method stubs on a simple keyboard shortcut.

In most cases you should be able to please the compiler with a simple UnsupportedException or return null in the generated method stub. Then you have a complete, compiling test before starting any real implementation of the inside of your method.

Licensed under: CC-BY-SA with attribution
scroll top