Unit testing with the Saff Squeeze

Yesterday I read an interesting post of Kent Beck about a testing technique called Saff Squeeze. The idea is that, when you have to fix a bug in your code, first of all you need to write an "high level" test (hit the bug high) that shows the problem, then, instead of stepping in your debugger looking for the bug, you try to write a test which goes deeper and deeper in the code, in-lining method calls, until you get to the root of the problem (hit the bug low). It's like the Tai Otoshi Judo throw.


Waterfall

A real world example

As I had to find a bug in my code, I decided to try this technique.

I'm working at an ERP that exports a list of tasks to Microsoft Project. If the user chooses to create a plan scheduled from the finish date, that date has to be the maximum value of the deadlines of each task. Unfortunately there was a bug in this piece of code, so I wrote this test. (Note that, for compatibility issues, I use Java 1.4 and JUnit 3 for the project.)

 
  protected void setUp() throws Exception {
    converter = new XMLConverter();
    projectBean = new ProjectBean();
  }   
 
  public void testIfScheduleFromFinishProjectFinishDateIsTheMaximumValueOfTasksDeadlines() {
    MyDate maxDeadline = new MyDate();
 
    ProjectTaskBean task1 = createTask(1);
    task1.setDeadline(maxDeadline.addDays(-1));
    projectBean.addTask(task1);   
 
    ProjectTaskBean task2 = createTask(2);
    task2.setDeadline(maxDeadline);
    projectBean.addTask(task2);   
 
    ProjectTaskBean task3 = createTask(3);
    task3.setDeadline(maxDeadline.addDays(-2));
    projectBean.addTask(task3);   
 
    projectBean.setScheduleFromStart(false);
 
    String xml = converter.toXML(projectBean);
    TestUtils.assertContains(
		xml,
        "<FinishDate>" +
			new DateProjectConverter().toString(maxDeadline) +
        "</FinishDate>"); //Fails
  }
 

This was my "high-hit" test. As converter.toXML is a full tested method that simply creates an XML from objects via reflection, I supposed that the problem was in projectBean.setScheduleFromStart, so I wrote this new test.

 
  public void testSaffSqueezeExample() {
    MyDate maxDeadline = new MyDate();
 
    ProjectTaskBean task1 = createTask(1);
    task1.setDeadline(maxDeadline.addDays(-1));
    projectBean.addTask(task1);   
 
    ProjectTaskBean task2 = createTask(2);
    task2.setDeadline(maxDeadline);
    projectBean.addTask(task2);   
 
    ProjectTaskBean task3 = createTask(3);
    task3.setDeadline(maxDeadline.addDays(-2));
    projectBean.addTask(task3);   
 
    projectBean.setScheduleFromStart(false);
 
    //projectBean.finishDate is now public for testing purposes
    assertEquals(maxDeadline, projectBean.finishDate);   //Fails
  }
 

The test failed: I was right. Then I tried to modify the test in-lining the code of the failing method.

 
  public void testSaffSqueezeExample() {
    MyDate maxDeadline = new MyDate();
 
    ProjectTaskBean task1 = createTask(1);
    task1.setDeadline(maxDeadline.addDays(-1));
    projectBean.addTask(task1);   
 
    ProjectTaskBean task2 = createTask(2);
    task2.setDeadline(maxDeadline);
    projectBean.addTask(task2);   
 
    ProjectTaskBean task3 = createTask(3);
    task3.setDeadline(maxDeadline.addDays(-2));
    projectBean.addTask(task3);   
 
    //projectBean.scheduleFromStart is now public for testing purposes,
    //and the setScheduleFromStart method is inlined
    projectBean.scheduleFromStart = false;
    if (projectBean.scheduleFromStart) {
      projectBean.finishDate = null;
    } else {
      //projectBean.maxDeadline() is now public for testing purposes
      projectBean.finishDate = projectBean.maxDeadline();
    }
 
    //projectBean.finishDate is now public for testing purposes
    assertEquals(maxDeadline, projectBean.finishDate);   //Fails
  }
 

