AntonioGanci

Il blog di Antonio Ganci
posts - 201, comments - 420, trackbacks - 31

Binding di dati ad una lista sviluppato utilizzando il TDD

Devo riempire una grid con i seguenti dati:

Lista fabbricati

La quale rappresenta un esempio di una lista di fabbricati di una pratica ICI. Ogni riga rappresenta la titolarità di un fabbricato quindi sono rappresentati tre fabbricati di cui il primo con due titolarita.

Come vediamo la rappresentazione dei dati ha un pò di logica ad esempio la prima colonna ha due formattazioni diverse in base ad un valore booleano, poi dalla seconda colonna i dati legati al fabbricato vengono ripetuti se ci sono diverse titolarità ed infine ci sono alcuni importi da visualizzare.

Vediamo la parte di dominio legata ai dati della lista:

Fabbricati class diagram

Come si vede abbiamo due relazioni uno a molti: Pratica -> Fabbricati e Fabbricato -> Titolarita.

Partiamo dal setup del test di accettazione (per chi non lo sapesse un test di accettazione è un test end to end, cioè testa la feature con tutti i componenti):

[SetUp]
public void SetUp()
{
    Pratica praticaIci = new Pratica();
 
    Fabbricato fabbricatoMilano = new Fabbricato
      {
        Verificato = true, 
        Progressivo = 1, 
        Comune = "Milano", 
        Indirizzo = "Viale Roma"
      };
    fabbricatoMilano.Titolarita.Add(new Titolarita{ RenditaCatastale = 180.76M, ValoreFabbricato = 18980.70M });
    fabbricatoMilano.Titolarita.Add(new Titolarita{ RenditaCatastale = 93.02M, ValoreFabbricato = 8950.20M });
    praticaIci.Fabbricati.Add(fabbricatoMilano);
  ...
}

Ho messo solo il primo per leggibilità. Vediamo il codice del test ora:

[Test]
public void ShouldFillFabbricatiGrid()
{
    Grid grid = new Grid();
    FabbricatiGridFiller filler = new FabbricatiGridFiller(grid);
 
    filler.Fill(praticaIci);
 
    Assert.That(grid.ColumnHeaders[0].Text, Is.EqualTo("Verif."));
    Assert.That(grid[0, 0].Text, Is.EqualTo("SI"));
    Assert.That(grid[0, 0].BackColor, Is.EqualTo(Color.LightGreen));
    Assert.That(grid[0, 0].FontStyle, Is.EqualTo(FontStyle.Bold));
    Assert.That(grid[1, 0].Text, Is.Empty);
    Assert.That(grid.ColumnHeaders[1].Text, Is.EqualTo("Progr."));
    Assert.That(grid[0, 1].Text, Is.EqualTo("1"));
    Assert.That(grid[1, 1].Text, Is.EqualTo("1"));
}

Ho riportato solo una parte delle Assert, ma il concetto spero sia chiaro.

A questo punto facciamo compilare il codice del test creando delle classi senza implementazione, che non riporto.

Il motivo per cui sono partito dal test di accettazione sta nel fatto che mi mi chiarisce l'obiettivo a cui devo arrivare, ma ora utilizzerò il TDD per arrivare all'implementazione della feature. Quindi non preoccupatevi in questo test non ho usato nessun Big Design Up Front (BDUF).

Qual è il prossimo passo?

Per rispondere a questa domanda definiamo il principio che vogliamo che il nostro design debba rispettare. Nel mio caso specifico ho molte liste da riempire simili a questa i cui dati sono memorizzati in classi analoghe a Fabbricato, Titolarita ecc. Quindi voglio descrivere il binding dei dati senza dipendere dai dati stessi. Inoltre non voglio utilizzare la reflection, ma un grafo di oggetti che collaborino tra loro per ottenere il risultato.

Partirò dalla relazione tra gli oggetti che contengono i dati (Pratica, Fabbricato, Titolarita). Come si può vedere sono due relazioni uno a molti la prima tra Pratica e Fabbricato, la seconda tra Fabbricato e Titolarita.

Come chiamiamo questo oggetto? OneToManyDataComponentRelation. Gli oggetti come Fabbricato li ho battezzati DataComponent e sono oggetti solitamente mappati con una tabella e che mi arrivano da un ORM di vostra scelta.

Vediamo il test:

[Test]
public void ShouldProcessRelatedComponents()
{
    var componentProcessor = this;
    var relation = new OneToManyDataComponentRelation<Pratica, Fabbricato>(
        pratica => pratica.Fabbricati, componentProcessor);
 
    relation.ProcessRelatedComponents(praticaIci);
 
    Assert.That(componentProcessedCount, Is.EqualTo(2));
}

Se vediamo il codice della classe penso che risulterà più semplice la comprensione del test:

public class OneToManyDataComponentRelation<TOne, TMany>
{
    private readonly Func<TOne, IEnumerable<TMany>> m_getRelatedComponentsFunc;
    private readonly IDataComponentProcessor<TMany> m_relatedComponentProcessor;
 
    public OneToManyDataComponentRelation(
        Func<TOne, IEnumerable<TMany>> getRelatedComponentsFunc, 
        IDataComponentProcessor<TMany> relatedComponentProcessor)
    {
        m_getRelatedComponentsFunc = getRelatedComponentsFunc;
        m_relatedComponentProcessor = relatedComponentProcessor;
    }
 
    public void ProcessRelatedComponents(TOne dataComponent)
    {
        IEnumerable<TMany> components = m_getRelatedComponentsFunc.Invoke(dataComponent);
        foreach (var component in components)
        {
            m_relatedComponentProcessor.Process(component);
        }
    }
}

In pratica ho definito una relazione uno a molti tra due tipi in questo Caso Pratica e Fabbricato. E per ogni link della relazione viene eseguito il metodo process dell'interfaccia IDataComponent.

Un'altra cosa interessante è l'inizializzazione del data processor a this e cioè alla classe di test. Questa tecnica si chiama Self Shunt ed è utile quando il test di interazione è molto semplice. Vediamo la classe di test al completo:

[TestFixture]
public class OneToManyDataComponentRelationTests : IDataComponentProcessor<Fabbricato>
{
    private int componentProcessedCount;
    private Pratica praticaIci;
 
    [SetUp]
    public void SetUp()
    {
        componentProcessedCount = 0;
        praticaIci = new Pratica();
        praticaIci.Fabbricati.Add(new Fabbricato());
        praticaIci.Fabbricati.Add(new Fabbricato());
    }
 
    [Test]
    public void ShouldProcessRelatedComponents()
    {
        var componentProcessor = this;
        var relation = new OneToManyDataComponentRelation<Pratica, Fabbricato>(
            pratica => pratica.Fabbricati, componentProcessor);
 
        relation.ProcessRelatedComponents(praticaIci);
 
        Assert.That(componentProcessedCount, Is.EqualTo(2));
    }
 
    void IDataComponentProcessor<Fabbricato>.Process(Fabbricato dataComponet)
    {
        componentProcessedCount++;
    }
}

Ora una cosa che mi piacerebbe è di poter concatenare le relazioni.

    new OneToManyDataComponentRelation<Pratica, Fabbricato>(
        pratica => pratica.Fabbricati,
        new OneToManyDataComponentRelation<Fabbricato, Titolarita>(
            fabbricato => fabbricato.Titolarita,
            ...));

Per ottenere questo risultato basta semplicemente far implementare l'interfaccia IDataComponentProcessor alla nostra relation:

public class OneToManyDataComponentRelation<TOne, TMany> : IDataComponentProcessor<TOne>
{
    private readonly Func<TOne, IEnumerable<TMany>> m_getRelatedComponentsFunc;
    private readonly IDataComponentProcessor<TMany> m_relatedComponentProcessor;
 
    public OneToManyDataComponentRelation(
        Func<TOne, IEnumerable<TMany>> getRelatedComponentsFunc, 
        IDataComponentProcessor<TMany> relatedComponentProcessor)
    {
        m_getRelatedComponentsFunc = getRelatedComponentsFunc;
        m_relatedComponentProcessor = relatedComponentProcessor;
    }
 
    public void Process(TOne dataComponent)
    {
        IEnumerable<TMany> components = m_getRelatedComponentsFunc.Invoke(dataComponent);
        foreach (var component in components)
        {
            m_relatedComponentProcessor.Process(component);
        }
    }
}

Scriverò un altro post per la parte rimanente.

Print | posted on mercoledì 30 dicembre 2009 12:10 | Filed Under [ Extreme Programming ]

Comments have been closed on this topic.

Powered by:
Powered By Subtext Powered By ASP.NET