AntonioGanci

Il blog di Antonio Ganci
posts - 201, comments - 578, trackbacks - 27

Un tecnica per testare l'interazione tra oggetti

Ogni tanto, nello sviluppo software, emerge qualche meme come ad esempio: tutte le dipendenze devono essere disaccoppiate tramite un interface, tutte gli oggetti devono essere creati tramite factory, l'accesso ad un database deve avvenire tramite un ORM, il TDD genera un buon design ecc.

Cosa hanno in comune tutte le frasi precedenti? Semplice: creano scorciatoie per evitare di pensare. Sarebbe bello se potessimo scrivere codice seguendo alla lettera un manuale, ma sarebbe anche terribilmente noioso e probabilmente verremmo sostituiti da qualche tipo di bot.

Prendiamo la prima affermazione: tutte le dipendenze devono essere disaccoppiate tramite un interface e cerchiamo di capire da dove nasce questa esigenza. Il vantaggio di avere le dipendenze solo tramite interfacce dovrebbe essere quello di rendere più testabile le nostre classi. Ma è l'unico modo? Ed è il più semplice?

Proviamo a fare un esempio. Supponiamo di voler testare il metodo SayHello:

var display = new Display();
var greeting = new Greeting(display);
greeting.SayHello();

L'implementazione di Greeting è la seguente:

public class Greeting
{
    private readonly Display _display;

    public Greeting(Display display)
    {
        _display = display;
    }

    public void SayHello()
    {
        _display.Show("Hello!");
    }
}

Mentre l'implementazione di display è la seguente:

public class Display
{
    public void Show(string text)
    {
        Console.WriteLine(text);
    }
}

Sembra proprio il tipico esempio da manuale in cui si estrae l'interfaccia IDisplay e tramite la libreria di mocking preferita testo le interazioni di Greeting con Display. Esiste un'alternativa?

La classe Console eredita da TextWriter, anche la classe StringWriter eredita da TextWriter. Proviamo quindi a passare la Console in costruzione a Display:

public class Display
{
    private readonly TextWriter _textWriter;

    public Display(TextWriter textWriter)
    {
        _textWriter = textWriter;
    }

    public void Show(string text)
    {
        _textWriter.WriteLine(text);
    }
}

Ora modfichiamo il main:

var display = new Display(Console.Out);
var greeting = new Greeting(display);
greeting.SayHello();

A questo punto possiamo scrivere il test:

var stringWriter = new StringWriter();
var greeting = new Greeting(new Display(stringWriter));

greeting.SayHello();

Assert.That(stringWriter.ToString(), Is.EqualTo("Hello!"));

In questo modo abbiamo fatto un test più solido rispetto a testare l'interazione con un interfaccia ed il codice è più semplice.

Print | posted on lunedì 6 settembre 2010 8.54 | Filed Under [ Tips Extreme Programming ]

Feedback

Gravatar

# re: Un tecnica per testare l'interazione tra oggetti

un commento al disegno della soluzione che proponi, che trovo valida.

Qualora nel contesto in cui viene sviluppata l'applicazione Display rappresenta solo un dettaglio implementativo interno di Greeting, questa soluzione é superiore per semplicitá. Se cosi non fosse, per un requisito semplice come questo sarebbe assolutamente good enough. Specialmente quando si é consapevoli di tutte le implicazioni e si ffa una scelta di disegno, un tread-off intenzionale.

Preciso allora quelle che secondo me sono le caratteristiche di questa scelta.


Display usa TextWriter che é una classe astratta che rappresenta l'output a basso livello. Non é una interfaccia e come fosse una interfaccia fa si che Display con TextWriter rispetti il DIP e il OCP.
Infatti nel test viene sftuttato proprio il OCP per ignettare una diversa implementazione di TextWriter che bene si adatta a essere testata.

