Moving development to the extreme

For sparing time, you end up wasting all time.

A colleague, when first time talked my about XP (more than 10 years ago), said something like "It's wonderfull. You are much quicker. You know it? Beautifull: you write tests, and then ...", and me "tests?!?", "yes, then the system continue to work along time .. tests .." .. and he went on talking about tests as if those were a thrilling job for a programmer. I just do not understand the needs of the test, I was studying and not coding for last 6 years, being at university. But really I should have understood it, few month before I was coding a java icq client, and for knowing where I was and in order to divide development in chunks, I started to write an icq server in C with pthreads and mysql db for linux .. and server was not what I needed, but it take more time than java code!

Tests are for this, spare time. The main functionality for a piece of code is that it does what it is supposed to do, every time,Per risparmiare tempo, finisci con lo sprecare il tempo.

Un collega, quando mi parlo per la prima volta, piu di 10 anni fa, di Extreme Programming, mi disse "e bellissimo. Fai molto piu veloce. Lo conosci? Troppo bello, scrivi i test ...", "i test?", "si, poi il sistema deve funzionare sempre .. i test .." .. e continuava a parlare dei test come se fossero qualcosa che riguarda la programmazione. Io non lo capivo, ero uscito dall'universita e non e che ti fanno programmare molto, ma non tenevo conto che pochi mesi prima avevo tentato di sviluppare un client icq in java e per andare avanti e dividere lo sviluppo in blocchi avevo iniziato a scrivere un server icq in C con pthread e db mysql. E il server icq non era il prodotto che dovevo realizzare, ma mi aveva preso piu tempo del codice java!

I test servono a quello, a risparmiare tempo. Il fatto e che il codice deve essere testabile, chiaramente, senza dubbi e per tutte le casistiche interessanti. Se si presentano problemi su di un prodotto, prima bisogna sapere come poter riprodurre il problema, poi si puo procedere a correggerlo. Il test stostanzialemente definisce la procedura.

Il bello dei test e che rimangono, cosi non puoi introdurre nuove funzionalita invalidando i test. Cosa succede, ad esempio, se un protocollo cambia di versione e la vecchia versione deve essere supportata? Si aggiungono nuovi test e tutto deve andare.

Ok, mi sono convinto, almeno io.

Ho visto ci sono buoni libri sul come scrivere test effettivi e devo leggerne sicuramente, ma per il momento scrivo qualche considerazione che sto trovando nel predisporre un sistema completo di test, con ambiente di sviluppo rapido.

Installare PHPUnit e scrivere i test

Il codice deve essere predisposto ad essere testato, quindi oltre ai metodi strettamente necessari per il funzionamento, vanno definiti metodi ad uso dei test. Ad esempio se un metodo fa il parsing degli header http, c'e' bisogno che questo metodo accetti anche un array come parametro, che, se valorizzato, salta il passo dell'acquisizione dell'array degli headers prendendo direttamente l'array al suo posto.

