[Test] Non ho mai capito i test JMock


Sono nato seguendo come filosofia di scrittura dei test quella state-based.
Ho sempre fatto un po fatica a studiare la filosofia interaction-based.
Spesso quest'ultima è molto sponsorizzata e apprezzata dai vari programmatori che incontro.
Eppure temo di non aver ancora visto un esempio virtuoso di sua applicazione.

Penso che i test interaction-based siano:

  • un buono strumento per esplorare le dipendenze e le interrelazioni tra oggetti; ma una volta scelto il diagramma di comunicazione tra gli oggetti il loro scopo si esaurisce e sarebbe meglio buttarli.
  • un pessimo strumento per quanto riguarda il supporto al refactoring e al testing vero e proprio. Questo perchè il piu delle volte si trasformano in un copia/incolla del codice applicativo. Non supportano il refactoring nel senso che se decido che un certo pattern di comunicazione tra oggetti non mi convince e voglio cambiarlo (che so, A non parla piu con B ma con C) molti test si spaccano, mentre a me interessa solo sapere che il sistema continua a funzionare con la nuova idea che sto implementando.

A margine di queste discussioni generali, JMock è davvero pessimo.
Stavamo implementando una funzionalità che, a fronte della presenza di un parametro http,
presenta un certo messaggio su una web page.

Primo difetto: no reset delle expectations

La suite di test ha una set up che imposta delle registrazioni di default:
context.checking(new Expectations() {
{
allowing(request).getUser();
will(throwException(new WebRequest.NoUserInSession()));
allowing(request).isPost();
will(returnValue(true));
...
}


La nostra chiamata è in GET e quindi il nostro test cozza con la setup. Soluzione?
JMock non permette il reset di una registrazione (ho visto che è una funzionalità richiesta
ma non ancora implementata
)
Soluzione? Tagliare dalla setup la chiamata
allowing(request).isPost(); will(returnValue(true));

e copia/ incollarla in *ognuno* dei test case precedenti. Bleah

Secondo difetto: verbosità

Altra cosa che non mi piace è che ogni test dovrebbe risaltare la parte sotto esame, nascondendo
i dettagli poco importanti. In questo esempio,
context.checking(new Expectations() {
{
oneOf(authenticator).authenticate("someMerchantId", "someUser", "somePassword");
will(throwException(exception));
oneOf(observer).notifyUserNotFound(exception);
allowing(view).toHtml(); will(returnValue("someHtml"));
oneOf(view).setError("Login fallito");
oneOf(response).writeHtml(with(containsString("someHtml")));
}
});

solo la parte evidenziata è importante ; il resto è tutta noia.

Terzo difetto: messaggio di fail poco efficace

Supponiamo nel contesto del test precedente di aver dimenticato nel codice
la scrittura del metodo observer.notifyUserNotFound .
Il test fallisce con questo messaggio di output.

java.lang.AssertionError: not all expectations were satisfied
expectations:
allowed, already invoked 2 times: webRequest.getUser(); throws
allowed, already invoked 1 time: webRequest.isPost(); returns
allowed, already invoked 1 time: webRequest.getParameter("username"); returns "someUser"
allowed, already invoked 1 time: webRequest.getParameter("password"); returns "somePassword"
allowed, already invoked 1 time: webRequest.getParameter("merchantId"); returns "someMerchantId"
expected once, already invoked 1 time: authenticator.authenticate("someMerchantId", "someUser", "somePassword"); throws
expected once, never invoked: authenticationObserver.notifyUserNotFound(); returns a default value
allowed, already invoked 1 time: notificationHtmlNode.toHtml(); returns "someHtml"
expected once, already invoked 1 time: notificationHtmlNode.setError("Login fallito"); returns a default value
expected once, already invoked 1 time: webResponse.writeHtml(a string containing "someHtml"); returns a default value

Ora, non è impossibile capire cosa non va ma è estremamente difficile.

Nessun commento: