Tuesday, June 27, 2006

Test this first...

I've got another email from an agile zealot today. This time, it was about "test first" and why we should never write code without writing some automatic tests first.
This is, in a sense, funny. "Test first" is extremely similar to a subset of Design By Contract. When you design your test cases, you're basically coding the expected post-conditions for method calls. Funny thing is, I've been a strong supporter of Design by Contract and, more generally, of the liberal use of assertions inside code. In most cases, however, I've found two kind of assertions to be the most efficient (meaning: you get real value from the effort you put in). One of them is the precondition. I want to trap programming errors in the calling site easily, and that's what preconditions do. Another kind is the generic assertion, used between portions of complex algorithms, to make sure that my understanding of program state was right. Long distant comes the postcondition, for a very simple reason: it's the hardest to write, sometimes so hard that is makes no economic sense to write one.
I'll skip a few words on how Design by Contract zealots tended to say mostly the same things as the Agile zealot do (only worse: never code a class if you don't have an invariant ready, never code a method without pre/post conditions, and so on). Zealots have this common tendency of confusing means with ends, ultimately jeopardizing the concepts they try so heavily to promote. [I'll even skip some linking back from Design by Contract to formal methods, weakest preconditions and the like (it would be fun, anyway, to trace some XP concepts back to formal methods :-))].
Enough talking, let's get some code running. Your mission, should you choose to accept it, is to code your way through this little problem in a test-first way. You'll have a class, say Canvas, that can display bitmaps, and where you can also get and set the color of each pixel. Your program must load a user-specified bitmap into the canvas, then draw a circle with user-specified center, radius, and color. The circle must be drawn over the bitmap, with smoothing. You have to implement the algorithm for that (you may borrow from the literature, using e.g. the Bresenham algorithm, but you're on your own about the smoothing).
Please, test this first. No code before you have some meaningful test in place. Meanwhile, someone else will use a code-first :-) approach. Let's see what happens ;-).
Winning later because of test automation? Maybe. Now your algorithm should also take a user-specified pen thickness as a parameter. Are your tests ready for that? Easily changed? C'mon.
Of course, test-first is useful in some cases, and has a very desirable side-effect: classes tend to be designed with the caller in mind, which is a good design concept. (Wouldn't be good anyway to know about design concepts and not just about programming techniques? :-))).
It's just that, like any other technique, we should use it only when it makes [economic] sense. Now, that would be agile :-).

44 comments:

Marco Abis said...

well, zealots are zealots therefore they are usually already wrong (even if sometimes I might look like one ;-D).

Of course, test-first is useful in some cases [...] we should use it only when it makes [economic] sense

this is why people started using two different descriptions: test driven and test first: http://blogs.pragprog.com/cgi-bin/pragdave.cgi/Practices/TestDrivenOrTestFirst.rdoc

No code before you have some meaningful test in place. Meanwhile, someone else will use a code-first

What's the difference with:

No code before you have designed. Meanwhile, someone else will use a code-first

?? ;-)

Test driven is just a way to investigate design.

Romano Scuri said...

[OT] Chissà perché parlando di zeloti mi era venuto in mente proprio Marco Abis... ;-))

Marco Abis said...

[OT] Probabilmente perche' non abbiammo mai avuto modo di parlare faccia a faccia.... ;-))

Carlo.Pescio said...

Marco, I'm glad to see you :-). As usual, I suspect we agree on a deeper level, but that's no excuse :-)) for not putting up a little fight :-). After all, what's the point of kata if you never move to kumite? :-)

The distinction between test-first and test-driven is rather moot in this specific case (see below), but still interesting in general. Test-first is a rather normative practice: it tells you what you have to do and, by and large, how to do it. Test-driven is a broader concept, much less specific, and doesn't tell you how to do it. As usual, when we move from normative to descriptive, we gain and we lose. I'll try to postpone this for another time (I'd like to add something about metaphors as well :-)

Back to my example: you didn't try either the test-first or test-driven approach with it! Otherwise, you would have seen that they give you no useful information. In fact, you said it rather clearly (not sure you wanted to :-))):
"Test driven is just a way to investigate design.
I would say a rather limited way :-). It would be hard to justify its importance in XP if its only aim was to investigate design.
Anyway, in my specific example, thinking about test cases won't give you any useful perspective on solving the problem. Implementing any kind of useful test case will prove much, much more difficult than implementing the real stuff (just try it). Test cases will break as soon as you change requirements (adding a pen thickness). And fixing automatic tests will prove a nightmare again.
So, in this specific case, you'll get no value (or a negative value) from test-driven (and you'll be simply paralized by test-first).
The same can be argued for an architecture-driven, and even for a design-driven approach. Not really in this case :-), but we can find specific examples where they give no particular value, or even a negative value (it's harder than with the test-first thing :-). In this specific case (so that I don't seem to avoid your direct question :-))), I would expect a code-first approach to give me back a function to draw a smooth circle over a bitmap. I would expect an ambitious :-)) [OO] design-first approach to give me back a Circle class where points are calculated and plotted without smoothing, and a SmoothedCirle subclass where only a virtual function is redefined, to plot pixels with smoothing. No paralysis, and we may want to talk about time and value :-).

I guess by now you know my point about agile methods: they just aren't agile enough. By trying to nail down software engineering to a small set of simple code-centric practices, they're losing too much value along the road, and bringing an excessive baggage in some cases (like prescribing test-first). There is no substitute for a deep and wide knowledge, and the wisdom to apply it properly (this would also bring up the issue of hierarchy, but that's too long a story). I want the freedom to (e.g.) use even the waterfall from the very beginning, if I see it as the most appropriate approach for a specific project, in a specific company, in a specific market. I remember Steve McConnell saying that the real difference is not between an approach and another, but between informed and uninformed choices. That's something I can strongly support...

---------

Romano: Marco e' una persona che ha sicuramente la mia stima (come ho gia' avuto modo di dirgli in passato), ed e' decisamente diverso dalla persona che ha indirettamente dato origine al mio post :-).

Matteo Vaccari said...

A quick glance to wikipedia turns out that Bresenham applies to straight lines. I suppose you can generalize it to curves, but wouldn't it be fairer to point to the exact version of the algorithm that is the object of your challenge?

The fact that you talk about an arbitrary bitmap underneath the circle seems to hint that the smoothing should interact with the colors of the underlying bitmap. Is that correct?

francesco cirillo said...
This comment has been removed by a blog administrator.
francesco cirillo said...

You may be interested in my feedback


http://www.xplabs.it/201015.html


Regards,

Francesco Cirillo

Anonymous said...

Nice discussion, anyway I didn't understand why the bitmap algo should be difficult to implement by TDD.
Maybe I'm missing something but I could start my test in 3 seconds from now... only I don't know if I could produce a nice smoothed circle... but this is independent from how I choose to program and very dependent on my actual programmer's capabilities! ;)

Uberto

Paolo "Nusco" Perrotta said...

Agile methodologies don't advocate testing everything, even admitting that "testing everything" means anything at all. Most agile methodologies stress testing, but not even XP prescribes 100% testing. If you consider methodologies such as Crystal Clear too prescriptive, then I'm not sure that we mean the same thing by "methodology".

I'm starting to fear that labeling myself an "agilist" automatically marks me as a "strict recipe follower" guy. Wasn't it supposed to be the other way round? To me, "pure" TDD is a useful mind tool. It prevents my laziness from taking over ("This is just too difficult to test - let's just code it"). I only give up when I find that testing makes no economic sense, and no earlier than that. How is that zealotry?

In other words, as Carlo hints: are we debating over our semantics, or our sintaxes? ;)

Carlo.Pescio said...

