Unit testing question

Hi,

What exactly should be tested on a class that does almost all of its job by delegating to other objects? All I can think of right now, is testing that correct methods are called (with the correct parameters) on the dependencies of the class. Thoughts?

Thanks in advance.

Yes. As mentioned, mock the collaborators.

Mocks and top-down TDD aren’t primarily about testing (although that too) but rather about discovering a design. Responsibilities which don’t fit in the the tested class are mocked out and these assertions begin to describe the next step down in the object graph. When the current test is done, and you start to implement the mocks, other new objects will be thrown up, and so on and so on until the app is complete.

The beauty of TDD and mocks is that a complex problem gets broken down into lots of small, easy steps. It’s a great way to work. The idea is to learn about a new domain by diving right in. Just invent the design as you go, taking your best guess at the time. Come back and refactor regularly to tidy up and make other improvements as you learn more.

Also, it’s good to write tests which read well as documentation. Clear and concise tests are a godsend when you can’t remember all the details of how a particular class behaves.

I’ve thought about this, and I guess it’s a perspective thing. On one hand, what you’re really testing is that a cart containing 4 items will return 4 when asked how many items it has.

On the other hand, because there’s a dependency, there’s no real safe way to test that without setting up your mocks to return a certain value when a certain method is called (and my example didn’t show that very well). This is what it should have been:

$item1->setReturnValue('getQuantity', 3);
$item2->setReturnValue('getQuantity', 1);

This is my current approach, but it felt somewhat weird when doing it. I can’t really explain why.

I agree.

Thank you all for your answers.

It is quite interesting to debate as I have read the http://blog.objectmentor.com/articles/2010/01/23/mocking-mocking-and-testing-outcomes one as well and it really comes to what is more readable, refactorable( renaming a method should rename the mocked methods ) and light not fragile. I really do believe testing effectively is possibly harder than writing functional, good but less than fundamentalist pure code whatever that is (though I think injecting by interface, nailing down responsibilities and a deep level understanding of the cost of coupling are a large part of it).

I think the testing rules of the language to a degree are at the mercy of the design of a language. Fowler and Martin use Java in a lot of examples which due to it has strong typed relations and checked exceptions and with it some test cases are removed from existence as it cannot compile. That is the benefit of strongly typed languages cheap, some would zealot like say baby like coddling, decisions are made for you. Though I say strongly typed public and protected interfaces protect against the almost Chinese whispers game of parameter passing in large systems, there is debate over weak scalar type hints (error triggering on loss of definition when casting) in PHP which would be good enough as those who believe a notice/ warning or whatever level they wish should be fatal can configure the server to do that. That topic does cause a lot of war between two sides who cannot see any benefit in the others, I think good languages learn off of the design benefits and mistakes of others taking into account that it may be the implementation was bad and not the idea. Though interface design is a two way street and the return type needs to be dealt with to balance the current enforceable one sided relationship.

Anyway I use the mockist approach in my day job and I appreciate it does get very costly and unreadable very easily, with the move to PHP5 a move to mocking was made( though in fact the technology was not directly linked ) due to the distaste of trying to stub objects in PHP4 where interface could not be regulated and am trying to find the middle ground to steer things back to a less carpet bomb approach of a constant rule, it was of those overly reflexive team decisions that became engrained for good or bad. A move to an IDE such as PHP Storm that handles implementing from interface and refactoring more elegantly would make stub objects such a breeze over the mockist approach as mocking usually breaks a lot of tool intelligence in these matters. Martin etc. all have tools and toys that make some grunt work so easy for them but so hard for us, they say if a method is named badly rename it. String searching for the method name and auditing those replacements is time consuming and makes it off putting to some, and that is the simple stuff let alone extract interface, push up method etc that needs deep level project type hint/phpdoc introspection to do it safely and properly. Shift f6 rename method, Ctrl-i and stub methods are born for an interface implementation etc. are lovely things when they work properly.

A bit of a side tangent but I find this stuff pretty tough as I have 3 people new to TDD that I am coaching under me and I am trying to teach them good practices versus overly idealistic practices as I am pretty idealistic to the point of fundamentalist on some things. It has to be sold as beneficial to them and provably so by ultimately be enjoyable and keep productivity up versus a cross to bare that helps to pay the bills. Tests can add debt due to their design and implementation and can ultimately help in the final collapse of a system due to aiding in the lack of internal quality restricting time effective feature change, though they will stave that point off for a lot longer with a near certainty when scaling teams flexibly up, down and interchangeably is a requirement.