Then I simplified the test.

 
  public void testSaffSqueezeExample() {
    MyDate maxDeadline = new MyDate();
 
    ProjectTaskBean task1 = createTask(1);
    task1.setDeadline(maxDeadline.addDays(-1));
    projectBean.addTask(task1);   
 
    ProjectTaskBean task2 = createTask(2);
    task2.setDeadline(maxDeadline);
    projectBean.addTask(task2);   
 
    ProjectTaskBean task3 = createTask(3);
    task3.setDeadline(maxDeadline.addDays(-2));
    projectBean.addTask(task3);   
 
    //projectBean.finishDate is set to private again
    assertEquals(maxDeadline, projectBean.maxDeadline());   //Fails
  }
 

So this was time to inline the projectBean.maxDeadline() method.

 
  public void testSaffSqueezeExample() {
    MyDate maxDeadline = new MyDate();
 
    ProjectTaskBean task1 = createTask(1);
    task1.setDeadline(maxDeadline.addDays(-1));
    projectBean.addTask(task1);   
 
    ProjectTaskBean task2 = createTask(2);
    task2.setDeadline(maxDeadline);
    projectBean.addTask(task2);   
 
    ProjectTaskBean task3 = createTask(3);
    task3.setDeadline(maxDeadline.addDays(-2));
    projectBean.addTask(task3);   
 
	//projectBean.tasks is set to public
	ProjectTaskBean maxDeadlineTask =
      (ProjectTaskBean) Collections.max(projectBean.tasks, new DeadlineComparator());
 
    //projectBean.maxDeadline is set to private again
    assertEquals(maxDeadline, maxDeadlineTask.getDeadline());   //Fails
  }
 

Well, at this point I saw that the problem should be in the DeadlineComparator class, so I could get rid of the intermediate tests, rollback the changes to the ProjectTaskBean class and write a test against the buggy class (my "high-low" test).

 
  public void testDeadlineComparatorFindsMaxDeadline() {
    MyDate maxDeadline = new MyDate();
 
	Collection tasks = new ArrayList();
 
    ProjectTaskBean task1 = createTask(1);
    task1.setDeadline(maxDeadline.addDays(-1));
    tasks.add(task1);   
 
    ProjectTaskBean task2 = createTask(2);
    task2.setDeadline(maxDeadline);
    tasks.add(task2);   
 
    ProjectTaskBean task3 = createTask(3);
    task3.setDeadline(maxDeadline.addDays(-2));
    tasks.add(task3);   
 
	ProjectTaskBean maxDeadlineTask =
      (ProjectTaskBean) Collections.max(tasks, new DeadlineComparator());	
 
	//All fields and methods in projectBean are set to their original visibility
	assertEquals(maxDeadline, maxDeadlineTask.getDeadline());   //Fails
  }
 

So this was time to fix-up my code! ;-)

Conclusion

Kent Beck says: "Squeezing encourages good design. If inlining creates too big a mess, back up, clean up the called method, and inline again. Even if I received no other benefits from squeezing, the design improvement would be worth it."

Well, if it's definitely true that you need a good design (or, at least, a not so bad one) for squeezing, I think that it could be difficult to refactor your code when you're doing it: after all, you have a red bar.

Anyway, it seems a nice technique to explore your code without digging in the debugger too much and, this time, it worked fine. :-)

Italian Agile Day 2009 - Le mie impressioni

Un altro Agile Day è passato ed eccomi nuovamente a scrivere un sommario della mia esperienza.

Peter Stevens - Fixed Price Projects With Agile – It can be done!

Possiamo stimare a priori tempi e costi di un progetto agile? Possiamo applicare i metodi agili quando il costo ed il tempo sono scolpiti nella pietra? Certo, sarebbe bello poter vivere in un mondo in cui tutti i contratti fossero tagliati sul concetto di iterazione, di sviluppo incrementale. Purtroppo ci sono occasioni nelle quali dobbiamo stimare i costi, nei quali dobbiamo sapere se riusciremo a rilasciare entro una certa data, magari remota. Chi, ad esempio, sviluppa il software per gestire grandi eventi come i mondiali di calcio non può chiedere di spostare la data della finale perché lavora in un team agile e ha bisogno di una nuova iterazione per completare le storie!

Peter Stevens ritiene che lo sviluppo agile si possa adattare a queste situazioni, anzi che sia il miglior metodo da adottare. Questo perché è l'unico sistema per valutare in corso d'opera la velocità alla quale lo sviluppo si sta svolgendo.


Waterfall

Per affrontare progetti di questo tipo occorre però:

  • un cliente del quale fidarsi;
  • una pianificazione che lasci "cuscinetti" liberi nei quali compensare eventuali ritardi;
  • criteri certi (e automatizzabili) per determinare quando una funzionalità è conclusa;
  • criteri per stabilire l'importanza delle varie funzionalità per realizzare prima le storie più importanti e consegnare alla fine del progetto, quando la pressione è maggiore e le energie si esauriscono, le funzioni di minor rilievo;
  • un team esperto.
  • Maggiori dettagli sulla presentazione si trovano qui.

Alberto Brandolini - Possiamo fare di meglio

La presentazione mattutina di Alberto Brandolini è partita da una serie di provocazioni ad effetto sui seguenti temi.

  • Spesso si rompe il rapporto di fiducia tra il software e chi lo usa, sia egli un utente finale o chi fa parte dello stesso team di sviluppo. Scatta allora il meccanismo della "complessità compensativa", cioè degli strani trucchi per aggirare i comportamenti errati dei sistemi.
  • A volte gli sviluppatori accettano passivamente i requisiti che sono dati loro: ma chi dà questi requisiti è il vero esperto del problema, o, se lo è veramente, ha avuto modo di pensare ai requisiti in maniera critica? I requisiti non sono in alcuni casi semplicemente la mummificazione di un processo che potrebbe essere invece migliorato?
  • Nei progetti software può nascere quello che è stato definito "technical debt". Brandolini suggerisce una metafora più calzante per questa progressiva deriva nella qualità del codice: inquinamento, ovverosia un processo estremamente dannoso, difficilmente reversibile e con un tempo di riparazione incalcolabile.
  • A volte lo sviluppatore non ha abbastanza umiltà per capire di dover approfondire il dominio da un punto di vista non semplicemente informatico.

Dopo aver introdotto questi spunti di discussione, Brandolni ha lasciato la parola all'uditorio. L'esito di questo esperimento di "terapia di gruppo" a mio avviso non è però stato brillantissimo: purtroppo non è emerso molto di interessante, se non una collezione di aneddoti sul nostro lavoro.

Pietro Brambati - ASP.NET MVC: Programming & Testing

Dato che prima del pranzo non mi sembrava vi fossero relazioni degne d'interesse, mi sono infilato nell'auletta dedicata alla presentazione dell'ambiente di test creato per .NET MVC. Io odio gli strumenti di sviluppo Microsoft!!! Questo mio sentimento nacque anni fa quando Visual Basic 6, con un fantastico "Il controllo OCX non è registrato correttamente", mi fece affogare in un oceano marrone nel bel mezzo di una demo ad un centinaio di persone. Nonostante questo, mi è sembrato che il supporto di Microsoft ai test unitari sia discreto e che l'oratore, molto preparato, sia riuscito a dare a i colleghi della sponda Microsoft un buon numero di informazioni utili su come scrivere codice in modo più efficace. L'impressione comunque è che con questo prodotto Microsoft stia svolgendo un diligente compitino per conquistarsi la medaglietta "agile" e la relativa fetta di sviluppatori.

Alberto Quario - Scenario testing

Questo è forse stato l'intervento più interessante della giornata. All'Agile Day si è molto parlato di processi, ma ci si è un poco scordati del codice. Alberto Quario ci ha riportati nel cuore del problema, citando le parole di Gerard Meszaros: I test possono diventare il collo di bottiglia dei processi agili. (Ma allora non le sparavo così grosse quando parlavo dei test come palle al piede!). Uno dei principi da seguire per evitare che i nostri test divengano dei mostri incomprensibili, difficili da scrivere, leggere e manutenere è il seguente: nel corpo del test deve andare tutto e solo quanto è strettamente necessario per la sua comprensione.

Alcuni consigli sono quindi:

  • radunare in metodi dal nome esplicito (operational methods) i dettagli di inizializzazione dello scenario del test
  • se il test prevede, come nel caso si utilizzi DBUnit l'uso di file di inizializzazione, magari in XML, parametrizzarne la creazione rendendone chiaro l'intento all'interno del test