Egregio Dott. Cirillo,
La ringrazio per aver ribadito, a beneficio di tutti, le posizioni dei "puristi" XP.
E' un peccato che abbia preferito farlo in astratto, attraverso le consuete pontificazioni che troviamo in innumerevoli libri, articoli, pagine web di ogni tipo, e non facendoci vedere in concreto, su questo esempio specifico, come il TDD aiuterebbe ad arrivare ad un buon design ed a garantire, come Lei dice, "una significativa riduzione di difetti", magari anche a "ottenere maggiore produttivita'".
Produttivita' che peraltro era il fulcro anche di un altro aspetto che Lei ha voluto diversamente interpretare. La mia "preoccupazione" non e' certo che i test case si rompano durante lo sviluppo. Nel caso concreto, volevo evidenziare come la grande fatica che occorrebbe profondere nello sviluppo di test case utili per il cerchio a spessore 1 non potesse essere efficacemente ammortizzata passando ad un cerchio di spessore maggiore. Riportando, quindi, su un esempio concreto le consuete teorizzazioni sulla "maggiore produttivita'".
Vede, la mia tesi, che evidentemente non Le e' risultata chiara, e' che non esiste un approccio intrinsecamente superiore agli altri in ogni specifica situazione, e che autolimitare il proprio repertorio di tecniche a quelle (ad es.) di XP significa non essere affatto "agili".
Cosi, quando io dico "I want the freedom to (e.g.) use even the waterfall [...]" (notare e.g. ed even, a sottolineare che si tratta di un esempio qualunque), non sostengo la superiorita' incondizionata (ci mancherebbe :-) dell'approccio a cascata su altri. Dico (lo ribadisco) che limitare il proprio repertorio di tecniche, o per converso, ritenere di aver trovaro l'approccio universale grazie a poche elementari tecniche code-centric, e' a mio avviso sbagliato.
Di nuovo, come ho peraltro detto in modo (spero :-) piu' che chiaro, cio' non significa che il TDD non sia mai utile: significa solo che esistono casi in cui non da' reale valore. Lei, purtroppo, non ci ha concretamente dimostrato il contrario.
Detto questo, devo dire che faccio un po' fatica a non rispondere punto su punto al suo commento. Ci sono talmente tante affermazioni discutibili (come "nello sviluppo in TDD non ho bisogno di usare asserzioni nel codice") che diventerebbe la solita diatriba, temo piuttosto sterile.
Faccio un po' fatica, purtroppo, anche a non leggere nella Sua un tono didascalico un po' troppo marcato (per usare un eufemismo), intervallato peraltro da divertenti insinuazioni, ma non importa. Chi legge giudichera' il merito e la solidita' delle rispettive posizioni :-).
Tanti anni fa, su Harvard Business Review, appariva spesso una bellissima pubblicita'. Parlava di chi cita spesso L'Arte della Guerra ma, alla fin fine, si guarda bene dal prendere in mano la spada (limitandosi, al piu', a dire che non vede problemi nel farlo). L'obiettivo del mio post era proprio di passare dalle pontificazioni ad un elementare esempio concreto su cui discutere senza bisogno di filosofeggiare. Tutto il fiume di parole (che fa decisamente sorridere) speso a spiegarmi che la "sfida" non e' ben posta perche' "voi in XP siete abituati ecc ecc" [ma non dovreste essere agili ed adattarvi?]) sarebbe stato speso molto meglio mostrandoci come, nel caso specifico, il TDD aggiunga valore. Non stiamo parlando di una disfida epocale di cui occorre chiarire con cura regole e regoline, ma di un banale problemino di programmazione!
Spero invece che Lei abbia notato lo smile ":-)" dopo "ambitious design". Con questo, e con la scelta di scrivere ambitious e non appropriate, elegant, ideal, perfect, o altro che suggerisse una connotazione positiva, volevo ovviamente sottolineare il fatto che non si trattava di una soluzione ideale. Essendo una semplice risposta ad un commento, e non un articolo sul design, non era certo il luogo ed il momento per discutere le differenze tra usare il pattern template method (come nella possibilita' citata), strategy (per estendere il tipo di disegno senza derivare da Circle), decorator (per avere decorazioni multiple in cascata), una combinazione di questi, un design banale monoclasse, ecc. Ad ogni modo, temo che la mia risposta a Marco fosse meno chiara di quanto speravo, visto che Lei l'ha letta come una implicazione che il design "ambitious" fosse il risultato auspicato da un metodo "piu' agile". Era comunque una risposta personale, e con Marco abbiamo gia' avuto modo di discutere di simili argomenti: dubito che lui l'abbia male interpretata.
Termino qui, proprio per evitare di iniziare a ribattere su tutti i punti, cosa che mi porterebbe esattamente dove il post originale voleva evitare di andare: sulla teoria filosofica, anziche' sulla pratica. La ringrazio comunque per il Suo feedback, che penso aiutera' chi legge a mettere in prospettiva le mie parole, ed a capire meglio tante cose.
PS: chi mi conosce sa bene che non amo prendere le cose troppo sul serio, il che mi aiuta anche ad evitare toni troppo cattedratici. Cosi', alla domanda "Lei con quale processo ha intenzione di lavorare? Con quale team, organizzato come?" penso di poter sinceramente rispondere che considerando la magnitudo della sfida (parola che peraltro non ho mai usato :-), pensavo di affrontare il problema con un team formato dalla mia sola mano sinistra, essendo la destra impegnata a reggere un bicchiere di coca cola (vista la stagione calda). A richiesta, come dimostrazione di agilita' :-)), sono pronto a cambiare mano in ogni momento :-).

Carlo.Pescio said...

Uberto: a test is (by definition) something that can either fail or succeed, revealing if the called code is wrong or right. If you feel you can successfully write meaningful, helpful (and hopefully automatic) tests for the "smoothed circle over a bitmap" problem, tests that can tell if your implementation is correct, and also guide you toward a better design (after all, it's TDD, folks :-), why don't you share them with us?

Carlo.Pescio said...

Paolo: indeed, I can't see any zealotry in your words. In fact, I never said that all agile guys are zealots.
I'll repeat my point once more: True agility cannot be pursued with a limited repertoire of techniques, applied indiscriminately to every kind of project and organization. True agility cannot be pursued by shoehorning the project and the organization into a method. This is, unfortunately, what too many zealots would like us to do.

Carlo.Pescio said...

Matteo: long time no see!
Bresenham is quite famous for his line and circle algorithms. A quick search on google with keywords Bresenham circle algorithm should give back a wealth of information.
Note, however, that this is just one among many algorithms. You can choose anyone you want.
As you properly understood, a smoothed circle is blended with the underlying bitmap (would be too trivial otherwise :-).

Paolo Polce said...

you can find a copy of this comment here: http://aboutagile.blogspot.com/2006/07/agile-zealotry.html

Hi,
your code-first approach "a function to draw a smooth circle over a bitmap" sounds like a spike to me: quick and dirty, useful sometimes to investigate how a problem could be solved, but definitely not the way to go ;-).

I would code-first to analyze something I'm not particularly familiar with, not more than a couple of hours tho'. I would then have enough knowledge to step back from the code, estimate my task and then proceed with TDD.

The ambitious design-first approach you mention, well... it just doesn't sound ambitious at all ;-).
It starts with a Circle, then a SmoothedCircle, then probably a SmoothedSomethingElseCircle, then a SmoothedCircle... eventually ending up with a CompletelyMessedUpCircle :-)) [tight coupling]

Incremental TDD/coding helps developers keep the entropy low, thus making the system ready for future changes (towards any direction).
If you said your code-first program could be easily changed I wouldn't trust you. :-)

There is one thing I agree with you tho', which is recognizing that plenty of developers have chosen agile methodologies as a religion.
They blindly apply the principles of their religion, regardless what context they're in.

Nice post by the way... ;)

Paolo Polce

francesco cirillo said...

http://www.xplabs.it/201016.html

Regards,

Francesco Cirillo

Matteo Vaccari said...

Gentlemen,

i miei 2 eurocent. E scusate se non proseguo in inglese...

Carlo, la "sfida", o "missione", come vogliamo chiamarla, non mi sembra molto ben posta. Come facciamo a stabilire se ho avuto "successo"? O forse la tua domanda è semplicemente, "mostrami un po' come fai ad affrontare un problema come questo con il tuo TDD"?

Mi pare di capire che la tua obiezione ad usare TDD in questo contesto è che si farebbe una gran fatica a scrivere i test, e poi sarebbero fragili e ci toccherebbe riscriverli ad ogni estensione delle specifiche.

Riguardo alla "gran fatica", questo credo che dipenda un po' dalla skill che si ha in tdd... Per applicarla _bene_ occorre un sacco di pratica. Io come hai capito non so nulla di computer graphics, però mi è capitato di usare tdd per algoritmi matematici/geometrici e mi sono trovato benissimo.

Ma a parte questo, per me c'è una differenza grossa fra ottenere codice e test sviluppati insieme con tdd, e ottenere solo il codice. Nel secondo caso, ogni successiva evoluzione del codice mi costa di più, perché non ho i test che mi supportano. Codice senza test per me è codice legacy. Codice ben coperto e documentato dai test vale di più.

E' vero che in certi casi posso arrivare a una soluzione più velocemente codificando senza test, e posso anche arrivare a una soluzione corretta! Che verifico manualmente. Però dopo questa velocità iniziale, il mio lavoro non potrà che rallentare. (Questo punto è argomentato molto bene su questa pagina drl nostro wiki: http://milano-xpug.pbwiki.com/Velocita)

E non solo: anche se io, che sono un abilissimo hacker (poniamo) riesco a scrivere una soluzione corretta in breve tempo, non riesco a trasferire questa mia magica abilità agli altri che lavorano con me. Questo è un altro punto --bellissimo-- di forza del TDD: che ti permette di far lavorare insieme senior e junior, e gradualmente sollevare il livello dei meno abili verso il livello dei più abili.

Vedo che ho anch'io pontificato invece che produrre software, e mi dispiace perché le mani mi prudono sempre quando c'è da codificare; però il tempo mi manca, e prima che la nostra scuola estiva sia conclusa (http://essap.dicom.uninsubria.it/) non potrò mettere mano al codice. Perché non ci dai tu un esempio di codice scritto in test first che ti pare insoddisfacente? Così possiamo dissezionarlo e discuterne! Posso suggerire di partire da un esempio più semplice? Non è che con tdd si possano affrontare solo problemi semplici, tutt'altro! Solo che i problemi semplici risultano più facili da discutere (come il mitico Stack: utilissimo per spiegare, quasi mai impiegato in pratica.)

Nice to talk to you again,

Matteo

Carlo.Pescio said...

Paolo: the problem is algorithmic in nature, so a code-first approach may not be so ugly after all, for the same reasons you said: familiarize with it. If I didn't knew already how to do it, and if I couldn't find a good answer on the huge computer graphics bibliography or on the net, I would probably:
- get a regular cirle algorithm working (code first, possibly copy&paste first :-)
- run a few manual tests to gain some confidence that it's probably working
- play with a few alternative ideas on how to get a nice blended circle
After that, I would probably sit back and do a nice, old-style, regular :-)) OO design, no TDD, because I've yet to see how you guys would apply TDD to the problem :-).
Then, depending on the importance of this code, after cleaning up everything I'll devise a set of test cases (using a mixture of equivalence categories for center and radius, and plain creativity for the underlying bitmap) and check the results manually as I've yet to see how you guys would test this stuff automatically.
I may get some help from a tester, but hey, I'm the lazy busy guy.

Now, just to keep the fun going :-)))

- For one reason or another, I thought that double quotes and a smile around ambitious were enough to reveal the tongue-in-cheeck nature. Guess not :-). It also seems like you all read "ambitious" as a positive attribute, which is somewhat strange (for the agile camp).

- Isn't it weird to see all you agile guys so worried about the hypothetical future of SmoothedSomething circles? Shouldn't you be concerned with the Simplest Thing that can work? (in this case, a barebone smoothed circle, as nobody asked for a regular circle, a dashed circle, etc, and you know, you can't predict future, and bla bla bla :-))).

- While we're talking about design: when complexity (inasmuch as we can talk about complexity in a toy problem) is heavily on the algorithmic side, the structural side may have to adapt itself. If you move from theory to practice, you may notice that drawing a single pixel of the blended circle may need some more data from the circle, or may require (it's not necessarily the best algorithm) to draw more than one circle.
Therefore, a strategy-based design (where the strategy is the "plot a pixel" logic) won't be necessarily appropriate. A trivial subclassing (the "ambitious" design :-)) would work just fine in the short term (as the derived class has access to all data and logic it may need), and a Decorator probably in the long term too, but that would sensibly increase complexity.
Again, if I change the problem so that you have to draw both a non-blended circle and a blended circle, you may find that your Simplest Thing [short of putting everything in one function with a flag] is that poor subclassing you're all spitting on (which doesn't seem so agile :-)), you can always refactor if that hypothetical SmoothedSomethingCirle shows up).

- About trust: I'll leave aside the serious stuff (which is: I trust the man, not the approach; I either trust you when you tell me that your code is easy to change, or I'll check it. If I don't trust you, TDD won't get into the equation :-).
Anyway, I'll be glad to see a set of test cases for the smooth circle, so that I can look at the CODE for the test cases alone and say: OK, if it passes those test cases, I'm confident that it works fine AND that the code, to reuse :-)) your words, "can be easily changed"...

Guys, c'mon, I thought you were the agile, code-centric bunch. Stop offering me free design lessons :-). Give me either a free can of coke :-) or a free set of useful test cases :-)))).

Piergiorgio Grossi said...

Ciao guys,
thank you ALL for this interesting conversation.

My 2 euro cents: reality is a bit more complex.

Join a team: a REAL ONE and for a decent time, not the one can join for few days/weeks during a mentoring or a consultant session.

And try: what's theory wil not become practice for others, it will practice for you and for monthes.

Evolve products, support them, and then judge. The value you'll see in test driven/first practices is not (only) code or design related: is communication, is documentation, is a 'natural' reminder toward semplicity, ...

PierG
http://pierg.wordpress.com

Carlo.Pescio said...

Matteo: vuoi dirmi che se IO, povero committente ignorante (per una verifica sperimentale, leggete quanto mi dice il Dott. Cirillo :-))), non sono capace di spiegarti un requisito in termini di verificabilita' algoritmica, VOI sviluppatori agili non siete capaci di muovere il primo ditino sulla tastiera? :-))))))).

Piu' di una persona che conosco direbbe: "success is when you get that !@#$%@#$ circle nicely blended with any !@##%^ bitmap I'll give you, using any !@#$ drawing color, radius, center. Is that @#$%$#%@# clear now?" :-)))).

Tornando seri (per quanto e' nelle mie limitate possibilita' :-)), l'obiettivo del mio post, che evidentemente e' cosi' elusivo (??), e' proprio di far riflettere sul bilancio economico della vicenda.

Se per sviluppare l'algoritmo impieghiamo (e.g.) 2 ore, e per dargli un briciolo di struttura OO decidiamo di spendere un tempo variabile tra 0 e 2 ore, dobbiamo valutare quanto ci costano i test automatici e NON infilarci ciecamente nella visione fideistica che senza di quelli ci rimetteremo perche' le modifiche future costeranno.

Nel caso specifico, se pensi ad un test "manuale", a mio avviso converra' (come accennavo a Paolo) spendere un po' di tempo almeno a trovare delle categorie di equivalenza per centro e raggio (vi verranno fuori un tot di casi comunque) ed un po' di creativita' nel pensare a qualche bitmap "problematica".

Premesso che questo mi viene difficile pensare di NON farlo se voglio dei test significativi (automatici o manuali che siano), a questo punto entra nel bilancio economico il fattore automazione del test. Che banalizzandolo un po' deve tener conto di:

- costo attuale, se lo faccio (costo sicuro)
- costo futuro, se NON lo faccio (costo ipotetico; come mai su questo voi agilisti non siete mai dubitativi??)
- complicazioni varie dovute al market delay cost, al money discount rate, ecc ecc. Per chi ha voglia di leggersi una cosa carina, potrei suggerire "Value Creation and Capture: A Model of the Software Development Process", Todd Little, IEEE Software, May/June 2004 (assicuro che non urtera' assolutamente i nervi degli agilisti, anzi :-)

Ora, se aveste tempo (e voglia :-) di pensarci qualche minuto, vi rendereste rapidamente conto che a differenza di uno Stack, di una moltiplicazione di matrici, di un calcolo di autovettori, ecc ecc [rimanendo nel campo che citavi], qui il costo attuale di automatizzare i test e' ENORME.
Diciamo che se vogliamo qualcosa di utile, che non sia un pro forma tanto per dire che l'ho fatto, ma che dia quei benefici che volete - ovvero, poter cambiare le cose con maggior sicurezza di non rompere tutto, perche' i test che passano mi rassicurano di non aver rotto tutto, oltre magari a guidarmi ad un miglior design [la vedo dura nel caso concreto] - qui parliamo con ogni probabilita' di SETTIMANE di lavoro. Altrimenti sono test pro forma, che non hanno alcuna delle proprieta che, come dici, aggiungono valore al software.

Per questo NON VALE cercare di farmi cambiare problema, NON VALE dirmi "fallo tu e poi raccontaci", e tanto meno vale, cirillamente, dirmi "non ci riesci tu perche' sei un !@#$$ mentre io che ci riuscirei ad occhi chiusi ho altro da fare".
Nella mia beata ignoranza :-))), avendo occasionalmente giocato con la computer graphics, direi che e' piu' che ovvio che IN QUESTO CASO SPECIFICO non ci si guadagna, anzi ci si perde, a tentare il TDD o anchw la "semplice" automazione dei test. Il mio obiettivo era "solo" farci riflettere sopra anche chi occasionalmente perde tempo a leggere il mio blog, inclusi alcuni cari amici agilisti [BTW, il buon Marco Abis sa che questo termine, per qualche ragione, a me ricorda sempre Aldo, Giovanni e Giacomo quando facevano il circo dei Bulgari :-))].

Ed infatti il post originale [peccato che qualcuno non sia di mente abbastanza aperta per capirlo :-))] in soldoni diceva soltanto: occhio ragazzi, ci sono ALCUNI problemi che NON E' ECONOMICO affrontare con test automatici, prima o dopo che sia (tanto meno, ovviamente prima). E dava un esempio concreto (e facile) su cui pensare cinque minuti.
Poi qualcuno lo capisce al volo, qualcuno purtroppo si crede troppo intelligente, e invece di prendere atto della realta', inizia a dire che senza uno studio scientifico - epistemologico - ontologico non se ne fa nulla :-). Agilissimo :-)))))))))))))))).

