views:

49

answers:

2

Hi guys, following on from this question (Developing to an interface with TDD), I'm still having some issues.

I test-drove two classes into existence, both of which ended up shared some identical functionality. I refactored a common base class into existence, and all the tests still passed. So far, so tdd-tastic.

I needed a third class to implement the base class, so copied some tests into a new fixture and made each one compile and go green in turn, until I had a fully-functional third class. This approach can be debated, because if I didn't copy one test across correctly, or didn't change one successfully to support the new class, I'd be in trouble, but that's not the main problem.

The problem I have now is that I want to add functionality to the base class. It can't be instantiated on its own, so it will have to be through one of the leaf classes. However if I forget to copy the tests across to the other classes, I'll have unsupported functionality. It doesn't seem a very software-engineer-y way of doing things, and I wanted to know where I was going wrong.

Is this a problem with my design? Should I lay my tests out in a different way? Or am I worrying about nothing?

Thanks in advance.

+1  A: 

Even though you can't create an instance of your base class directly, you can still unit test it by deriving a test-specific subclass that can be tested.

Assume that you have an abstract class called MyBase. Obviously, you can't create an instance of MyBase directly, but in your unit test project, you can create a test-specific specialization of MyBase called TestableBase or something else.

So let's assume that you want to test something like this:

public class MyBase
{
    public abstract void DoStuffCore();

    public void DoStuff()
    {
        // Do something interesting first
        this.DoStuffCore();
    }
}

And you want to test that DoStuffCore was correctly invoked by DoStuff, you can create something like this:

public class Spy : MyBase
{
    public bool CoreInvoked { get; private set; }

    public override void DoStuffCore()
    {
        this.CoreInvoked = true;
    }
}

This would allow you to create a new instance of Spy and call its DoStuff method and then subsequently verify that the CoreInvoked property is true.

Mark Seemann
But this means testing private functionality, implementation details. A lot of tdd developers (me included) are of the opinion that tests should be performed on public APIs.
Virgil Dupras
@Virgil Dupras: Maybe my example is not the best, but it was never my intention that the DoStuffCore would symbolize anything private. It could be part of an extensibility mechanism - in which case it would be *very* relevant to test that it works as intended. Please note that the DoStuffCore method is public, but it might more reasonably have been protected.
Mark Seemann
@Mark: Your answer is exactly what I would have done. Surely Spy.DoStuffCore() can be private as it is only ever invoked by MyBase.DoStuff()? This indicates that your class Spy is just a mock.
quamrana
This is what I ended up doing, but I still am not happy with the results. If I had to refactor one of my leaf classes to derive from another class, I would lose functionality without the tests telling me so.
tenpn
@tenpn: You could write a test that asserts that your leaf class derives from the base class. Then you would know.
Mark Seemann
@mark - that's "implementation details": there's only a common base class because of common code. Also it doesn't enforce the behaviors. Someone could update that test and the behaviors would disappear. However I suppose if the behaviors aren't being used by some other tests, or app code, you're not going to miss them.
tenpn
@tenpn: In TDD, the distinction between 'feature' and 'implementation detail' can often get blurry. Normally, you should be creating a lot of classes and methods that are not directly dictated by any feature requirement. All of these types help opening the API for extensibility, but are fair game for redesign if you can think of a better way to implement something. You still want to test these things so that you know when you are breaking something.
Mark Seemann
A: 

One approach you could take is to organize your test units so you can re-use test code. This way, you don't have to copy/paste stuff around (copy/paste, even when it's for tests, is usually not something you want to do).

Or (what I do), you could just test one of your subclasses. I know, I know, it means that if you decide that one of your "partially covered" subclasses has to use another base class, or if that behavior you tested end up "bubbling up" to subclasses, you will have uncovered code.

However, this is the kind of things that seldom happen. Usually, when you refactor, you push down code to superclasses (to eliminate code duplication). When you do that kind of refactoring, you don't lose any coverage on the account of having only one of your subclasses tested for its superclass' behavior.

Therefore, I'm of the opinion that it's generally OK to test just one of your subclass in order to test a superclass' behavior.

Virgil Dupras