Procediamo con la terza parte (la seconda la trovate qui).
Siamo rimasti al punto di implementare il model, il quale lo svilupperemo rigorosamente in TDD. Lo scheletro del model è definito dall'interfaccia ISelectSymbolModel che è venuta fuori dai test scritti precedentemente. Ora andremo a testare la segnalazione dell'evento SymbolNameListChanged, a cui il presenter è sottoscritto, quando viene inizializzata la lista con i nomi dei titoli.
Creiamo quindi la classe SelectSymbolModel con il primo test:
using MbUnit.Framework;
namespace PresenterFirstExample
{
[TestFixture]
public class SelectSymbolModelTests
{
private int _symbolNameListChangedCallCount;
string[] _symbolNameList = new string[] { "ENEL-MIL", "G-MIL", "AL-MIL", "AGL-MIL" };
[Test]
public void WhenTheSymbolNameListIsInitializedTheModelRaisesTheSymbolNameListChangedEvent()
{
SelectSymbolModel model = new SelectSymbolModel();
_symbolNameListChangedCallCount = 0;
model.SymbolNameListChanged += new System.EventHandler<StringListEventArgs>(Model_SymbolNameListChanged);
model.SetSymbolNameList(_symbolNameList);
Assert.AreEqual(1, _symbolNameListChangedCallCount);
}
void Model_SymbolNameListChanged(object sender, StringListEventArgs e)
{
_symbolNameListChangedCallCount++;
ArrayAssert.AreEqual(_symbolNameList, e.StringList);
}
}
}
Analizziamo il codice che abbiamo appena scritto: ciò che vogliamo testare è che quando viene richiamato il metodo SetSymbolNameList viene segnalato l'evento SymbolNameListChanged; quindi una volta creata la classe SelectSymbolModel ci sottoscriviamo all'evento e controlliamo che il metodo venga richiamato una sola volta, inoltre controlliamo anche che il parametro dell'evento corrisponda alla lista dei titoli.
Per fare passare il test dobbiamo creare la classe del model:
using System;
namespace PresenterFirstExample
{
class SelectSymbolModel : ISelectSymbolModel
{
public event EventHandler<StringListEventArgs> SymbolNameListChanged;
public event EventHandler<StringListEventArgs> ChartPaneListChanged;
public void SetSymbolNameList(string[] symbolNames)
{
if (SymbolNameListChanged != null)
{
SymbolNameListChanged(this, new StringListEventArgs(symbolNames));
}
}
}
}
Il prossimo test da scrivere è molto simile a questo, cioè quando cambia l'elenco dei chart pane il model deve segnalare l'evento ChartPaneListChanged, non riporterò quindi il codice.
Manca ancora un ultimo tassello per poter vedere il tutto funzionare e cioè istanziare le classi e visualizzare la form, nel metodo main aggiungiamo il seguente codice:
using System;
using System.Windows.Forms;
namespace PresenterFirstExample
{
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
SelectSymbolForm form = new SelectSymbolForm();
SelectSymbolModel model = new SelectSymbolModel();
new SelectSymbolPresenter(model, form);
model.SetSymbolNameList(new string[] { "AL-MIL", "AGL-MIL", "ENEL-MIL" });
model.SetChartPaneList(new string[] {"One"});
Application.Run(form);
}
}
}
In un'applicazione reale l'elenco dei titoli si sarebbe probabilmente letto da un database, qui per rendere più semplice e leggibile il codice l'abbiamo creato a mano, se proviamo a selezionare un elemento dalla combobox dei chart pane vediamo che il radio button diventa checked.
Vediamo il class diagram delle classi e interfacce del model view presenter create fino ad ora:
Il presenter dipende dal model e dalle view tramite due interfacce, mentre il model e la view comunicano con il presenter solo attraverso eventi.
Indubbiamente il lavoro necessario a questo approccio è apparentemente molto superiore a quello di scrivere il codice direttamente nella form, ma consideriamo un'applicazione software durante tutto il ciclo di sviluppo e non solo durante l'implementazione di una prima versione. Solitamente dopo aver creato una prima versione della UI ci viene chiesto di modificarla oppure di supportare diverse modalità (smart client e web) ecco che qui i tempi si riducono drasticamente; non solo, anche il business layer cambia man mano che vengono aggiunti nuovi requisiti, ma grazie a questa architettura la riusabilità del codice già scritto è molto elevato. Infine abbiamo i test che ci garantiscono che i comportamenti attesi siano sempre verificati.
Un aiuto per velocizzare il lavoro meccanico è quello di creare un template che in un unico passo crei le classi, le interfacce e le classi di test.
Spero che questi articoli diano uno stimolo a provare questo approccio, che a fronte di alcune difficoltà iniziali si ripaga abbastanza in fretta.