Carlo.Pescio said...

Caro Dott. Cirillo,
La ringrazio nuovamente per il suo apporto alla conversazione.
Un po' di sano fondamentalismo, condito da ormai sempre meno velati insulti :-), da una non ben giustificabile aria di sufficienza, da tanta filosofia senza UN solo spunto concreto, da tante belle (?) divagazioni sui massimi sistemi senza alcuna dimostrazione di aver capito minimamente il problemino sotto il naso... tutto cio' non puo' che aiutare chi legge a capire meglio, come dicevo, tante cose.
Quanto a me, un po' di sane risate fanno vivere piu' a lungo, quindi, grazie ancora :-). La parte piu' bella non e' tanto quella in cui reitera il fatto che di software non capisco e conosco nulla :-)), e nemmeno quella in cui mi suggerisce che forse leggendo, studiando e praticando molto avrei una remota chance di provare a capire qualcosa (senza garanzie, ma grazie, grazie ancora :-))). Non e' neppure quella in cui mi spiega di non avere alcuna voglia di dare un contributo costruttivo, ma di essere qui solo per difendere la purezza del dogma dagli infedeli rozzi ed ignoranti. Per me, e' quella in cui mi dice quello che avrei dovuto fare :-)), con tanto di grassetto imperativo :-))). Non avete per caso anche un campo correzionale nel quale "rieducarmi"? :-))))))))).
Avrei qualcosina da dire sul fatto che apparentemente Lei non intende affatto applicare il paventato metodo scientifico al TDD, altrimenti sarebbe interessato ad ogni minimo controesempio (sufficiente ad invalidare una teoria) al fatto che sia sempre vantaggioso, e non chiederebbe ad altri di costruire prima chissa' quali altre teorie bisognose di ermeneutica :-), di proiezioni in un sistema cosmogonico da cui attingere attraverso una scrupolosa esegesi, rivedendole pero' dinamicamente in una luce automaieutica e quant'altro :-)).
Tanto tempo fa, qualcuno piu' intelligente di me (serviva dirlo??? :-) ha messo in guardia dal discutere con alcune specifiche persone, sotto il rischio che non si veda la differenza. Pertanto, prosegua pure in un Suo monologo, se crede. A molti piace avere l'ultima parola :-)).

Carlo.Pescio said...

Ragazzi, mi aspettano alcuni giorni un po' intensi, quindi se per motivi imperscrutabili :-) vi va di proseguire, non aspettatevi risposte rapidissime...

Franco Lombardo said...

Ragazzi,
da anni ormai cerco di applicare il TDD ed ho trovato la sua utilità in alcuni casi, mentre in altri, ovviamente, risulta assolutamente controproducente: l'esempio di Carlo mi sembra significativo, ma se ne potrebbero fare molti altri.
La pulce che Carlo mi ha messo nell'orecchio è però un'altra:
"Test cases will break as soon as you change requirements (adding a pen thickness). And fixing automatic tests will prove a nightmare again."
Questo purtroppo mi accade spesso anche quando il TDD mi sembra appropriato ed anzi mi aiuta nella definizione iniziale del design. Proprio in un contesto di design emergente, e quindi in continua evoluzione, mi risulta effettivamente pesante andare a cambiare tutti i test quando occorre cambiare il design. E' vero, se sapessi applicare bene OCP non avrei bisogno di modificare il codice esistente a fronte di modifiche di richieste da parte dell'utente, ma capita sempre che quel "fetente" del cliente indovini la modifica per la quale non hai predisposto una chiusura. Per risolvere il problema sto applicando un approccio che prevede una minore granularità dei miei unit test, che si stanno pian piano trasformando in "acceptance test" lato programmatore. Che ne pensate? Grazie. Ciao.

Citrullo a motore said...

Ma insomma, il problema qual e'? Non siete capaci di inventare un sistema che controlli automaticamente se il codice ha disegnato il cerchio smoothed? Perche' il punto della questione mi pare tutto li'...

Facciamo finta che ci sia una funzione che prende la bitmap iniziale, raggio, centro, colore, spessore del cerchio e la bitmap finale, e che sappia valutare se la bitmap finale e' cio' che ci si aspetta. Se questo e' sufficiente, vorrei capire come andrebbe avanti il programmatore TDD. Per adesso, da quanto abbiamo letto, non sarebbe nemmeno partito.

Vorrei anche qualche delucidazione da Carlo Pescio, che molto chiaramente ha detto che in certi problemi (tipicamente algoritmici) e' troppo costoso fare i test PRIMA. Su questo concordo pienamente, ma il dubbio che mi ha lasciato il post originale e' la sua sottintesa conclusione: dobbiamo quindi evitare ogni tipo di test? Perche', come ho detto all'inizio, se il problema e' trovare un test automatico, farlo prima o dopo aver scritto il codice non cambia molto. Supponi anche tu di avere quella funzione di test: come andresti avanti?

La mia posizione (lo dico tanto per dirlo, non sono qui per difenderla o per promuoverla) e' quella di progettare software testabile, ma dall'esterno e per livelli. Istintivamente cercherei di modellare un sistema a stati: catturo uno stato, eseguo una operazione, e per verificare di aver fatto la cosa giusta controllo lo stato finale. Ogni livello deve essere conforme a tale schema, e testare un dato livello significa testare anche i livelli da cui dipende.
Nel caso del cerchio smoothed confronterei le due bitmap, prima e dopo aver disegnato il cerchio, senza sapere che classi sono coinvolte o se il codice ha subito refactoring: lo stato finale dev'essere cio' che mi interessa. Insomma il lavoro maggiore sta nel progettare le componenti del programma secondo un banale schema strutturato.

Una parola solo sulle asserzioni: la fatica di scriverle e' pochissima, l'aiuto che restituiscono e' di enorme valore.

lorenzo said...

Ciao,
non capisco esattamente il punto della tua critica.
Testare del codice di grafica a livello di pixel e' scomodo, sia che lo si faccia con test automatici che con uno screenshot e photoshop. Sia che si inizi dalla riga di spessore 1 sia da quella di spessore 10 con antialiasing. Entrambe devono essere disegnate correttamente.

La verifica fatta guardando i valori rgb del singolo pixel in photoshop non e' meno massacrante di scrivere 10 assert a livello di pixel.

In che modo questo mette in difficolta' il TDD piu' di quanto metta in difficolta il design tradizionale?

Il tuo esempio coinvolge diversi aspetti (formati di file, grafica 2d, algoritmi) e nessuno di questi mi sembra particolarmente problematico.
Cos'e' che caratterizza il tuo esempio? Qual'e' al parte "anti-TDD"?

Ciao

Citrullo a motore said...

Nessuna critica al TDD! Vorrei solo capire che codice scriverebbe il programmatore TDD, se avesse in mano la funzione che fa il test delle bitmap. Premesso che tale funzione sia cio' che serve al programmatore TDD, magari non se ne fa un bel nulla.
La "critica", da intendersi in senso puramente costruttivo, e' rivolta ai frequentatori del blog che fino adesso hanno descritto il TDD da lontano e non sono scesi al livello pratico. Vorrei vedere qualcosa che assomiglia a del codice, per dirla chiaramente.
E dall'altra parte vorrei che Carlo Pescio mi dicesse se userebbe la funzione suddetta e come.
Per quanto riguarda le modalita' di funzionamento di tale funzione magica, discutiamone dopo, per adesso assumiamo che esista e che funzioni a dovere.

(Ora mi e' venuto il dubbio che lorenzo non stesse rispondendo a me... beh io scrivo, poi vedremo :-)

lorenzo said...

Ciao...ehm...Citrullo :)
ti faccio subito un esempio in pseudo java improvvisato

testLineaInDiagonale() {
Image img = new Image(20, 20);
Line l = new Line(...);
l.drawOn(img);

assertBlack(img.getPixelAt(0,0));
assertBlack(img.getPixelAt(1,1));
assertBlack(img.getPixelAt(5,5));
assertBlack(img.getPixelAt(20,20));

assertWhite(img.getPixelAt(1,2));
assertWhite(img.getPixelAt(4,10));

}

Ciao

Gianfranco Zuliani said...

Salve a tutti,
per un problema simile alla fine ho adottato un approccio ibrido, che non so se si possa configurare come TDD (prima di leggere questo post sinceramente pensavo di sì).