Greeting usa Display che é una classe concreta di piu basso livello (rispetto a Greeting), quindi in questo caso DIP e OCP sono violati. Questo comporta che in futuro per fare un SayHello() su un display differente sará necessario modificare Greeting invece che solamente implementare una interfaccia o classe derivata. Se Greeting fosse definito in un assembly di terze parti lo svantaggio diventerebbe insuperabile. Simmetricamente lo svantaggio é il non poter testare unitariamente Greeting, serve testare Greeting + Display insieme.

Invece una soluzione che definisca e usi una interfaccia IDisplay (che non esclude anche l'uso del TextWriter che trovo essere una scelta azzeccata) rispetterebbe DIP e OCP su tutta la linea permettendo di testare sia l'integrazione che unitariamente e di per fare un SayHello() su un display differente senza dover modificare Greeting.
Il solo aggiungere l'interfaccia o l'usare un tool di mocking o un mock fatto a mano, in alcune situazioni potrebbe essere una complessitá eccessiva.


Commento anche questa provocazione :)
> Cosa hanno in comune tutte le frasi precedenti? Semplice: creano scorciatoie per evitare di pensare

likando questa storia: googletesting.blogspot.com/.../...and-no-less.html


ti trovi d'accordo con questi commenti sul disegno ?
06/09/2010 20.30 | Luca Minudel
Gravatar

# re: Un tecnica per testare l'interazione tra oggetti

assolutamente d'accordo! e bello l'esempio :)

in sostanza, l'importante è mantenere un'astrazione, se questa esiste già (nel tuo caso TextWriter) allora usiamola, a patto che sia ad alto livello e non "di infrastruttura".

unica osservazione: nel caso di un test di interazione si sarebbe trattato di un test di unità di Greetings. Nel tuo esempio invece stai testando insieme Greetings e Display, quindi un test di integrazione. che va bene, ma bisogna esserne consapevoli ;)

ciao
-jacopo-
06/09/2010 22.04 | Jacopo
Gravatar

# re: Un tecnica per testare l'interazione tra oggetti

x Luca: carina la storia che hai linkato :-).
Se avessi due tipi di display e Greetings usasse uno o l'altro in base al contesto allora l'interfaccia sarebbe una buona soluzione altrimenti mi accontento.
Comunque sto rivalutando i test end-to-end cioè non testare unitariamente tutte le classi perchè questo comporta extra lavoro durante il refactoring.

x Jacopo:
Diciamo che i test unitari li sto scrivendo sempre meno e ne vedo sempre meno l'utilità.
07/09/2010 8.53 | Antonio Ganci
Gravatar

# re: Un tecnica per testare l'interazione tra oggetti

> Comunque sto rivalutando i test end-to-end cioè non testare unitariamente tutte le classi perchè questo
> comporta extra lavoro durante il refactoring.

quando avrai tempo posta su questa esperienza.

ho sentito dei commenti simili ma da parte di persone che col TDD avevano fatto casino e ora ci stavano riprovando col A-TDD.

personalmente sono soddisfatto di un mi equilibrio trovato col TDD + A-TDD e dello stile di TDD che ho raggiunto che non rompe test a sproposito quando faccio refactoring. ma sono certo che ne ho ancora da imparare :)

quindi leggerei volentieri il tuo resoconto.

07/09/2010 17.36 | Luca Minudel
Gravatar

# re: Un tecnica per testare l'interazione tra oggetti

> quando avrai tempo posta su questa esperienza.
ho sentito dei commenti simili ma da parte di persone che col TDD avevano fatto casino e ora ci stavano riprovando col A-TDD.

Posterò volentieri sull'argomento. Il motivo per cui sto facendo così non è stato tanto perchè facessi "casino", ma dopo aver partecipato al corso di Cirillo lui mi ha spiegato che è il TDD secondo XP che fa test in quel modo. Ed effettivamente ho notato un miglioramento sia nella qualità del codice che è più semplice sia nella malleabilità.
08/09/2010 9.13 | Antonio Ganci

Post Comment

Title  
Name  
Email
Url
Comment   
Please add 8 and 1 and type the answer here:

Powered by: