Perche’ non si dovrebbero mockizzare classi concrete

Mi sono sempre chiesto perche’ mai Steve Freeman e gli altri ‘padri’ dei Mock Objects e dello stile di testing interaction-based sconsigliassero di fare mock di classi concrete, tanto che jMock ed EasyMock non supportavano questa possibilita’ ‘nativamente’, ma si doveva installare un’estensione a parte, che usa la famosa libreria CGLIB (per inciso con il nuovo jMock 2 e’ possibile fare mock di classi concrete, ma si deve usare la classe UnsafeHackConcreteClassImposteriser, ed il nome dice tutto di come scoraggino questa pratica).

Tanto che tempo fa avevo anche commentato un post di Freeman chiedendogli perche’ lui giudicasse il mock di classi concrete come una sorta di ultima possibilita’ da adottare solo in casi di emergenza (es quando si affronta codice legacy particolarmente ostico e chiuso). E lui mi aveva risposto cosi’:

Because then the interface is implicit, which means you can’t see it and you haven’t given it a name. Instead of using the test to expose a feature of the design, you have more to think about whenever you’re working in that area: is the method overwritten? is it in a super class? what about the state of the rest of the class I’m extending? That sort of thing.

For me, the CGLIB should have a “Break Glass in Case of Emergency” written on the front. It’s useful in tight situations, but not to be recommended.

Eppero’ nonostante la sua risposta la cosa non mi era ancora chiara fino in fondo.
Poi un recente post su mockobjects.com (Test Smell: Mocking concrete classes) mi ha finalmente aiutato a capire meglio le cose.

Il fatto e’ che lo scopo del TDD con i Mock Objects e’ quello di scoprire e far emergere relazioni tra oggetti, e dare nomi a queste relazioni. Se pero’ si mantiene la relazione tra oggetti a livello di classi concrete, queste relazioni rimangono per cosi’ dire piu’ nascoste, implicite, e quindi diventa difficile individuarle e darle un nome.

Cito direttamente il post, laddove spiega cosa non va con i mock di classi concrete:

The problem with this approach is that it leaves the relationship between the objects implicit.
I hope we’ve made clear by now that the intention of Test-Driven Development with Mock Objects is to discover relationships between objects.
If I subclass, there’s nothing in the domain code to make such a relationship visible, just methods on an object. This makes it harder to see if the service that supports this relationship might be relevant elsewhere and I’ll have to do the analysis again next time I work with the class.

Addirittura si spinge a dire che lasciando le relazioni tra oggetti a livello di classi concrete, si rischia di violare l’Interface Segregation Principle, perche’ le classi dipendono da una interfaccia piu’ grande di quella che usano veramente.

E poi poco piu’ avanti spiega che l’approccio mockist aiuta anche a dare nomi alle relazioni ed ai ruoli, e a ragionare piu’ in termini di dominio piuttosto che di implementazione:

There’s a more subtle but powerful reason for not mocking concrete classes.
As part of the TDD with Mocks process, I have to think up names for the relationships I discover—in this example the ScheduledDevice. I find that this makes me think harder about the domain and teases out concepts that I might otherwise miss.
Once something has a name, I can talk about it.

Il tutto e’ poi riassunto bene nell’altro post, What the tests will tell you, da leggere e rileggere, dove elenca alcuni benefici derivanti dall’approccio TDD+MockObjects

  • Keeping knowledge local
  • If it’s explicit I can name it
  • More names mean more domain information
  • Pass behaviour rather than data

Detto questo, ammetto che sono sempre stato affascinato da questo approccio ma non ho mai trovato la forza necessaria per decidere di applicarlo un po’ piu’ sistematicamente, metterlo alla prova insomma.

Un po’ perche’ nel mio team da sempre si usa l’approccio classico, e l’approccio interaction-based e’ visto con qualche dubbio/sospetto, un po’ perche’ i nostri progetti per ora sono tutti Java 1.4 e quindi buona parte delle comodita’ delle versioni piu’ recenti dei framework jMock e Easymock te le perdi (soprattutto la loro maggiore letteralita’, cosa importante per avere dei test validi).

Ascoltare i propri test, ovvero migliorare il codice partendo dagli smell dei propri test

Steve Freeman e Nat Pryce hanno iniziato una serie di interessanti post sul loro blog mockobjects sul tema dei Test Smells, ovvero su come ‘ascoltare’ i propri test per scoprire possibilita’ di miglioramento nel design del codice sotto test

In our experience, when we find that our tests are awkward to write, it’s usually because the design of our target code can be improved.

Una cosa su cui mi trovo d’accordo, piu’ che altro perche’ ho avuto la stessa esperienza in passato (e anche nel presente!)

Test Smells…

Il primo post della serie e’ Test Smell: I need to mock an object I can’t replace (without magic), dove si parte da un codice piuttosto ‘chiuso’, con dipendenze nascoste (magari usando dei Singleton), e passo passo lo si rifattorizza, aprendolo, prima introducendo un fake (o stub che dir si voglia) e poi passando ai mock, mostrando come il codice migliori, prima esplicitando le dipendenze e poi assegnando meglio le responsabilita’.

Se vi capita dategli un’occhiata!

Law Of Demeter

Di recente ho seguito un thread intitolato “The Law of Demeter and Testability” sulla lista xp americana.

A partire dal primo post di Jay Flowers (niente di travolgente), il discorso si e’ poi spostato sulla legge di demeter di per se’, il cui ‘peso’ personalmente non ho mai capito fino in fondo. Ne’ tantomeno mi sembra che se ne parli molto, al contrario magari di altri principi piu’ noti come OCP, DIP, LSP, SRP, eccetera.

Alcuni messaggi di questo thread mi hanno fatto capire meglio questo ‘principio’ (chiamarla legge fa un po’ ridere) e a vederlo in un’ottica diversa.

Vi sottopongo gli spunti piu’ interessanti, sperando possano esservi di stimolo quanto lo sono stati per me

Michael Feathers:

It’s a great article, but the thing to remember about LoD is that it is not iron-clad. There are cases where you’re better off not having demetered code. The classic example is the Excel Object model. You get the workbook from the application and the worksheet from the workbook, and drill down through ranges to cells. It works because the model is very stable.

In general, when the structure of data has meaning, LoD gets in the way; when you care more about behavior (and usually you can), LoD is the way to go.

Ron Jeffries:

Yes … the observation about structure is a good one. If you think of what you’d have to do, otherwise, with the Excel model, you’d have to make each call to Excel, parameterized with workbook name, worksheet name, maybe range, then cell. Pulling the innards out amounts to an addressing prefix, or stepping down through a hierarchy.

Mostly, though, I’d say LoD is the way to bet.

E infine Brad Appleton:

I actually took a class from Dr. Lieberherr on the full Demeter method (of which LoD is one small part). Having had the benefit of that, I think I have a lot of additional insight into the above.

The issue with the LoD is that, the way it is usually stated, the assumption is any/every class in an O-O program should be exhibiting functional behavior and hiding the details of what it contains, and how it contain them.

There are classes whose job is to encapsulate functional behavior/services, and there are classes whose job is to encapsulate structural behavior/services. That is to say that for some classes, the “secret” they need to “hide” isnt the fact that they contain some kinds of objects, but the details of HOW they contain those objects.
Simple examples of these would be data-structures, lists, collections, iterators, dictionaries, data-stores, etc. Accessing the interfaces of the objects contained by such encapsulated structures actually doesnt violate the principle behind the LoD. But because of the way LoD is stated in such an oversimplified manner, those things appear to be violations when they really arent.

When a method-call returns a subobject of its containing object, then consider that invocation to be a violation of LOD *only* *if* the containing object is supposed to encapsulate behavior concerning its subobjects. If the object really only needs to encapsulate structural details about HOW it contains that object (rather than WHAT it contains), then it’s not really a violation of LoD.

So it’s less about whather or not the caller cares about the behavior or structure of the data, it more about whather the callee cares about (should be encapsulating) the behavior or the structure of what it contains.