A tempo "quasi perso" sto sviluppando una libreria di analisi di segnale per uno strumento di misura prototipale. Al momento lavoro secondo due modalità: test-first per i singoli componenti (es. attenuatori, filtri, ..., facendoli operare su segnali artificiali elementari), code-first per quanto riguarda l'applicazione dell'intera catena, testando la risposta del software su un campione ragionevolmente numeroso di segnali/configurazioni. Questi test "verticali" mi consentono di lavorare con sufficiente tranquillità sull'architettura interna del software, soprattutto per quanto riguarda l'aspetto delle ottimizzazioni. Quando il cliente richiede un nuovo modulo o una modifica ad un componente esistente, scrivo prima il test relativo, assicurandomi che il componente si comporti come da specifiche, dopodiché verifico quali test verticali saltano, controllando se le nuove risposte sono in linea con quanto atteso; se così è, li modifico di conseguenza.

Utilizzare un simile approccio per il problema della bitmap (seleziono un campione di bitmap significative, applico l'algoritmo, verifico che i risultati siano quelli attesi, utilizzo le coppie di bitmap per definire una serie di test) mi sembra il percorso più ragionevole, tenendo conto che lo sforzo richiesto per realizzare i test verticali, una volta realizzato l'algoritmo, è minimale:

load (input_bitmap);
transform;
save (output_bitmap);
compare(output_bitmap, test_bitmap)).

Dalla mia esperienza, l'aggiornamento dei test verticali, a fronte di nuove/diverse specifiche, è limitato all'analisi dei nuovi risultati (qualche nuova output_bitmap da valutare perché il pennello ha cambiato dimensione), e alla scrittura di nuovi test e/o alla sostituzione delle relative test_bitmap per far passare di nuovo i test "sballati".

Forse però mi sfugge qualcosa...

Anonymous said...

"""
Uberto: a test is (by definition) something that can either fail or succeed, revealing if the called code is wrong or right. If you feel you can successfully write meaningful, helpful (and hopefully automatic) tests for the "smoothed circle over a bitmap" problem, tests that can tell if your implementation is correct, and also guide you toward a better design (after all, it's TDD, folks :-), why don't you share them with us?
"""

I haven't time to read all the thread and so I quoted my direct reply.
Still I don't see any particular reason for which it should be more difficult testing this than the other 27000 samples of TDD that you could find on internet.
Surely you cannot imagine that I have to test every possible smoothed circle?

regrettably, I have no time to completely explore this, moreover I've not the client onsite ;), but I'd rather start with some test to explore how I want to use this new class of mine.

In this first part we test only to explore the design and to decide which entity (class, methods and so on) we really need, also considering YAGNI principle.

In this phase we don't have to really automatically check the shape drawn but we could be satisfied also checking only 2 or 3 pixel, just to check that something is being drawn.

When we are satisfied with the design and the circle is produced nicely (hopefully by implementing some known algorithm), we could save 3 o 4 bmp to keep like samples and making the regression tests that compare the drawing with the expected results.

I hope this ultra short description could suffice. I'm sorry but I cannot write a more detailed explanation.

Anyway this is how I apply TDD in my everyday work, and I found it very useful to help me to design the code.

Not knowing much about smoothed circles ;) I don't know if there is a standard algorithm (like to say how to calculate the first n digits of pi) or not. Surely you cannot discover new algorithms BY using TDD, but you could use TDD when discovering new algorithms! If you mean what I mean.

Uberto

Carlo.Pescio said...

Citrullo, bravo :-), almeno tu vai sul concreto :-).

Molto rapidamente [come dicevo sono giornate un po' intense] aggiungo qualche parola su pochi punti essenziali (che speravo ormai fossero chiarissimi :-))

- La tua funzione magica, che e' uno dei punti focali, e' purtroppo MOLTO difficile da scrivere se confrontata con l'algoritmo per disegnare il cerchio. Poi tu te ne intendi e sai che molte verifiche sulla grafica non riusciamo ad automatizzarle in modo economicamente sensato. E peraltro, essendo la funzione cosi' complicata, se dice che il disegno non va bene dov'e' il bug, nell'algoritmo o nel test? [cosa che nessun agilista, casualmente, ha sottolineato nei post precedenti :-))]

- Ancora peggio: la funzione magica NON e' indipendente dall'algoritmo del cerchio, non puoi scriverla facilmente a priori. Non e' facile infatti dire a priori, data una bitmap, un colore del cerchio, un centro, un raggio, di che colore *esatto* dovra' apparire un pixel, perche' ci sono N possibilita' implementative differenti, ognuna delle quali puo' dare un cerchio *visibilmente* smooth e blended [che e' quello che chiede il cliente].
Non e' sensato [oltre che non economico] pensare di fissare a priori queste cose (tanto meno chiedere al cliente di definire queste cose iper-tecniche), senza fare un tot di esperimenti IMPLEMENTATIVI (da cui l'idea che SU QUESTO ESEMPIO SPECIFICO si lavora meglio code first, design second, manual test all around).
Ovvio, il requisito non e' definito in modo ovvio (yes/no), ed anche questo non si puo' fare in modo economicamente sensato senza fare esperimenti. Dai post precedenti, capisco che questa cosa ha dato (paradossalmente!!) fastidio agli agilisti :-))).
- Per chi ti rispondeva: certo io non verificherei in photoshop guardando il colore del pixel con il cursore, anche perche' visto da solo non saprei che farci :-). Guarderei, prima senza zoom e poi qua e la' con zoom, *se viene bene o meno*, ovvero se il mio algoritmo e' buono o meno. Lo farei [ad un certo punto, e probabilmente con l'ausilio di qualcuno, leggi tester] anche in modo sistematico con le categorie di equivalenza, ma questo non e' certo TDD.

- Se poi ad un certo punto dico OK, l'algoritmo e' buono, prendo N bitmap, fisso dei colori, centro, raggio, lo applico, ottengo altre bitmap e poi trasformo un confronto con queste in un test, ovviamente a) e' tutto tranne TDD, b) e' piuttosto fragile ugualmente...

- Giusto per dire ancora due parole, una volta che avessi messo su il baraccone del punto precedente, i test continuano ad essere utili per refactoring puramente strutturali [meglio che niente, ma qui sarei curioso di capire secondo voi quanti ne avrei che NON siano motivati da variazioni ANCHE ai requisiti funzionali :-))]. Non appena cambia una briciola nell'algoritmo, e' tutto da rifare da capo (vedi spessore maggiore, o come diceva qualcuno, il tratteggio, o magari e' un quadrato e non un cerchio, ecc ecc).
Da rifare sia se ho una funzione magica complicata che ora e' ancora piu' complicata, da reinventare e riscrivere, e da ripetere comunque se ho optato per la validazione manuale + capture delle immagini + scrittura A POSTERIORI di alcuni test automatici, anche qui da capire sin dove utili se si rompono appena guardi il codice...
In generale, come gia' dicevo, e' divertente quanto tutti si preoccupino di non "perdere" tempo in un sovra-design del sistema, ma pochi / nessuno di perderlo in un sovra-design del test, meglio ancora se fatto prima del codice :-)). Ah, la fede... :-))

- Per finire (che poi non sto dicendo quasi niente che non abbia gia' detto) sarebbe tutto da vedere come la difficolta' di scrivere la funzione magica, in ottica TDD, mi guidi nel design del cerchio blended, attraverso la famosa storiella del design emergente...
Peccato che sinora tutti abbiano parlato d'altro :-)).

Matteo Vaccari said...

Carlo,

se ho ben capito allora tu stai confrontando la difficoltà di scrivere un algoritmo e verificando *esattamente* che l'algoritmo sia corretto, con la difficoltà di mettere insieme un programma ed eseguirlo una volta e vedere se um, più o meno, sì, sembra rotondo, e um, sì, il bordo non è tanto seghettato. Non mi stupisce che allora dici che il primo non è cost-effective... se la correttezza è optional si può risolvere qualsiasi cosa in 0 sec. Se la correttezza è "deve sembrare OK quando guardo il risultato" allora l'unico test automatico che puoi scrivere è confrontare l'output con uno salvato che è stato verificato ad occhio. Molto facile da scrivere, e cost-effective rispetto al livello di correttezza che ti serve.

Romano Scuri said...

L'algoritmo di disegno del cerchio, di spessore a piacere, con anti-aliasing dovrebbe essere più o meno il seguente (se poi ne esistono altri benvengano, ma non riesco ad immaginarli):

- colora i pixel della bitmap, completamente ricoperti dal cerchio, con il colore del cerchio.
- colora i pixel della bitmap, parzialmente ricoperti dal cerchio, con il colore intermedio fra il colore del cerchio ed il colore della bitmap in base alla percentuale di ricopertura.

Detto questo mi sembra che un programma di test sia inutile e possa bastare una verifica visiva a campione, più che altro per verificare che la nostra funzione di tracciamento del cerchio di spessore a piacere produce effettivamente quanto richiesto o accende pixel a caso da altre parti come mi succedeva con l'editor di risorse di Visual Studio versioni precedenti :-((. Non c'é dubbio che se coloriamo un pixel di un certo colore, il programma di test rileverà nella bitmap il colore che abbiamo impostato, se è questo che il programma di test deve fare, ma mi pare un test inutile, come già detto. Forse il test migliore sarebbe verificare che al di fuori del rettangolo massimo i pixel di partenza e quelli finali non siano variati, come pure i pixel interni al cerchio, sotto un certo raggio.

Gabriele Lana said...

Vorrei fare anch'io un paio di considerazioni:

TDD = Test Driven Development è una pratica di design e non di test (è stato già detto in molte salse, ma forse vale la pena di ripeterlo), l'intento non è quello di verificare che l'algoritmo utilizzato sia corretto e completo, ma quello di aiutare lo sviluppatore ad ottenere un codice migliore. Probabilmente la soluzione banale del problema sarebbe quella di copiare l'algoritmo sostituendo le syscall del proprio SO, il che non mi sembra una soluzione "ottimale". Utilizzando TDD molto probabilmente si arriverebbe a:
- confinare le syscall relative al SO, rendendo più portabile il codice
- disegnare su un buffer intermedio
- separare in step l'algoritmo dando un minimo di struttura al tutto
- ecc...

I test unitari devono essere esaustivi? No, l'importante è che mi guidino verso una soluzione con la quale ho confidenza e della quale sono sicuro. Non mi sento sicuro o non so bene cosa succede in un passo dell'algoritmo, bene, estraggo quella parte di codice e scrivo dei test che "verifichino" (test come verifica/specifica) che quel pezzo di codice faccia quello che io mi aspetto, aumentando la confidenza che ho nel codice.

Quanti test unitari?
- Quanta confidenza hai con il codice?
- Quanta ne vorresti?
- Quanto sei disposto a spedere per ottenere quella confidenza (bilancio costi/benefici)?

Ultima cosa che vorrei sottolineare è che non c'è scritto da nessuna parte che i test che vengono scritti durante il TDD debbano essere gli unici e soli test messi in campo per verificare la "correttezza" di quanto implementato (anche se mi piacerebbe parlare di "adeguatezza" alle esigenze del cliente), quindi i discorsi del tipo "con TDD non puoi testare questo o quello" dal mio punto di vista non sembrano adeguati

Citrullo a motore said...

Grazie Gabriele Lana, ora capisco come procede il TDD! :-)
Piccoli test (scritti subito prima o subito dopo il codice?) che verificano e allo stesso tempo specificano il funzionamento di piccole sequenze di righe di codice. Nessuna pretesa di verificare la correttezza del tutto, solo una guida per scrivere codice piu' semplice da capire, avendo in mente che "se posso scrivere un piccolo test, allora e' codice buono". Correggetemi se ho capito male, naturalmente.

Forse il passaggio di Carlo Pescio che ha causato l'arricciamento di qualche naso e': "Please, test this first. No code before you have some meaningful test in place. Meanwhile, someone else will use a code-first :-) approach. Let's see what happens ;-).
Winning later because of test automation? Maybe. Now your algorithm should also take a user-specified pen thickness as a parameter. Are your tests ready for that? Easily changed? C'mon."

Ma torniamo al nostro esempio. L'algoritmo di Bresenham verrebbe scomposto in passi a dire poco elementari. Il numero di linee di codice aumenterebbe di un bel po'. Certo, avrei in mano un codice in qualche modo "migliore" (piu' leggibile?), ma a che prezzo? Piu' lungo, piu' lento, infarcito di codice di test che, per quanto sia stato utile a scrivere un "migliore" algoritmo, non mi sa dire se il cerchio smoothed e' venuto bene o no.

Citrullo a motore said...

Mi sono dimenticato una cosa!
Trovo che, sempre limitatamente a questo esempio del cerchio smoothed, sarebbe meglio infarcire il codice di asserzioni. Descrivono/specificano l'algoritmo ed i suoi passaggi, avvisano quando qualcosa non va secondo i piani, e costano pochissimo.

grullo :-) said...

> infarcito di codice di test

citrullo di solito gli unit test vengono scritti in file separati dai sorgenti dell'applicazione. Dai un'occhiata ad esempio a www.junit.org (per java ma ne esistono praticamente per qualsiasi linguaggio)

Marco Foco said...

M'hanno invitato a postare qui un commento che ho dato alla cosa sulla mailing list del xp user group di Milano. Lo riporto, con qualche aggiunta finale.

Premetto che di XP e TDD ne so poco: sto in questi mesi cercando di capirne i vantaggi e svantaggi (senza preconcetti), per cui provo continuamente a tentare di introdurli nel mio lavoro (che riguarda la computer grafica). Essendo appunto uno "del mestiere" ho pensato che la "sfida" fosse interessante, ed ho provato a risolverla.

Innanzitutto le specifiche sono descritte solo approssivamente, per questo a mio parere non e' possibile sviluppare dei test molto soddisfacenti. Suppongo che per smoothing si intenda l'antialiasing: questo puo' essere implementato in svariate maniere, ottenendo risultati numericamente _diversi_ ma ugualmente corretti (ci sono diverse scuole di pensiero su come farlo, diversi target di complessita e svariati metodi per valutare la qualita' del risultato).

Parliamo di un cerchio e basta, innanzitutto.

Sparo un test per questo:

Test 1 - immutabilita' dell'immagine "distante" dal cerchio
Testare che tutti i pixel distanti piu' di un pixel (o meta' della thickness +0.5) dal cerchio siano invariati rispetto all'immagine originale. Test perfettamente automatizzabile, che tralaltro puo' usare un metodo DIVERSO dall'algoritmo di bresenham citato
per testare la distanza dal cerchio (distanza dal punto meno raggio), validando cosi' anche che l'implementazione dell'algoritmo di bresenham non vada a sporcare in giro.

Questo test lascia passare una funzione vuota.
Se parliamo di un test che debba disegnare un un cerchio di spessore unitario (dato che bresenham viene utilizzato per questo, e non direttamente per cerchi con thickness, mi sembra inutile parlare di quest'altro parametro).

Test 2 - variazione dell'immagine
Testare che all'interno dell'area non-invariabile (di cui s'e' parlato prima) ci siano almeno 4R-2 pixel di colore "variato" (R = raggio). Il motivo del numero 4R-2 e' lasciato per esercizio al lettore ;)

Questo test lascia passare i cerchi con dei "buchi" (in cui manchi qualche pixel), per questo occorre un TERZO test.

Test 3 - Continuita'
Se il raggio e' maggiore o uguale a 1, ogni pixel "variato" deve avere ALMENO due pixel variati attorno che siano NON contigui a loro volta.
Con bresenham e in realta' in assenza di antialiasing, l' "almeno" diventa "solo", e il test e' capace di decidere la correttezza del risultato anche
in assenza del test 2.

Nel caso ridotto che ho citato, effettivamente si puo' iniziare a lavorare a questo punto.
I test sono piuttosto semplici da scrivere, e utilizzano per la validazione metodi diversi da quelli utilizzati per il rendering.

Per aggiungere l'anti aliasing e' necessario modificare leggermente i test, e comunque il metodo non sara' perfettamente in grado di validare il risultato.

Per validare anche il risultato si potrebbe procedere valutando l'entita' della variazione di colore sull'immagine e scrivere una funzione che, a seconda della distanza dal cerchio, dica quale sia l'intervallo di variazione del colore accettabile. Questa funzione dipende dallo specifico
metodo di antialiasing utilizzato e dal risultato che si vuole ottenere.
Questo, comunque, e' solo un modo banale di valutare il risultato.

Non ritengo, comunque, si sia specificato il problema a sufficienza per descrivere dei test sullo "smooting" citato nell'articolo.

In realta' pensandoci meglio il test 2 puo' essere implementato in modo che cerchi da verificare che l'area variabile sia "simile" all'area della corona circolare da R-thickness/2 a R+thickness/2, includendo quindi la thickness in questo (e rendendo il test indipendente dall'algoritmo utilizzato). Il test 3, cosi' come descritto, rischia di lasciare buchi... va un po' stravolto, ma e' adattabile, cosi' come lo e' il test per l'antialiasing (ammesso di volerlo testare).

Ciao,
Marco

PS: Mi scuro per aver scritto in italiano, ma ritradurre tutto il post mi sembrava un po' dispendioso =)

Citrullo a motore said...

> citrullo di solito gli unit test vengono scritti in file separati dai sorgenti dell'applicazione

Di solito? Ma qui non siamo nel solito, siamo nell'algoritmo di Bresenham per fare un cerchio smoothed su una bitmap... saranno 30 righe in C. Se pensi che sia meglio fare una serie di funzioni per spezzettare i passi dell'algoritmo, chiamabili da un file esterno per eseguire gli unit test, allora sei... un grullo! :-)

Grullo :-) said...