D'altra parte posso testare se il sistema fa veramente il parsing dell'header facendo una chiamata curl ad una url che mi restituisca gli header inviati come testo, cosi basta fare un match per determinare se il sistema riceve gli header http correttamente. (si puo controllare a mano, ma la parola d'ordine e sempre automatizzare)

MakeGood e un plugin per eclipse

http://piece-framework.com/projects/makegood/wiki

dopo averlo installato ho avuto problemi a farlo funzionare, sembrava mancassero librerie ed altro. C'e da definire il php executable e se gli si passa il solo php.ini della cli non prende tutte le configurazioni dei moduli aggiuntivi (tutto cio che e in /etc/php5/cli/conf.d). Per questo ho creato uno script bash

{syntaxhighlighter brush:bash}
#! /bin/sh
cd ~
cat /etc/php5/cli/php.ini >~/inieclipse.ini
cat /etc/php5/cli/conf.d/* >>~/inieclipse.ini
{/syntaxhighlighter}

e passato ~/inieclipse.ini come ini per l'eseguibile php in eclipse. Non mi piace, ma trovare la soluzione mi stava facendo perdere piu di un ora e tra le buone pratiche c'e quella delle scadenze per task: se non riesci a farlo nel tempo stabilito giraci attorno.

(macchinoso ma funziona, trovare come risolvere il problema)

Funzionalita e test vanno portate avanti di pari passo, ci vuole certamente una pianificazione iniziale per evitare di rifattorizzare continuamente, ma e piu che sensato che ogni volta che viene scritta una funzionalita venga definito contestualmente il test di essa.
("Design Pattern Explained" - Alan Shalloway & James Trott, da una buona metodologia per avere una visione olistica del progetto. Suggeritemi altre letture)

Per una buona pratica di progettazione orientata alle interfacce (o a contratto) e meglio invertire le cose: definire i requisiti del test, implementare il test. Il test puo evidenziare la conformita delle interfaccie (anche se solitamente il linguaggio supporta questo nativamente, in javascript ad esempio potrebbe tornare utile).

Essendo ogni test un caso a parte, lo stato del sistema alla fine del test deve rimanere uguale allo stato iniziale. Vale a dire che se nel database c'erano dei dati in uno stato, e per effetto del test lo stato e cambiato, questi dati devono essere riportati allo stato di partenza.

setUp() e tearDown()

Per questo i method setUp() e tearDown() sono utilissimi, e vanno definiti tanti test case per quante versioni di setUp() e tearDown() sono necessarie.

setUp() registra lo stato attuale e imposta quello necessario al test.
tearDown() riporta tutto il modificato allo stato iniziale.

Supporto a db: DbUnit

Il sistema che si va a testare e generalmente complesso, e quindi avere molte interazioni col dbms. Per questo e bene usare il supporto ai database DbUnit per:

1. stabilire uno stato iniziale
2. controllare quali sono le variazioni di stato (con assert) durante il test
3. ripristinare lo stato di partenza

E spiegato in http://www.phpunit.de/manual/3.6/en/database.html

... to be continued
in every scenario. The only requirement for a system is that it does all the functionality in the right way, all time, and in all scenario. Defined all the test, when adding a new functionality, old test have to pass. If there is a bug, it is a scenario that is not in the test cases defined. Define it, and fix the code to pass it.

For example what happen if a new protocol version is supported by a server? Old test have to works as expected. New version has to be detected.

Ok, I convinced myself at least.

I see there are a number of book on writing good test cases, but now I start to write some experience on this field. I want to do more, with less time, and do it better.

Installing PHPUnit and writing tests

The project code should be ready for test, so, in a OO, project class should have method for use in tests, or variant version of some method. For example if a method needs to parse http headers before doing its task, it is needed a default variant NULL that, if setted, is used as headers array.

Then it should be tested if system can parse http headers correctly, I'll do this by a curl call and a test script (that use internal system method) that parse header and print it in a readable format. I can assert on text output and header input.

MakeGood is an Eclipse plugin
http://piece-framework.com/projects/makegood/wiki

after installed I got problems to make it works, I read documentation, but it looked as libraries was missing, and other. In Eclipse it has to be defined a php executable cli and without giving php.ini path it wont works, giving it is not enough in Ubuntu (miss what's in /etc/php5/conf.d). I wrote this

{syntaxhighlighter brush:bash}
#! /bin/sh
cd ~
cat /etc/php5/cli/php.ini >~/inieclipse.ini
cat /etc/php5/cli/conf.d/* >>~/inieclipse.ini
{/syntaxhighlighter}

and then used ~/inieclipse.ini as cli ini. (not elegant, but rapidity is a requirement, @todo: find how to make it work)

Functionality and tests has to be developed in parallel. It is needed an initial plan (design), for having a working all the architecture, but every time a new functionality is added the corresponding test has to be wrote.
("Design Pattern Explained" - Alan Shalloway & James Trott, propose a good methodology to have an holistic view of the project. Other book suggesting are welcomed)

TDD say to reverse things: wrote test, the implement functionality. For design requirement, such as contract requirement, it is a good practice.

Being every test an isolated case, system state at end of test's running have to be the same as starting state. C'est a dire, if db has data in a state before test run, after test has ran it has to have the old state.
(this is my requirement, I want to see interface and make test, and want to execute test on working copy, maybe not a good practice, but I am learning)

setUp() and tearDown()

For this setUp() and tearDown() are very useful methods, and has to be defined for each test cases, a test case class has to be defined for every different initial state requirement.

DB support (DbUnit)

The system to be tested is naturally complex, so it has a lot of interaction with dbms. For this there is DbUnit for:

1. set initial state
2. test db state during test
3. restore starting state

This are explained in http://www.phpunit.de/manual/3.6/en/database.html

... to be continued