Object Oriented Testable Designed – You must be out of your mind

Roy has posted a new design methodology: Object Oriented Programming that is Testable!

Roy and I are on the opposite sides of this methodology. I personally hate to change my production code just for the tests.
As Roy says “pure object oriented design does not go well hand in hand with the notion of testable design.” After creating a good design (without taking testability into account), there is no real business value of implementing hooks to change internal private members and to expose properties that should remain private by design! Unless you want to clutter your code with load of Totally Irrelevant Code

Please, Stop Designing for testability!!

There is no need to do this, using the right tools you can enjoy both worlds.
We all have have seen testable code. The code is hard to maintain and understand. A Testable code, will have many interfaces that have no real design value, that will just confuse developers. Have you ever tried to browse a code with loads of interfaces, it take ages because you have to keep finding the concrete implementation, and the place where the instance was created.

Think about this code smell: When you find that an interface and implementation have exactly the same public methods, it is a sign that the interface is not exposing a trait of the object but the Object itself. This is what happens when your code is Designed for Testability, you do have a safety net, but the design is flawed.

Using TypeMock.NET your code can be encapsulated (OOD) and Testable at the same time:

  • OOD: Do not expose any private members or methods that are not needed by the developer using your API .
  • Testable with TypeMock: Easy to replace private instances of objects without changing your code or exposing unnecessary parts
  • OOD: Seal classes to inheritance by default unless you explicitly mean for others to extend them
  • Testable with TypeMock: No need to extend and override your objects just to test them
  • OOD: Only make methods virtual explicitly when you want others to override them.
  • Testable with TypeMock: Keep your methods non virtual (its faster). you can still test the code.
  • OOD: Use singletons to make sure only one instance exists. do not allow anyone to touch that private instance.
  • Testable with TypeMock: Don’t allow others to replace Singleton instances, TypeMock will do it for you in your tests.
  • OOD: make types private or internal by default unless you want to expose them specifically.
  • Testable with TypeMock: Keep your types private so that you can hide the implementation, you can sill test the code
  • OOD: Don’t add unnecessary API’s
  • Testable with TypeMock: No need to clutter you production code with special API’s
  1. “When you find that an interface and implementation have exactly the same public methods, it is a sign that the interface is not exposing a trait of the object but the Object itself.”

    What if there is more than one implementation of that interface (in the production code)?

    I agree over-abstraction merely for testability is wrong but configuring runtime replacements of concrete classes for the same purpose sounds equally awkward…

  2. Yoni,
    I must agree that AOP does sound awkward, but more on that later.

    When there is more then 1 implementation of an interface and both have the same public methods as the interface – It is still a code smell.
    In most cases the code is not designed well. You will almost ONLY see this in ‘Testable Code’.

    An interface in a good design exposes a trait of an object, Take IList – when an object implements IList it has the trait of a list. When an object implements IEnumerable, you can iterate on the object. Notice that each interface is a trait that we add to our objects.
    Take ArrayList that implements IList, it has many more public members then IList. Notice that even Count is not a member of IList. This is a because IList is a Trait of a list and not just an Interface used to isolate and test the code.

    So basically as you say, the choice is:
    1. Create these interfaces, deliver them in your production code (over abstraction)
    2. Use tools that enable you to test your code without mucking with the design (automatic isolation by adding a testable aspect* to you code)

    And yes AOP is really weird…

    * aspect as in Aspect Oriented Design

  3. Let us consider System.Data.IDbConnection. When I create a gateway to my new database I would probably create a connection class which exposes that interface and requires no more public members in order to function as an ADO.NET provider, is this a code smell?

    An interface is an abstraction that should have different concrete implementations, this does not require the implementations to have any other public members.

    And, besides, if there is more than one implementaion then the code that uses the interface can be tested using a mock without designing for testability (consider code that operates on an IList).

  4. Yoni,
    That is a great example.
    Take a look at SQLConnection is has 3 time the amount of public API’s then the IDbConnection that it implements. OracleConnection has twice as many.

    I am sure that if you had to create your own Connection Class it would have to have more public methods then just the interface.

    So I still stand behind: “If your interface and concrete class have the same public methods, something is probably wrong with your design”.
    You might find having a base class is a better design or if think about the Traits that the object has you can extract those into interfaces.

    Of course if your production code REQUIRES using pluggable and changeable components, you will have to use some kind of Dependency Injection and then this will lead to the code being testable, but the business requirements should guide you NOT the testability.

    Making your code testable, is like creating a modular TV Set, with an opening at the back of the set that you can easily open (without screws) put your hand through, dislodge the Remote Receiver (which you can switch with a mock Remote and test the TV), same goes with the volume controls, speakers etc. etc.
    This is great and now you can sell the Modular TV Set to the world.
    The thing is that it is more expensive to make (in code this means that the code is more complex) and there is no real business value. Who wants to pay more for a modular TV set?
    Of course you can say that the TV set have been tested and this has value. But then with TypeMock you can do the same and lower the price of the TV set because you don’t have to make it modular.

  5. If you are familiar with the GoF design patterns it won’t come as a surprise that almost all of them are cases of classes that expose the same public interface as the interfaces they implement.

    Take a look here:
    (this site uses pure abstract base classes but that is exactly the same as using an interface).

  6. 🙂
    I understand that we differ here, this is of course my opinion, you may not agree and design differently.
    The GoF design patterns may have examples like you say, but these are examples, and in real life code you don’t normally just print text to the console.

    My point is: when you find classes that expose only the same methods as the interface they implement – Take a GOOD look at your design 🙂
    Your code might be over abstracted.

  7. “Have you ever tried to browse a code with loads of interfaces, it take ages because you have to keep finding the concrete implementation, and the place where the instance was created.”

    I’d suggest that that’s a problem with the tool. It _should_ give you a drop-down of the interface and all types that implement it in an intellisense-y (resharper-y) fashion.

  8. This is probably just me, but I consider mock based tests a design smell. You see, mocks and I are not friends. Each time I found a useful mocking framework, I failed.

    The root cause of my failure is my mock based tests verifies implementation and not behavior. That makes refactoring hard.

    Last time I gave up a third of my tests, because I lacked the energy to do the right thing. The tests broke due to internal implementation changes, even when external behavior was unchanged.

  9. Thanks for posting your ideas.
    JH, thanks for the pointer, I was really trying to point out that introducing IoC, lowers the maintainability of the code.

    Thomas, I understand where you are coming from, there is a lot of truth in what you say. You have to know when to use mocks.

  10. @ Eli

    The modular tv analagy would fit only if the tv owners were trying to add additional features or change existing features after the set was purchased. I don’t know many TV owners who try and upgrade their analog TV to support HiDef. Every application I’ve worked on, however, has gone through an equivalent large-scale feature upgrade.

    I would agree that concepts like IoC and dependency injection are code smells in an application which does not need to evolve with business requirements, but until I meet an entepreneur with a set-in-stone business plan, I will continue using my DI container and programming to interfaces.

    On the other hand, my view of this topic is only through the eyes of enterprise application development. If I were developing something else (say..a mail server or general purpose library), my opinion would probably be quite different.

  11. Evan,
    Thanks for your post,
    Perhaps the analogy wasn’t so good.
    The point is: Once a business requirement arises that required IoC, by all means do it. But not one second before.
    (As you say) We are not profits and we don’t know what future requirements will be needed. Lets do it the Agile way:
    Have a test bed, so that we can re-factor our code base easily and add features as they come. Do the Simplest thing that can work.
    Don’t start putting hooks, this is NOT the simplest thing that can work.

Comments are closed.