no, stavo rispondendo alla tua frase "infarcito di codice di test" spiegandoti che il codice ti test non risiede negli stessi file del codice dell'applicazione. Ripeto: dai un'occhiata a come funzionano le varie librerie/framework di unit testing come junit :-)

Gabriele Lana said...

torniamo al nostro esempio. L'algoritmo di Bresenham verrebbe scomposto in passi a dire poco elementari. Il numero di linee di codice aumenterebbe di un bel po'. Certo, avrei in mano un codice in qualche modo "migliore" (piu' leggibile?), ma a che prezzo? Piu' lungo, piu' lento, infarcito di codice di test che, per quanto sia stato utile a scrivere un "migliore" algoritmo, non mi sa dire se il cerchio smoothed e' venuto bene o no

Chi decide cosa vuol dire migliore? Chi decide cosa è venuto bene o no? La qualità del codice è affar tuo, giudicare la qualità della soluzione affare del cliente

E' ovvio che entrambe le cose sono collegate (e per questo serve collaborazione), ma quello che fai con il TDD non ti dirà mai se il tuo codice va bene o no (per quello che serve al tuo cliente), ti darà la possibilità di provare in qualunque momento che il tuo codice fa quello che ti aspetti che faccia, indipendentemente dal fatto che vada bene o male per il cliente (questo rientra sotto il cappello di altre pratiche)

Citrullo a motore said...

Per Grullo:
Ok, forse ci sono, c'e' un equivoco. Dunque tu dici che il codice dei test sta fuori dal codice del programma, come si vede per esempio in JUnit. E infatti il codice sta fuori (ero anche andato a vedere!).
Pero', se vuoi che il codice stia fuori, devi avere la possibilita' di chiamare delle funzioni "dentro" al programma, per testare i vari passaggi del nostro simpatico algoritmino. Immagino una funzione diversa per ogni step dell'algoritmo, cosi' potro' chiamarle una per una dall'esterno e testarle.
La mia perplessita' e' che, essendo l'algoritmo in questione piuttosto corto, non conviene spacchettarlo in diverse funzioni (quindi molto piu' lento) da testare separatamente, bensi' preferiro' scrivere codice di test direttamente dentro al codice dell'algoritmo.
Se mi sfugge qualcosa correggimi pure.


Per Gabriele Lana:
Non pretendo che il codice prodotto applicando il TDD mi dica se al cliente piacera' il cerchio oppure no. Sto considerando il rapporto costo/beneficio dell'operazione: nel particolare caso che consideriamo, paragono 30 linee di C con un po' di economicissime e vantaggiose asserzioni contro un codice piu' lungo e articolato, concepito per essere testato in ogni suo passo (dall'interno o dall'esterno non importa).

Mi pare che la discussione sia arrivata su elementi alquanto concreti, sara' contento Carlo Pescio? Abbiamo svolto il compitino... :-)

Carlo.Pescio said...

Su consiglio indiretto :-)) di Marco Abis ho dato anche una rapida occhiata al thread parallelo sull'xpug di Milano.

Aggiungo qualche commento a quanto sopra:

Matteo: mi stupisce che un PhD come te pensi (o provi a raccontarmi :-)) che con il TDD sto, usando le tue parole, verificando *esattamente* che l'algoritmo sia corretto. Sai benissimo che nessuna quantatita' di test puo' verificare esattamente che una porzione di codice (non banale) e' corretta. Puo' solo dimostrare, se un test fallisce, che e' sbagliata (Dijkstra). Sai anche benissimo che una dimostrazione formale di correttezza del codice e' ben altra cosa, e certo non ricade tra le pratiche agili. Il che non significa, secondo la mia visione della vita :-)), che non abbia un suo senso, tipicamente in porzioni di codice molto piccole e molto critiche (l'unico caso REALE in cui sinora ho applicato tecniche formali di dimostrazione di correttezza e' stato in campo medico, e per quanto mi riguarda, sono pure contento di averlo fatto :-).

Chi ha proposto esempi (o mini-esempi) di come testare in modo automatico l'algoritmo, o di come seguirebbe il TDD in questo caso (Uberto, Lorenzo, ecc, a parte Marco Foco che conoscendo la materia e' andato piu' a fondo, e sui cui suggerimenti dico alcune cose in seguito) a mio avviso ha dato una visione troppo semplicistica della cosa. Testo qualche pixel qua e la', poi faccio una copia della bitmap... premesso che non e' per niente ovvio dove stia la D di Design in questa cosa, nel senso che non e' stato minimamente mostrato perche' quei test semplicistici dovrebbero condurmi ad un buon design emergente IN QUESTO CASO SPECIFICO (vorrei smettere di dirlo, ma chissa' che succederebbe :-)), questo fa il pari con quello che vedo in tanti esempi (libri, pubblicazioni). Test semplicistici su problemi ancora piu' semplicistici.

Sulla mailing list dell'XPUG, qualcuno ha detto: "In prima istanza chiunque copierebbe l'algoritmo, sostituirebbe le syscall con quelle del suo SO, e via andare il gioco e' fatto (tra l'altro e' quasi quello che consiglia Pescio). E' una sindrome che ho riscontrato in molti neolaureati [...]". Ora io capisco tutti i problemi di comunicazione del caso, ma a me pare di aver detto piuttosto chiaramente come procederei, e non c'e' niente di simile a quanto sopra. Dicevo peraltro che arrivati ad un certo punto (algoritmo che da' soddisfazione al cliente, design OO ragionevole) mi servira' anche un test sistematico. Tra l'altro, ad un certo punto accennavo ad una cosa, che [forse perche' tutti voi la date per scontata, chissa' :-))] non e' saltata mai fuori. Esistono tutta una serie di tecniche di test "tradizionali" (nel senso che sono note da molti anni, usate sia dai programmatori che dai tester), tra cui scegliere quando dobbiamo testare un sistema. Qui, ad esempio, esistono una serie di fattori che spingono verso quelle che di norma si chiamano categorie di equivalenza, almeno su centro e raggio. Tanto per fare qualche esempietto ovvio:
- cerchio che rimane totalmente inscritto nell'area di disegno (che sia in memoria o su una scheda grafica poco importa)
- cerchio parzialmente fuori (da definire meglio, sono diverse categorie)
- cerchio tangente, esterno o interno, anche qui da definire meglio
- cerchio con centro interno ma raggio che fa uscire dall'area
- cerchio con centro esterno ma raggio che attraversa l'area (e' un caso particolare del "parzialmente fuori)
- ecc. In realta' sarebbe da applicare anche la tecnica dei boundary values.
Ci sono poi categorie di equivalenza anche su altre cose, ad es. il numero di bit per pixel della scheda. Viene bene con ogni bpp? Devo cambiare qualcosa nell'algoritmo per dare un risultato civile a soli 256 colori? [il cliente vuole che funzioni bene anche a 256 colori?] ecc ecc. C'e' anche una ovvia problematica creativa che citavo, ovvero quali bitmap di sfondo usare per mettere realmente alla prova l'algoritmo.
Ovviamente, nell'ansia :-)) di dirmi che basta controllare un paio di pixel e poi catturare la bitmap, nessuno parla di fare un PIANO DEI TEST serio (lo so, lo so, lo date tutti per scontato :-)))). Che per voi deve per forza essere scritto nel codice per avere valore, ma per me ha piu' valore un BUON piano dei test da eseguire a manina che un controllo banale su 3 pixel + 1 bitmap catturata eseguito in modo automatico. Chi riesce a leggere oltre il dogma magari ha voglia di pensarci su un po', sempre tenendo presente l'idea di fondo, ovvero che occorre valutare SUI SINGOLI CASI.

Due parole su quel che dice Gabriele Lana:
Utilizzando TDD molto probabilmente si arriverebbe a:
- confinare le syscall relative al SO, rendendo più portabile il codice
- disegnare su un buffer intermedio
- separare in step l'algoritmo dando un minimo di struttura al tutto

Premesso che nella "specifica" del "problema" vi regalavo una classe Canvas che gia' faceva le prime due cose, sull'ultimo step ha probabilmente ragione Citrullo, e' piu' economico metterci dentro qualche asserzione che non spezzarlo, rallentarlo (siamo in computer graphics in fondo :-) e sperare di testarlo "da fuori". Ma la cosa divertente e' che non e' ben chiaro come quel che dice si sposi col vostro caro YAGNI. Codice portabile? Chi ve lo ha chiesto? YAGNI. Buffer intermedio? E perche' :-)? Solo per semplificare dei test (quali?) e in cambio mi rallenti tutto? YAGNI. Ecc ecc. O appena si parla di test, YAGNI non si applica piu'? :-)). Ci torno tra un momento.

