views:

637

answers:

12

When you use composition, then you can mock the other objects from which your class-under-test depends, but when you use inheritance, you can't mock the base class. (Or can you?)

I generally try to prefer composition over inheritance, but sometimes inheritance really seems like the best tool for the job - well, at least until it comes to unit-testing.

So, how do you test inheritance? Or do you just trash it as untestable and use composition instead?

Note: I mostly use PHP and PHPUnit, so help on that side is most appreciated. But it would also be interesting to know if there are solutions to this problem in other languages.

+3  A: 

As long as you don't override public methods of the parent class, I don't see why you need to test them on all the subclasses of it. Unit test the methods on the parent class, and test only the new methods or the overriden ones on the subclasses.

gizmo
+1  A: 

Why should you mock the base class?

How can you create derived classes from a non-existent parent class?

Just test it as usual, but have the parent class.

I think you haven't told the whole story.

In addition, language features supposedly work (unless you're working with beta releases or so), so you don't need to test if the method actually exist in a derived class.

Vinko Vrsalovic
A: 

I don't see how it would be possible to mock a superclass. That would have to be a language feature ihmo. What exactly are you trying to test? To me it sounds like reflection could be helpful to you?

Robse
I also don't know how mocking a parent class would be possible. That's why I'm asking. But I don't think that it's impossible - very few things in computers are.
Rene Saarsoo
+2  A: 

The reason you use mock objects in composition is if the real objects do something you dont want to set up (like use sockets, serial ports, get user input, retrieve bulky data etc). You should always use real objects where possible. Mock objects are only for when the estimated effort to implement and maintain a test using a real object is greater than that to implement and maintain a test using a mock object. Your base class shouldnt be doing anything fancy like that!

So you dont have to test the inheritance. Presumably you are using the behaviour of the base class, so just test the derived class as you would normally - calling methods on both the base and derived class as appropriate for the test. This ensures that all intended behaviour of the derived class is tested.

Essentially, (most of the time) you test a derived class as if the base class is invisible.

metao
A: 

There seems to be some confusion, so I try to be more specific and provide an example. The following is quite similar to one of the actual cases where I have problems with testing inheritance.

/**
 * Creates HTML <select> elements
 */
class HtmlSelect {

    private $name;
    private $options = array();

    function setName($name) {
        $this->name = $name;
    }

    function setOptions($options) {
        $this->options = $options;
    }

 /**
  * Here we create an array-structure that represents
  * the HTML we would like to create (later we can transform
  * it to real HTML string, but this kind of array structure
  * is easier to work with, especially if we want to apply
  * some modifications to it in subclasses).
  */
    function toHtmlArray() {
        $html_options = array();
        foreach ($this->options as $label => $value) {
            $html_options[] = array(
                "value" => $value,
                "_contents" => $label,
            );
        }

        return array(
            "_tag" => "select",
            "name" => $this->name,
            "_contents" => $html_options,
        );
    }

}

/**
 * Creates HTML <select> elements where the first option
 * is always empty. Additionally that first <option> element
 * has class="empty", so that we can style it differently with CSS.
 */
class NullableHtmlSelect extends HtmlSelect {

    function setOptions($options) {
        $options = array("" => "") + $options;
        parent::setOptions($options);
    }

    function toHtmlArray() {
        $html_select = parent::toHtmlArray();

        $html_select["_contents"][""]["class"] = "empty";

        return $html_select;
    }

}

The new bahavior of subclass is different enough from parent, so that when I would try to run all tests of parent class on child class, some of them would fail.

The subclass strongly depends on the bahavior of superclass, adjusting it only the slightest way, so if I would test it as if the superclass would be invisible (as metao suggests), I would need to duplicate a lot of tests I have already written for the superclass. This would result in some heavy duplication.

@metao: I don't understand why my base class shouldn't do anything fancy. What do you mean by fancy anyway? If my superclass wouldn't do anything that would be worth testing, then that I would be better off without that kind of useless base class at all.

Rene Saarsoo
You should really edit the question with this response. "Fancy" just meant anything that would require mocking. Base classes are generic.
metao
Duplication in test harnesses, while not ideal, is often unavoidable. The penalties are not severe, as, if the superclass is changed, the derived tests will fail, but your test reports should reveal this.
metao
A: 

I still don't understand your requirements. What's wrong / missing if you test the superclass methods with their own unit tests, and then the subclass methods with also their own, distinct tests? It sounds like you want to test the superclass methods again in the tests for the subclass? Why would you need to do that? Or am I missing something

Robse
A: 

Robse wrote:

What's wrong / missing if you test the superclass methods with their own unit tests, and then the subclass methods with also their own, distinct tests?

This only works, when subclass just adds new methods without overriding any of those in parent class. But when I override a method in superclass, then I need to both ensure that the new method now changes the behavior of superclass as it should, and at the same time doesn't screw up the behavior that it shouldn't effect.

Rene Saarsoo
A: 

No, you don't. You just have to check that the overriden methods do what they should. It should in NO WAY impact the behaviour of your parent methods. If your parent methods start to fail, then it means that you missed bound conditions when testing them at the parent level.

gizmo
A: 

@gizmo: I completely agree that subclass methods shouldn't affect the way parent class methods behave. That is why I have avoided accessing member variables of parent class and only used its public inteface.

But when the subclass method calls method in parent class, then when testing the subclass method I also end up testing parent class method.

To use the code example I gave earlier: it is not enough when I just test that NullableHtmlSelect::toHtmlArray() returns HTML where first <option> has class "empty". We also need to test, that other <option> elements don't have this class, and that the other <option> elements are still returned in the correct way, because it could have happened, that our setOptions() mistakendly discarded all the other options, and so on...

Rene Saarsoo
A: 

You'r right, and that's exactly what I'm pointing too!

As long as your override a method in a subclass, you HAVE TO unit test it. The fact that it call the parent method or not does not matter, just test that the result of the subclass' method is the one you expect, as well as the parent method behave like you want it.

Moreover, as @metao pointed, there is no additional state set-up in your parent class that you did not catch in your subclass. And if this occurs, then you should reconsider using composition over inheritance.

gizmo
+1  A: 

Use a suite of unit tests that mirrors the class hierarchy. If you have a base class Base and a derived class Derived, then have test classes BaseTests and derived from that DerivedTests. BaseTests is responsible for testing everything defined in Base. DerivedTests inherits those tests and is also responsible for testing everything in Derived.

If you want to test the protected virtual methods in Base (i.e. the interface between Base and its descendent classes) it may also make sense to make a test-only derived class that tests that interface.

munificent
A: 

There is a nice example for the solution given by munificent. Let the test hierarchy resemble your class hierarchy.

GrGr