Francesco Mondora: vivere in un angolo proattivo

Francesco Mondora nel corso della sua presentazione ha detto di apprezzare eventuali riscontri da parte del pubblico. Ecco quindi il mio, che è purtroppo negativo. Il tema trattato mi è apparso fumoso, ed il tono ieratico utilizzato era probabilmente fuori luogo. Ma forse il problema è solo mio che non sono riuscito a capire cosa si volesse comunicare...

Alberto Brandolini - Introduzione al Domain Driven Design

Ecco un'altra gran bella sessione, nella quale Brandolini ha descritto con abilità e preparazione i principi del Domain Driven Design.

Entrare nel dettaglio di quanto visto è molto difficile, data la mole di informazioni trasmesse. Mi piacerebbe comunque segnalare il concetto di Bounded context, ossia il limite entro il quale il significato di un'astrazione del dominio è non ambigua, o, per usare una visione più orientata al codice, il limite di applicabilità di un gruppo di classi del sistema. Mi sono imbattuto in questo problema quando, nella mia presentazione su Scala, ho parlato di oggetti che ingrassano a dismisura. Da quanto ho capito, anche nel DDD ci si occupa di questo problema, ma da una prospettiva "sistemica": occorre definire l'ambito entro il quale è opportuno riutilizzare il codice che definisce un'astrazione. All'esterno di tale ambito è meno costoso riscrivere parte delle astrazioni ed accettare una certa duplicazione nei sistemi. Il tema, interessante e complesso, è alla base del fallimento di grandi utopie Object Oriented come il progetto San Francisco di IBM. Anzi, a mio avviso mette in discussione tutti i miti dell'Object Orientation a partire dalla riusabilità, ma forse converrà parlarne in un'altra occasione.

Conclusioni

Anche quest'anno l'Italian Agile Day non ha deluso le aspettative: relazioni quasi tutte di altissimo livello, con l'apprezzabile idea di aprire a contributi internazionali. Se proprio si vuole fare un piccolo appunto, si dovrebbe parlare di un'eccessiva enfasi posta sugli aspetti di processo a scapito delle sessioni "pratiche", anche se, come avete potuto leggere, i "puri" sviluppatori come me non hanno avuto occasione di annoiarsi.

Grazie a Marco Abis ed ai ragazzi dell'XPUG Bologna per l'enorme e riuscitissimo sforzo organizzativo.
(Ah, avete già donato qualcosa all'Agile Day? ;-) )

Functional setter Java idiom

The way of organizing the code I will show in this post is very common, but, perhaps, it's useful to give it a name, and I will call it the "functional setter java idiom". Let's see.

In java there are no named parameters, i.e. parameters that I can pass to a method in the order I like calling them with their name. For example in Visual Basic for Applications we can sort some Excel cells this way:

 
SelectedSheets.PrintOut Copies:=1, _
    Preview:=True, _
    PrintToFile:=True, _
    Collate:= True
 

Note that the order in which I set the parameters is arbitrary, and that some parameters can be omitted, having it assigned with default values.

Now let's suppose we need to create an object with a long series of parameters (OK, I know, it's a code smell, but, please, close your nose for a while):

 
PrintType printType = new PrintType(1, true, true, true);
 

Well, not a good piece of code! Especially the sequence of boolean parameters is a mess. Moreover, sometimes I'd like to create my object with less parameters, having the others set with default values. I could create many constructors, but this would increase the complexity of my program.

I could solve the problem using some setters:

 
PrintType printType = new PrintType();
printType.setCopies(1);
printType.setPreview(true);
printType.setPrintToFile(true);
printType.setCollate(true);
 

Uhmm, better enough, but what if I need to assign my object to a constant, i.e. a final static field? I should write:

 
public final static PrintType DEFAULT_PRINT = new PrintType();
 
static {
	DEFAULT_PRINT.setCopies(1);
	DEFAULT_PRINT.setPreview(true);
	DEFAULT_PRINT.setPrintToFile(true);
	DEFAULT_PRINT.setCollate(true);
}
 