Due parole su quel che dice Marco Foco:

- non so quanto sia ovvio per tutti, ma ognuno dei suoi step, che testa alcune condizioni necessarie (Matteo: mai sufficienti), ha complessita' paragonabile a quella dell'algoritmo del cerchio, se non superiore. Inoltre hanno la loro bella quantita' di problemi, se non sto ben attento ad implementarle con la dovuta cura. Ad esempio il suo Test 2 va accuratamente incrociato con le categorie di equivalenza di cui sopra, perche' a volte non disegnamo l'intero cerchio se un pezzo sta fuori (altra complesita' da aggiungere al codice di test). Inoltre ovviamente non passa il test classico che farebbe (GIUSTAMENTE) un tester professionista, ovvero un bel cerchio giallo su sfondo giallo (per vedere che non ci siano strani effetti tentando lo smoothing, magari con pochi bpp). Qui non avresti pixel variati. Eccetera. Ovvio che tutto si puo' sistemare e migliorare, investendoci ancora. Quello che mi interessa evidenziare e':
- quando il codice di test e' piu' complesso di quello che devo testare, e' quasi inutile. Se mi dice che c'e' un bug, dov'e' il bug?
- leggendo su XPUG vedo che ormai, con l'ardore della fede :-), siete arrivati al refactoring dei test ed ai bad smell sul codice di test. A quanto un TDTD :-), Test Driven TEST Design, per i casi come quello sopra? :-)) Quanti livelli di ricorsione possiamo reggere? :-))
- io capisco la fede cieca :-), ma vi chiederei di pensare ad una cosuccia in senso economico. Voi predicate di fare il sistema piu' semplice possibile, perche' non siamo (cirillamente dovrei dire siete :-) capaci di prevedere bene il futuro, e potremmo fare tanto sforzo inutile. Pero' siete disposti, senza battere ciglio, a spendere una fatica immane nella scrittura di codice di test automatico. Perche' non fate una valutazione economica in ogni caso (che poi era lo stimolo originale del post)? Cosa vi fa pensare (ovvero, quale PREVISIONE DEL FUTURO ve lo fa pensare) che avro' bisogno di ritestare molte volte il cerchio con smoothing? Tenete presente che se sbagliate futuro, tutto lo sforzo che avete fatto e' inutile anche qui: se diventa un triangolo invece di un cerchio, si butta via tutto il vostro certosino lavoro. Perche' vivere le cose come un dogma anziche' valutare ogni volta anche il trade/off tra probabilita' di dover cambiare realmente le cose, costo di ri-testare a mano, costo di sviluppo del test... perche' pensare che siamo incapaci di prevedere un futuro quando progettiamo, ma bravissimi quando scriviamo codice di test?
Qui ci sarebbe tutta una questione di self-fulfilling expectations, ma ci tornero' in un post futuro.

- Ultime parole su qualcosa che e' emerso qui (quanto vi danno fastidio i requisiti poco chiari, cari agilisti :-)) ed una cosa scritta su XPUG:
Probabilmente il TDD e' piu' adatto in contesti nuovi e in cambiamento, forse la grafica 2d e' un ambito talmente stabile (si fa per dire, ovviamente...) da beneficiare meno dei punti di forza del TDD
Da tutto quanto e' emerso nei vari interventi, a me pare piuttosto chiaro che sia esattamente IL CONTRARIO. Quando il contesto e' nuovo e complesso, e' spesso poco chiaro cio' che si desidera, occorrono prove esplorative per capire se ci avviciniamo ad una buona soluzione, e spesso non e' pensabile che il committente ci dia una specifica chiarissima, praticamente algoritmica, con dei netti criteri pass/fail. Inoltre in molti problemi nuovi e complessi vi trovate nella condizione di cui sopra: una verifica automatica e' ancora piu' complessa.


Sempre da XPUG, ecco qui:A me sembra un po' che Pescio abbia lanciato il sasso e nascosto indietro la mano. Senza considerare il carattere un po' pretestuoso dell'esercizio.
Boh, puo' darsi, la mia intenzione era di far riflettere (quindi certo che ho lanciato il sasso), non mi e' ben chiaro il momento in cui ho nascosto la mano :-))). Sul fatto che sia pretestuoso non sto nemmeno a discuterne, ogni problema di grafica ha sfumature simili, e "grafica" qui vuol dire anche quella di un widget, persino la classica griglia un po' sofisticata. Lo stesso si applica a tante altre cose fuori dalla grafica, basta tenere la mente aperta.

Comunque, ragazzi, i commenti al solito sono benvenuti, se non emergono spunti nuovi per me su questo thread e' tempo di wrap-up, chi era di mente abbastanza aperta per me ha capito da un pezzo, chi preferisce chiamarsi agile ma perseguire ad oltranza pratiche rigidamente fissate, e negare ogni evidenza contraria al dogma, e' ovviamente liberissimo di farlo (io non ho campi correzionali :-)).

zibibbo said...

Carlo,

I've tried to implement a test for your function. I've put it here. (I haven't tested it:-), I've just made sure it compiles). Instead of trying to devise improbable postconditions for the drawing function, I've just implemented a very simple and accurate (but also too slow to be really used for something else than a test) function for drawing the circle, and I've compared its output with the one of the function under test, allowing of course for a margin of error, seeing as the output of these functions is, out of necessity, only approximate. I think the results are acceptable, both in terms of implementation effort and accuracy (the code itself could be improved in a number of ways, of course, but it's just a proof of concept after all). The test code comprises about 130 lines of code (including a great deal of comments), and most of them could be reused to test drawing functions for different geometric shapes. Somewhat surprisingly, it would be much harder, using this approch, to test a drawing function without smoothing.

Please don't read this as an endorsement of TDD. I don't believe in TDD, and I have exactly the same opinion as you about it. I was actually curious to know what you thought about it, and glad to see we were thinking along the same lines. You see, I hardly ever write any automatic test code (unless my manager forces me to) but every now and then I wonder whether it's just my lazyness that keeps me from doing the right thing :-).

But I also think that, if there is code that is worth writing some automatic test cases, it's the very "algorithmic" one, and your example seems to fit into this class. And I also think that the tecnique I've used in the above code is sometimes surprisingly effective, and can help you out when other tecniques fail: just find the simplest algorithm that can solve the problem (which is usually unacceptably slow but can also be very easy to write) and compare its output with the production quality algorithm's one. In this specific case, I think the results might be worth the effort, especially if you're developing an entire library of drawing functions (it wouldn't replace manual testing (or the use of assertions), of course, just complement it). Anyway, I would be glad if you would, when you have some time to spare, have a look at my code and tell me what you think about it.

Regards.

francesco cirillo said...

See

http://www.xplabs.it/201017.html

Regards,

Francesco Cirillo

Carlo.Pescio said...

Zibibbo, thanks for the hands-on approach :-)). Weird that code had to come from a non-agile guy :-)).

I'll leave aside a few TDD-related points I've already touched (your code is almost as complex as the routine under test; it is not obvious how having this code can positively influence design; etc).

There are also, in this specific case, a few minor issues (like: you probably want to take a distance in the HSV space, not in the RGB one) and major issue (like: are you sure your simple reference implementation is truly a good reference implementation of a smoothed shape, without testing it? What is a good threshold for difference? Your concept of smooth circle is quite different from what I would have implemented, anyway :-))), but that could be an issue with my wishy-washy (aka realistic :-)) specification.

Still, your approach is extremely valuable in a large set of cases. Indeed, it is known in the (traditional) testing literature as comparison testing, and yes, it's often applied to algorithms, as you suggested.
However, we usually adopt comparison testing when we want the same functional results (and different non-functional attributes, usually higher performance).
A simple example: years ago I designed (taking lots of suggestions from the literature :-)) and then implemented a parallel sorting algorithm. It was quite tricky, as sorting does not lend itself easily to [efficient] parallelism, and there were also (among other things) non-trivial issues with cache line contention on dual processors boxes working on memory-intense algorithms.
Anyway, the bright side was that it was trivial to compare the results of my algorithm against a slow, traditional, tried and true :-) reference implementation. The bonus was, of course, that I didn't have to implement a reference implementation, it was already there. And the results had to match exactly, without a blurry concept of "almost equal".
Of course, no amount of testing could ever prove that my algorithm didn't contain some concurrency bug, like a race condition. Since that code had to be used in a pretty serious application, in the end I decided to use also a model checker (SPIN, http://spinroot.com/spin/whatispin.html). Maybe that was not agile :-), but I'm still pretty sure it was the right thing to do :-)).

Thanks again for bringing up the issue - I wish the whole conversation was so anchored on the real stuff, and not on evangelism :-))).