Pattern MVP: usare una classe base per ottimizzare il test dei Presenter

In questo ultimo periodo ho usato molto il pattern ModelViewPresenter. Per scrivere le mie triadi MVP ho utilizzato la tecnica del presenter first con ottimi risultati. All'interno dei miei unit-test ho fatto ampio uso di Mock Objects ed ho notato una certa ripetitività nel codice prodotto, soprattutto nei metodi di Setup e di TearDown. Con una soluzione simili a quella usata per il refactoring del presenter, ho usato i generics ed una classe base per evitarmi inutili ripetizioni.

Vediamo un esempio concreto. Di seguito un presenter con la sua View ed il suo Model

    public interface IMyView
    {
       
void ShowLoggedUser(string s);
    }
   
public interface IMyModel
    {
       
string GetUserName();
    }

   
public class MyPresenter :Presenter<IMyView,IMyModel>
    {
       
public MyPresenter(IMyView view,IMyModel model)
            :
           
base(view, model)
        {
        }

       
public void Init()
        {
            View.ShowLoggedUser(
"utente:"+ Model.GetUserName());
        }
    }/p>

 

Per testare il comportamento del metodo Init devo scrivere una classe di test come la seguente:

    public class MyPresenterFixture
    {
       
protected MockRepository _repository;
       
protected IMyView _view;
       
protected IMyModel _model;

        [
SetUp]
       
public virtual void SetUp()
        {
            _repository =
new MockRepository();
            _view = _repository.CreateMock<
IMyView>();
            _model = _repository.CreateMock<
IMyModel>();
        }

        [
TearDown]
       
public virtual void TearDown()
        {
            _repository.ReplayAll();
            _repository.VerifyAll();
        }

        [
Test]
       
public void Init_ReadUserNameFormModel_DisplayUserNameView()
        {
           
string user ="pippo";
           
Expect.Call(_model.GetUserName()).Return(user);
            _view.ShowLoggedUser(
"utente:" + user);
            _repository.ReplayAll();
           
MyPresenter presenter =new MyPresenter(_view, _model);
            presenter.Init();
        }
    }

Se lanciamo il test il risultato é:

1 passed, 0 failed, 0 skipped, took 3,52 seconds.

Ora nel caso volessi scrivere una nuova triade MVP avrei 3 nuovi tipi da testare (es. MyPresenter2, IMyView2, IMyModel2) ed una nuova classe di test che molto probabilmente si chiamerà MyPresenter2Fixture. Ora in questa classe MyPresenter2Fixture il metodo marcato con l'attributo TearDown sarà identico a quello del fratello MyPresenterFixture, mentre il metodo marcato con l'attributo Setup sarà diverso solo per i tipi di View e Model che dovrà moccare. Usando erditarietà e generics si può evitare parecchio copia ed incolla.

    public abstract class PresenterFixture<V, M>
    {
       
protected MockRepository _repository;protected V _view;
       
protected M _model;

        [
SetUp]
       
public virtual void SetUp()
        {
            _repository =
new MockRepository();
            _view = _repository.CreateMock<V>();
            _model = _repository.CreateMock<M>();
        }

        [
TearDown]
       
public virtual void TearDown()
        {
            _repository.ReplayAll();
            _repository.VerifyAll();
        }
    }

 

così facendo possiamo fare refactoring sulla classe MyPresenterFixture in questo modo

    public class MyPresenterFixture :PresenterFixture<IMyView,IMyModel>
    {
        [
Test]
       
public void Init_ReadUserNameFormModel_DisplayUserNameView()
        {
           
string user ="pippo";
           
Expect.Call(_model.GetUserName()).Return(user);
            _view.ShowLoggedUser(
"utente:" + user);
            _repository.ReplayAll();
           
MyPresenter presenter =new MyPresenter(_view, _model);
            presenter.Init();
        }
    }

ed quindi dimenticarci dei metodi di Setup e TearDown per tutte le classi derivate da PresenterFixture<V,M>.
Qualora fosse necessario è possibile fare l'override dei 2 metodi perchè nella classe base sono dichiarati come metodi virtual.

 

 

 

 

Pattern MVP: usare una classe base per ottimizzare i Presenter

In questo periodo ho scritto molto codice che usa il pattern MVP ed ho notato che il costruttore di un presenter fa bene o male fa quattro operazioni fondamentali:

  1. riceve riferimento ad un'instanza della view 
  2. controlla che la view non sia null
  3. riceve riferimento ad un'instanza del model
  4. controlla che il model non sia null

Con una sintassi simile a questa:

public class MyPresenter
    {
       
private readonly IMyModel _model;
       
private readonly IMyView _view;

       
public MyPresenter(IMyView view,IMyModel model)
        {
           
if (view ==null)
               
throw new ArgumentNullException("view");

           
if (model ==null)
               
throw new ArgumentNullException("model");

            _model = model;
            _view = view;
        }
    }/p>

 

In un progetto mediamente complesso capita di riscrivere parecchie volte lo stesso codice. E' questo male! Questa la soluzione che ho trovato per aggirare il problema.

Si parte isolando il codice in una classe astratta che usa una View e un Model generico

  public abstract class Presenter<V, M>
    {
       
private readonly M _model;
       
private readonly V _view;

       
protected V View
        {
           
get {return _view; }
        }

       
protected M Model {get {return _model; }
        }

       
public Presenter(V view, M model)
        {
           
if (view ==null)
               
throw new ArgumentNullException("view");

           
if (model ==null)
               
throw new ArgumentNullException("model");

            _model = model;
            _view = view;
        }
    }

e quindi i presenter concreti diventano molto più semplici riulizzando più volte il medesimo pezzo di codice

public class MyPresenter : Presenter<IMyView,IMyModel>
    {
       
public MyPresenter(IMyView view, IMyModel model)
            :
base(view, model)
        {
        }
    }

 public class MyPresenter2 :Presenter<IMyView2,IMyModel2>
    {
       
public MyPresenter2(IMyView2 view,IMyModel2 model)
            :
base(view, model)
        {
        }
    }

 

 

p>