Mamma mia, this code is ugly! Maybe I could improve it just a bit using some functional setters, i.e. setters that have a return value, as a function. In the PrintType class I should write:

 
public PrintType setCopies(int copies) {
	this.copies = copies;
	return this;
}
 
public PrintType setPreview(boolean value) {
	this.preview = value;
	return this;
}
 
public PrintType setPrintToFile(boolean value) {
	this.printToFile = value;
	return this;
}
 
public PrintType setCollate(boolean value) {
	this.collate = value;
	return this;
}
 

Now the initialization of my static field becomes:

 
public final static PrintType DEFAULT_PRINT = new PrintType()
	.setCopies(1)
	.setPreview(true)
	.setPrintToFile(true)
	.setCollate(true);
 

This is not so bad, don't you think?

Italian Agile Day 2009


Italian Agile Day 2009

E' stata annunciata la data per lo svolgimento del sesto Italian Agile Day: si terrà il 20 novembre 2009 a Bologna. Come sempre la conferenza si annuncia interessantissima: vi darò aggiornamenti nei prossimi giorni.

La tessera mancante

E' stata una coincidenza fortunata. Nei giorni scorsi stavo scrivendo del codice, per la prima volta da tempo del codice altamente algoritmico. Da bravo scolaretto XP, avevo creato una serie completa di test, avevo rifattorizzato estraendo metodi, rinominando variabili, abbassando il numero ciclomatico. Le classi erano piccole, i metodi corti, eppure il codice, pur funzionando egregiamente, in qualche modo mi infastidiva.
Poi alla scorsa riunione dell'XP-UG di Milano, Matteo Vaccari ha parlato brevissimamente dell'argomento, ed è stato illuminante. Da qui il suo interessante post.
In particolare una frase pesa come un macigno su quanti, come me, pensano di essere agili solo perché fanno TDD (o qualche altra pratica XP):
"TDD could be a crouch that allows you to give up finding a good design and settle for a mediocre one".
Puoi fare tutto quello che dice il libro bianco di Kent Beck, ma forse non ti accorgi che c'è una tessera mancante....

Il crepuscolo dello Sviluppo Agile?

In un post molto interessante di James Shore leggo "Because Scrum works in short cycles and doesn't include any engineering practices, it's very easy for teams using Scrum to throw out design. Up-front design doesn't work when you're using short cycles, and Scrum doesn't provide a replacement. Without continuous, incremental design, Scrum teams quickly dig themselves a gigantic hole of technical debt."
Ovviamente l'autore pensa che ciò sia dovuto ad un cattivo impiego di Scrum, nel quale vengono tralasciate le seguenti pratiche:

  • spazi di lavoro condivisi o tecniche che enfatizzino al massimo la comunicazione;
  • contatto strettissimo con il cliente;
  • gruppi di lavoro interfunzionali;
  • pratiche di ingegneria del software prese da XP (EXtreme Programming): penso a refactoring, TDD, continuous integration.

Ma ciò sarà davvero sufficiente a colmare il debito tecnologico maturato con l'abbandono dell'up-front design?
Qualcosa su questi temi si sta muovendo, ne sia prova il recente thread sulla lista extremeprogramming-it dal titolo Perche' il design emergente non e' robusto?. Ovviamente scopo del thread era quello di cercare di confutare questa tesi, ma mi sembra che in ogni caso dei dubbi stiano emergendo.

Ora devo scappare, ma concludo segnalando un questo post al vetriolo sull'argomento. Forse qui si esagera, però.....

Design Object Oriented: Factory method

Spesso ci accontentiamo di scrivere programmi funzionanti e questo sarebbe già un successo, direbbe qualcuno. ;-) A volte però non ci accorgiamo che i nostri programmi, anche se svolgono il loro compito, non contengono un codice elegante, non hanno cioè un buon design. La ricerca dell'eleganza del codice non è un mero esercizio estetico. Scrivere bel codice significa avere programmi facili da comprendere, quindi facili da cambiare e da correggere.
Vedremo qui e nei prossimi post alcune tecniche che, se bene applicate, possono aiutarci a raggiungere questi obiettivi.
Continue reading ‘Design Object Oriented: Factory method’ »