I Prefer to Read the Classics

 

Programs must be written for people to read, and only incidentally for machines to execute.

- Abelson & Sussman, Structure and Interpretation of Computer Programs

I like to occasionally revisit Martin Fowler's article Mocks Aren’t Stubs whenever I’ve been thinking a lot about unit testing. I tend to pick up on nuances that I didn’t pick up on previously.

In the article, Fowler breaks down what he calls the Classicist vs. Mockist schools of unit testing. He describes what he sees as the advantages and disadvantages of each approach in a fairly unbiased way.

In my most recent pass through the article, I noticed that he leaves out what I see as an important facet of this divide in approaches: readability of tests.

Here’s an example of the Classicist style of unit testing that Fowler uses in the article:

public void testOrderIsFilledIfEnoughInWarehouse() {
Order order = new Order(TALISKER, 50);
order.fill(warehouse);
assertTrue(order.isFilled());
assertEquals(0, warehouse.getInventory(TALISKER));}

 

And here’s a functionally similar test he provides in the Mockist style of unit testing, which I understand may not be at the cutting edge of Mockist style, but I think illustrates the problem for me:

public void testFillingRemovesInventoryIfInStock() {
//setup - data
Order order = new Order(TALISKER, 50);
Mock warehouseMock = new Mock(Warehouse.class);

//setup - expectations
warehouseMock.expects(once()).method("hasInventory")
.with(eq(TALISKER),eq(50))
.will(returnValue(true));
warehouseMock.expects(once()).method("remove")
.with(eq(TALISKER), eq(50))
.after("hasInventory");

//exercise
order.fill((Warehouse) warehouseMock.proxy());

//verify
warehouseMock.verify();
assertTrue(order.isFilled());
}

In my opinion, Mockist-style unit tests are much harder to read than Classic tests. And readability is king. If you scan a test and think to yourself I have no idea what this test is supposed to prove, then that’s a bad test.

And to be frank, I sometimes feel that mock-heavy tests are more a demonstration of Look what objects can do! than a practical means of gaining confidence in one’s software. Each new mocking framework tries every syntactic trick it can think of to mitigate the fact that behavior-based verification is inherently an awkward concept.

If the primary test of a test is readability, then the Classicists have it.

2 comments :

Marcel said...

Agreed. However, that's more of a Java issue (or maybe JUnit) rather than an intrinsic problem with mocks.

Here's the C# / Moq equivalent:

// setup - data
var order = new Order(TALISKER, 50);
var warehouse = new Mock();

// setup - expectations
warehouse.Setup(it => it.hasInventory(TALISKER, 50)).Returns(true);
warehouse.Setup(it => it.remove(TALISKER, 50));

// exercise
order.fill(warehouse.Object);

// verify
warehouse.Verify(it => it.hasInventory(TALISKER, 50)).Times(1);
warehouse.Verify(it => it.remove(TALISKER, 50));

The only difference here is that I don't verify the order of the calls; I believe that Moq has a way to do that but I've never used it.

Matt Blodgett said...

Thanks for the comment Marcel!

I'll quote the penultimate paragraph from my post :)

"Each new mocking framework tries every syntactic trick it can think of to mitigate the fact that behavior-based verification is inherently an awkward concept."