Results-Based Testing

March 19, 2010

A while ago I gave a talk on unit testing at a Philly ALT.NET meeting.  On the way home from that meeting, Brian Donahue and I had a discussion that went something like this (somewhat paraphrased (ok, a lot parapharased)):

Brian: Dude, you have to stop testing expectations and start testing the results!
Me: Damn, you’re right!  I used to do that but kinda fell away from it.  Guess it’s time to go back.

What do I mean by results-based testing?

If you’re familiar with most any testing framework (like NUnit) and Rhino.Mocks, you’ll be familiar with two ways of testing objects.  One way is asserting something was called (ex: foo.AssertWasCalled(x => x.MyMethod()), another way is just comparing objects and ensuring the value returned was expected (ex: Assert.AreEqual(foo, bar)).

Results-based testing means testing more of the latter, and less of the former.  As I’ve gone through the learning curve of TDD, I’ve noticed that focusing on using the AssertWasCalled style is often less than useful.  Let’s face it, usually you’re aware you called that method, what you want to know is what was returned from it.  So results-based testing focuses more on comparing results to ensure they’re what you expect.

An Example

Let’s say we have a simple class with one method on it, Execute().  Let’s also say that it has one dependency, a repository whose job it is to get all users.  The Execute method may look something like this:

   1: public IList<User> Execute()

   2: {

   3:     return userRepository.GetAll();

   4: }

Now, if you’re just testing expectations, you might write a test that says something like this:

   1: userRepository.AssertWasCalled(x => x.GetAll())

As mentioned above, this doesn’t feel very useful.  My class is only doing one thing, so I’m pretty confident that the repository was called.  What I want to test is that this method is actually returning all users.

To do this, first you have to set up your expectations, then you can do a simple Assert.AreEqual to ensure that the results are equivalent.  For the first part, Rhino.Mocks lets you stub out expectations on a class, so we’ll take advantage of that to set up the results that we expect to be returned.  Then we’ll get the actual result of the method call, and finally we’ll assert that the result we set up is equal to the one that was actually returned:

   1: var myListOfusers = new List<User> { new User() };

   2: userRepository.Stub(x => x.GetAll()).Return(myListOfUsers);

   3: _result = myClass.Execute();

   4: Assert.AreEqual(myListOfusers, _result);

That’s all there really is to it.  Now your tests are really going to start to become valuable, because as soon as the logic in your class changes, and affects the users that are returned, your test will break in a meaningful way.

Expectation Tests Are Not Useless

Far from it, actually.  They have their place.  Typically I use them in scenarios where different conditions mean that different methods will be called, for example, an if/else statement where the method called on an object changes depending on what branch of the statement you are in:

   1: if (criteria != null)

   2: {

   3:     userRepository.GetByCriteria(criteria);

   4: }

   5: else

   6: {

   7:     userRepository.GetAll();

   8: }

In this case, if you don’t have access to the results returned by these calls, it might be worthwhile to write 2 tests, one for each condition, and each test will simply assert that the appropriate method was called.

In either case, find the test type that’s right for you – the one that really tests the business logic, because that’s where you get your real value.

Advertisement

4 Responses to “Results-Based Testing”

  1. Dan said

    That’s too funny – I was recently thinking the same thing and was planning on writing a blog about the topic as well.

    Now I feel like I’m going to plagiarize you.

    I agree though – since both of us got our start in unit testing and TDD at around the same time, on the same project, I think we both have the same slanted mockist viewpoint as far as testing goes. It’s good to hear I wasn’t the only one (Brian may have nudged me in that direction too, as well as the Boodhoo videos and an article I read on Martin Fowler’s website recently) that was starting to think that testing expectations on mock objects was not always the best way to go.

  2. “It’s a family affair…. it’s a family affair…”

    The only thing I’d say is that your example where interaction/expectation testing is useful is actually easily tested by result if you can stub out both methods to return different result objects. If for some reason you can’t stub a result (design smell) then I suppose it’s useful, or if you need to verify that a void method was called, then it’s also useful. Can’t think of many other examples, though I’m sure there are some.

  3. cerikpete said

    Thanks brothers Donahue. :) @Brian: Yeah the last example I used for explaining how expectation testing was a bit weird, that’s why I prefaced it with saying it would be useful only if you didn’t have access to the results. However, as you mentioned in our original discussion (and also in your comment above), a better example is testing methods with a void return type, where you really can only test expectations.

    As for not having access to results, I’m thinking of the scenario we often have where you call NHibernate to load an object, then call AutoMapper to load its properties with updated values. In this case, you might have logic in your class that dictates how you load the object from NHibernate, so you might want to test expectations there, but you can’t test results since you don’t do anything with that object besides call AutoMapper. You don’t need to test that Save was called because, at least in our solution, the HttpModule we wrote handles that.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.