views:

84

answers:

3

I've used unit tests successfully for a while, but I'm beginning to think they're only useful for classes/methods that actually perform a fair amount of logic - parsers, doing math, complex business logic - all good candidates for testing, no question. I'm really struggling to figure out how to use testing for another class of objects: those which operate mostly via delegation.

Case in point: my current project coordinates a lot of databases and services. Most classes are just collections of service methods, and most methods perform some basic conditional logic, maybe a for-each loop, and then invoke other services.

With objects like this, mocks are really the only viable strategy for testing, so I've dutifully designed mocks for several of them. And I really, really don't like it, for the following reasons:

  1. Using mocks to specify expectations for behavior makes things break whenever I change the class implementation, even if it's not the sort of change that ought to make a difference to a unit test. To my mind, unit tests ought to test functionality, not specify "the methods needs to do A, then B, then C, and nothing else, in that order." I like tests because I am free to change things with the confidence that I'll know if something breaks - but mocks just make it a pain in the ass to change anything.
  2. Writing the mocks is often more work than writing the classes themselves, if the intended behavior is simple.
  3. Because I'm using a completely different implementation of all the services and component objects in my test, in the end, all my tests really verify is the most basic skeleton of the behavior: that "if" and "for" statements still work. Boring. I'm not worried about those.

The core of my application is really how all the pieces work together, so I'm considering ditching unit tests altogether (except for places where they're clearly appropriate) and moving to external integration tests instead - harder to set up, coverage of less possible cases, but actually exercise the system as it is mean to be run.

I'm not seeing any cases where using mocks is actually useful.

Thoughts?

A: 

Writing Integration Tests is a viable option here, but should not replace Unit Tests. But since you stated your writing mocks yourself, I suggest using an Isolation Framework (aka Mocking Framework), which I am pretty sure of will be available for your environment too.

Johannes Rudolph
+2  A: 

To my mind, unit tests ought to test functionality, not specify "the methods needs to do A, then B, then C, and nothing else, in that order."

I agree. Behavior testing with mocks can lead to brittle tests, as you've found. State-based testing with stubs reduces that issue. Fowler weighs in on this in Mocks Aren't Stubs.

Writing the mocks is often more work than writing the classes themselves

For mocks or stubs, consider using an isolation (mocking) framework.

in the end, all my tests really verify is the most basic skeleton of the behavior: that "if" and "for" statements still work

Branches and loops are logic; I would recommend testing them. There's no need to test getters and setters, one-line pure delegation methods, and so forth, in my opinion.

Integration tests can be extremely valuable for a composite system such as yours. I would recommend them in addition to unit tests, rather than instead of them.

You'll definitely want to test the classes underlying your low-level or composing services; that's where you'll see the biggest bang for the buck.

EDIT: Fowler doesn't use the "classical" term the way I think of it (which likely means I'm wrong). When I talk about state-based testing, I mean injecting stubs into the class under test for any dependencies, acting on the class under test, then asserting against the class under test. In the pure case I would not verify anything on the stubs.

TrueWill
I would not use the terms "state based testing" or "state verification" when talking about method stubbing, as these two things are completely separate and independent.In the Fowler article, he specificaly describes state verification as "examining the state of the SUT and its collaborators after the method was exercised", which is done through regular JUnit/TestNG asserts, nothing more.
Rogerio
@Rogerio - I do mean (almost) the same thing as Fowler. Roy Osherove describes it best in the book The Art of Unit Testing: "When using a stub, the assert is performed on the class under test." With mocks, "The test uses the mock object to verify that the test passes." The main difference from Fowler is that in this version of state verification, we are using injected stubs to isolate the SUT from all of its collaborators and **only** examining the state of the SUT (with regular xUnit asserts). http://www.artofunittesting.com/
TrueWill
You're right, I was reading too much into "state-based testing with stubs". On the other hand, stubs can be seen simply as degenerate mocks which don't perform an automatic assertion. Implementation-wise, the support for mocks and stubs in JMockit Expectations, for example, is mostly shared. The JMockit Annotations API, however, adds a twist to the concept of state verification, by allowing regular asserts to be performed on method call arguments, for calls from the tested unit to its dependencies; or maybe I should call this something else... still thinking about it.
Rogerio
+2  A: 

If you can write integration tests that are fast and reliable, then I would say go for it. Use mocks and/or stubs only where necessary to keep your tests that way.

Notice, though, that using mocks is not necessarily as painful as you described:

  1. Mocking APIs let you use loose/non-strict mocks, which will allow all invocations from the unit under test to its collaborators. Therefore, you don't need to record all invocations, but only those which need to produce some required result for the test, such as a specific return value from a method call.
  2. With a good mocking API, you will have to write little test code to specify mocking. In some cases you may get away with a single field declaration, or a single annotation applied to the test class.
  3. You can use partial mocking so that only the necessary methods of a service/component class are actually mocked for a given test. And this can be done without specifying said methods in strings.
Rogerio
+1. Strict mocks are a quick route to brittle tests.
TrueWill