What method gets called isn’t really important

Why do you say that? I have always understood BDD to focus more on behavior details. How else to you verify behavior if don’t observe/verify which methods or objects are used internally?

This is why Fowler disliked the idea of Mock testing as opposed to classical testing, because of the proximity of the “test” to the “implementation” detail.

I don’t see any compelling benefits for mockist TDD, and am concerned about the consequences of coupling tests to implementation.

Personally, the only time I verify behavior, is when an implementation would break if anything changed but not trigger erros or exceptions.

Otherwise, I think the resultant state of an operation more helpful.

Cheers,
Alex

And then it is on to test unexpected behaviour with the boundary tests. Currently your example handles positive numbers, should it be able to handle negative numbers? Where should that intelligence lie in the design? Should that part of the contract be in Item and the test for that show that? Should negative numbers be guarded on both the entrance in the constructor and at point of retrieval in Cart from Item? What about nulls etc? Weakly typed languages come with cost as well as benefit and communication of intent when testing can actually get more verbose because of this. Depends how defensive the testing is but the usual thing is write enough test to fail, write the code to pass, break it again with a test, fix it etc. Keep going until it can communicate its unhappiness helpfully no-more and the test validates this in case someone refactors it to a less ideal state, tests are more authoritive than code otherwise benefit is lost.

It does rely on a more aggressive mental approach where the assumption is that piece of code does not handle edge cases elegantly which in turn can hide a cascade of nasty behaviour and it is not going to be your friend about it. Especially when there is a group of interacting classes that all have holes in their handling at an interface level due to assumption and are collaborating in varying circumstances to create a similar problem labelled as one bug. You end up having to fix the bug in multiple places, 1st fix only fixes 1/3 of the time etc. Though I do see only expected behaviour being tested quite a lot and I do believe it is can be a thing of religion.

Unit tests when well written come in very handy when things go wrong and the unexpected does turns up. If I see holes in the test it makes me look for holes/exception swallowing behaviour in the code. I just love exception swallowing when the question arises why 140,000 emails did not get sent and it turns out after a lot of digging around 140,000 exceptions were eaten and the system just smiled and whistled innocently as if it did had displaying a reassuring Done message :slight_smile:

Anyway without knowing what the design will be and how the responsibilities (including value and type guarding) are shared combined with the assumptions you are willing to allow it is quite hard to design a test. Perfect world scenarios are easy but the murky stuff is where the pain lies and the banging head on table pleading where will it stop :slight_smile:

Right BICEP is often one of the chants of TDD, combined with a bit of LDUF
http://blog.objectmentor.com/articles/2009/04/25/the-scatology-of-agile-architecture

Without good code design the corresponding tests fare a lot worse, having to back track can be very expensive time wise.

This is quite a good cheat sheet, it is PDF though
http://www.google.co.uk/url?sa=t&source=web&cd=1&ved=0CBgQFjAA&url=http%3A%2F%2Fmedia.pragprog.com%2Ftitles%2Futj%2FStandaloneSummary.pdf&ei=I6d-TNecLtyc4AaYtrmxBg&usg=AFQjCNG75PQNa9uXmI_dORmhooQwL98BLA
(I quite like the guilty until proven innocent thing)

What method gets called isn’t really important.

What you’ll want to do is mock the dependency, like this (using SimpleTest):

class Cart {
  function add (ItemInterface $item) {
    // ...
  }

  function getNumberOfProducts() {
    // ...
  }

}

class Item implements ItemInterface {
  function __construct($quantity,$productName){
    // ... 
  }
}

Mock::Generate('Item');

class TestOfCart extends UnitTestCase {

  function setUp() {
    $this->cart = new Cart();
    $this->cart->add(new MockItem(1,'Product 1'));
    $this->cart->add(new MockItem(3,'Product 2'));
  }

  function testGetNumberOfProductsDelegatesToItems() {
    $this->assertEqual($this->cart->getNumberOfProducts(),4);
  }

}

Once written, the above test case allows you to implement the code in Cart::getNumberOfProducts() that loops through each item, totals the quantity, and returns the result. If you did it right, the test will pass, indicating the class under test (Cart) exhibits the expected behavior.

Think in terms of verifying behavior, as opposed to testing/asserting results.

Cheers,
Alex