13

Recently I was TDDing a factory method. The method was to create either a plain object, or an object wrapped in a decorator. The decorated object could be of one of several types all extending StrategyClass.

In my test I wanted to check, if the class of returned object is as expected. That's easy when plain object os returned, but what to do when it's wrapped within a decorator?

I code in PHP so I could use ext/Reflection to find out a class of wrapped object, but it seemed to me to be overcomplicating things, and somewhat agains rules of TDD.

Instead I decided to introduce getClassName() that would return object's class name when called from StrategyClass. When called from the decorator however, it would return the value returned by the same method in decorated object.

Some code to make it more clear:

interface StrategyInterface {
  public function getClassName();
}

abstract class StrategyClass implements StrategyInterface {
  public function getClassName() {
    return \get_class($this);
  }
}

abstract class StrategyDecorator implements StrategyInterface {

  private $decorated;

  public function __construct(StrategyClass $decorated) {
    $this->decorated = $decorated;
  }

  public function getClassName() {
    return $this->decorated->getClassName();
  }

}

And a PHPUnit test

 /**
   * @dataProvider providerForTestGetStrategy
   * @param array $arguments
   * @param string $expected
   */
  public function testGetStrategy($arguments, $expected) {

    $this->assertEquals(
      __NAMESPACE__.'\\'.$expected,  
      $this->object->getStrategy($arguments)->getClassName()
    )
  }

//below there's another test to check if proper decorator is being used

My point here is: is it OK to introduce such methods, that have no other use than to make unit tests easier? Somehow it doesn't feel right to me.

1

4 Answers 4

14

My thought is no, you shouldn't do anything only because it is needed for testability. A lot of decisions people make have benefit to testability and testability may even be the main benefit but it should be a good design decision on other merits. This does mean that some desired properties are not testable. Another example is when you need to know how efficient some routine is, for example does your Hashmap use an evenly distributed range of hash values - nothing in the external interface would be able to tell you that.

Instead of thinking, "am I getting the right strategy class" think "does the class I get perform what this spec is trying to test?" Its nice when you can test the internal plumbing but you don't have to, just test turning the knob and seeing if you get hot or cold water.

4
  • +1 OP is describing 'clear box' unit testing, not TDD feature testing Jun 24, 2011 at 20:02
  • 1
    I see the pointh ere, although I'm a bit reluctant to add testing of StrategyClass algorithms when I want to test if factory method is doing it's job. This kind of breaks isolation IMHO. Another reason I want to avoid that is that these particular classes operate on database, so testing them requires additional mocking/stubbing.
    – Mchl
    Jun 24, 2011 at 20:05
  • On the other hand and in the light of this question: programmers.stackexchange.com/questions/86656/… when we distinguish "TDD tests" from "unit tests" this becomes perfectly fine (still the database plumbing though :P )
    – Mchl
    Jun 24, 2011 at 20:12
  • If you add the methods they become part of the contract with your users. You will end up with coders who call your test-only functions and branch based on the results. Generally, I prefer to expose as little of the class as possible.
    – BillThor
    Jun 24, 2011 at 23:58
5

My take on this is - sometimes you have to rework your source code a little to make it more testable. It's not ideal and shouldn't be an excuse to clutter the interface with functions that are only supposed to be used for testing so moderation is generally the key here. You also don't want to be in the situation where the users of your code suddenly use the test-interface functions for normal interactions with your object.

My preferred way to handle this (and I have to apologise that I can't show how to do this in PHP as I mainly code in C-style languages) is to provide the 'test' functions in a way that they aren't exposed to the outside world by the object itself, but can be accessed by derived objects. For testing purposes I would then derive a class that would handle the interaction with the object I actually want to test and have the unit test use that particular object. A C++ example would look something like this:

Production type class:

class ProdObj
{
  ...
  protected:
     virtual bool checkCertainState();
};

Test helper class:

class TestHelperProdObj : public ProdObj
{
  ...
  public:
     virtual bool checkCertainState() { return ProdObj::checkCertainState(); }
};

That way, you at least are in a position where you don't have to expose the 'test' type functions in your main object.

1
  • An interesting approach. I'd need to see how I could adapt this
    – Mchl
    Jun 24, 2011 at 20:02
3

Few months ago when I placed my newly purchased dishwasher a lot of water came out of it's hose, I realized this is probably due to the fact it was properly tested in the factory it came from. It's not uncommon to see mounting holes and stuff on machinery who are there -only- for the purpose of testing in an assembly line.

Testing is important, if need be, just add something for it.

But do try some of the alternatives. Your reflection based option isn't all that bad. You could have a protected virtual accessor to what you need and create a derived class to test and assert against. Perhaps you can split your class and test a resulting leaf class directly. Hiding the test method with a compiler variable in your source code is an option too (I barely know PHP, not sure if that's possible in PHP).

In your context you could decide to not test the proper composition within the decorator, but test the expected behaviour the decoration should have. This would perhaps put focus somewhat more on expected system behaviour and not so much on technical specification (what does the decorator pattern buy you from a functional perspective?).

1

I'm an absolute TDD newbie, but it seems to depend on the method being added. From what I understand of TDD, your tests are supposed to "drive" the creation of your API to some degree.

When it's OK:

  • As long as the method doesn't break encapsulation and serves a purpose that's in line with the object's responsibility.

When it's not OK:

  • If the method seems like something that would never be useful, or doesn't make sense in relation to other interface methods, it may be incohesive or confusing. At that point, it would muddy my understanding of that object.
  • Jeremy's example, "...when you need to know how efficient some routine is, for example does your Hashmap use an evenly distributed range of hash values - nothing in the external interface would be able to tell you that."

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.