Friday, October 5, 2018

"Mockist" or "Classicist"? Both!

From time to time, an article or a talk with an interesting name pops up on the internet:
TDD is dead, Mocking sucks, Object-oriented programming is embarrassing, ...
In my opinion, these kinds of articles are good for the industry because it encourages discussion and inspires us to re-examine our views.
However, one of the things that is common for most of these articles and talks is a placement of paradigms and methodologies into the wrong context:

  • Trying to see the benefits of OOP (Object-oriented programming) over procedural programming on toy examples,
  • Trying to apply mocking on a procedural code,
  • Mocks are confused with stubs, etc.
This lack of understanding can be very harmful because it can lead to the wrong impression and premature rejection of abovementioned methodologies.

“What I have seen is novices deciding to be “classicist” or “mockist” instead of learning how to pick a right tool for the job at hand”
Nat Pryce

Let us examine both TDD styles so we can understand the benefits and drawbacks of each one.   

“Classicist” style

“Classicist” style is described in Kent Beck's book Test Driven Development: By Example.

In short, the TDD cycle happens in the following steps:

  • Red: Create a test and make it fail,
  • Green: Make the test pass by any means necessary,
  • Refactor: Change the code to remove duplication and to improve the design while ensuring that all tests still pass.
An important thing to notice here is that design decisions are made in the blue phase of the cycle.

This style of testing is based on state verification. It means that we check the correctness of the unit by examining its state.

My first contact with unit tests and TDD was in the environment where isolation of a unit from all dependencies, mocking and stubbing were a standard way of writing tests. At the time, I’ve heard a talk from Kent Beck where he said that he doesn’t use mocks (with exception of things like communication with a database, filesystem, etc.). Frankly, I was not sure how is that even possible. Where is the isolation of the unit? The answer is simple: A unit uses real dependencies. There is no isolation that I was used to.

One more thing that I didn’t get at the time is that unit of work isn’t necessary one class.

Roy Osherove in “The Art of Unit Testing" describes a unit of work as:
“A unit of work is the sum of actions that take place between the invocation of a public method in the system and a single noticeable end result by a test of that system. A noticeable end result can be observed without looking at the internal state of the system and only through its public APIs and behavior.”

This may result in tests that are dealing with a cluster of objects. Because of that, we might need a large number of tests to cover all the important parts of our code. J. B. Rainsberger explained this in his brilliant talk: Integrated tests are a scam.

Another issue is that, in my experience, “classicist” style doesn’t create useful pressure on design as “mockist” style does.

“Mockist” style:  

The best resource for this style of TDD is Growing Object-Oriented Software, Guided by tests.

Usual statement on the internet: Mocks will result in brittle tests that bind to implementation details and will lead to destructive decoupling (test induced damage).

Is this really true?

“The key in making great and growable systems is much more to design how its modules communicate rather than what their internal behaviors should be.”
Alan Kay

Majority of us came to the object-oriented world from procedural languages and most of us retained our procedural habits. Trying to apply mocks on procedural code can(will) cause abovementioned problems.

Key to the good object-oriented design lies in messaging. Whether we are going to create a good design depends on our ability to make correct decisions in terms of what belongs to the inside and what to the outside of an object. “Mockist” style TDD can be of great help in this complicated process.

Let's say that I'm practicing “mockist” style TDD during the implementation of some class Foo. I create a failing test and to make it pass, I detected that I'm going to need a collaborator to do something for me. I create a mock of IBar interface and I'm asserting that mock has received a matching call. An important thing to point out is that I'm going to assert only on command messages to my collaborators (for reference please watch Sandi Metz talk “Magic tricks of unit testing”). The great thing here is that even if I need a collaborator, I can concentrate on the implementation of the class Foo (thanks to mocks), and when I'm done, I can start implementing its collaborators. That should be straightforward because I already defined contracts that my collaborators must adhere to.

SOLID

The behavior of the OOP system should be modified by object composition and not by changing the implementation of methods. IBar interface that we introduced during the implementation of Foo class enables us to change behavior with composition (we can inject another implementation of IBar interface, or maybe decorator or composite).
This also allows us to comply with the Open/Closed principle.
Interface segregation principle is all about the process of creating interfaces from a client's perspective. This is exactly what we are doing here. We are creating interfaces from Foo's perspective.
In my experience, this kind of interfaces (created by client’s needs) are helping with Liskov substitution principle also and by thinking of what should be the responsibility of a Foo class and what should be its collaborator’s role, we are getting feedback about Single Responsibility Principle.
Implementation of IBar interface is injected into Foo class using dependency injection, so we are respecting Dependency inversion principle also.    

I'm not claiming that TDD leads to a better design mechanically, a developer does that! If you mechanically apply the Single Responsibility Principle and Dependency injection everywhere without deep domain analysis and thinking, you will produce incohesive design and brittle tests. If you are not following Law of Demeter, you will probably end up with deeply nested mocking which is also bad for design, etc.

My point is that this methodology puts us into the position where we'll be able to make better design decisions. You can create a good and modular design with “classicist” approach of course, but to me, "mockist" style provides a better environment for this.

When I mentioned the Open/Closed principle, I made the strong statement. One of the biggest problems with this principle is that we cannot predict the future. When is the right time to introduce abstraction? There is no concrete answer to this. It all depends on the context. However, wrong abstractions can be (are) painful (Sandi Metz: Wrong Abstraction). If I'm working in the environment where I'm familiar with domain and I can detect (guess with enough confidence) the abstractions, “mockist” is the right tool for the job. However, if I need to explore, I'll start with “classicist” and then switch to “mockist” when I'm confident enough.

Summary

Classicist style can lead to the problem with a large number of integrated tests. Mocks are not for procedural code. “Mockist” style creates positive pressure on design. In order to give us best possible results, “mockist” style requires deep domain knowledge about a problem that we are solving (deep domain knowledge is always preferable, but I think that it’s crucial here in order to detect correct abstractions as early as possible) and deep understating of Object-oriented principles.

To be able to pick a right tool for the job means that you have a choice. To have a choice means that you know multiple ways to do one thing. If you don't, then there's no choice. If you know only one design pattern or style of TDD, that's the way you will always do things. Only if you truly understand different approaches you can choose the most suitable one for you and the problem you are currently solving.