Domanda

Sto solo imparando il modello / View / ViewModel e le sue varianti (DataModel / View / ViewModel o Model / View / Presenter).

Quello che mi chiedo è: se uso questo modello con un servizio WCF, è il servizio Model (DataModel) o ho bisogno di un modello separato per incapsulare il livello di servizio WCF ??

Quando utilizzo WCF come DataModel il mio ViewModel non è testabile senza deridere l'intero servizio WCF, poiché le chiamate a WCF devono gestire la connessione. Le chiamate in questo ViewModel assomigliano a questo:

List<Sam.Alyza.WcfInterface.Website> rc = null;
Service<Sam.Alyza.WcfInterface.IServiceWebsites>.Use(alyzaSvc =>
{
  rc = new List<Sam.Alyza.WcfInterface.Website>(alyzaSvc.GetSites());
});

Per testare il mio ViewModel ho provato ad aggiungere un DataModel separato alla connessione astratta WCF. Dopo questo ViewModel era testabile, le chiamate apparivano così:

List<Sam.Alyza.WcfInterface.Website> rc = new List<Sam.Alyza.WcfInterface.Website>(_datamodel.GetSites());

Problema: la maggior parte del codice che avrebbe dovuto essere testato ora era stato spostato nel DataModel, che, di nuovo, aveva bisogno di essere testato da WCF. Ciò che rimaneva nel ViewModel era un guscio sottile, che può essere testato. Ma dal momento che il codice principale è stato spostato nel DataModel, testare il ViewModel è stato piuttosto inutile

Quindi a me sembra che aggiungere un livello DataModel separato a un'applicazione View / ViewModel usando WCF aggiunga molto lavoro, ma la testabilità non migliora.

È stato utile?

Soluzione

Woot, dopo due giorni passati a risolvere questo problema, ho trovato una soluzione con cui convivere:

Come visto nell'esempio di codice sopra, utilizzo questa classe di supporto per gestire la mia connessione WCF (a causa della corretta gestione di Close vs Abort):

public delegate void UseServiceDelegate<T>(T proxy);

public static class Service<T>
{
  public static ChannelFactory<T> _channelFactory;

  public static void Use(UseServiceDelegate<T> codeBlock)
  {
    if (_channelFactory == null)
      _channelFactory = new ChannelFactory<T>("AlyzaServiceEndpoint");
    IClientChannel proxy = (IClientChannel)_channelFactory.CreateChannel();
    bool success = false;
    try
    {
      codeBlock((T)proxy);
      proxy.Close();
      success = true;
    }
    finally
    {
      if (!success)
      {
        proxy.Abort();
      }
    }
  }
}

Come visto nella mia domanda, questo è il modo in cui questa classe viene utilizzata nel mio ViewModel:

Service<Sam.Alyza.WcfInterface.IServiceWebsites>.Use(alyzaSvc =>
{
  rc = new List<Sam.Alyza.WcfInterface.Website>(alyzaSvc.GetSites());
});

Per deridere l'interfaccia WCF avrei bisogno di creare e ospitare un servizio WCF finto, cambiare tutte le stringhe di connessione. Molto lavoro solo per aggiungere alcuni test.

Ho trovato un modo più semplice: È semplice creare un servizio simulato implementando l'interfaccia:

public class MockWebsiteService : WcfInterface.IServiceWebsites
{
  internal List<Sam.Alyza.WcfInterface.Website> _websites = new List<Sam.Alyza.WcfInterface.Website>();
  internal int _GetSitesCallCount;

  IEnumerable<Sam.Alyza.WcfInterface.Website> Sam.Alyza.WcfInterface.IServiceWebsites.GetSites()
  {
    _GetSitesCallCount++;
    return _websites;
  }
}

L'unico problema è: come posso fare in modo che ViewModel chiami questa classe simulata anziché il servizio?
Una soluzione: Service.Use () gestisce la connessione. Aggiungendo funzionalità per sovrascrivere la gestione delle connessioni sono in grado di intrufolare il mio oggetto mock WCF in Service.Use ().
Per questo ho bisogno di un modo per fare in modo che Service.Use () chiami qualcosa di diverso da WCF (guarda le sezioni #DEBUG):

public static class Service<T>
{
#if DEBUG
  public static T DebugOverride = default(T);
#endif
  public static ChannelFactory<T> _channelFactory;

  public static void Use(UseServiceDelegate<T> codeBlock)
  {
#if DEBUG
    if (!Object.Equals(DebugOverride, default(T)))
    {
      codeBlock(DebugOverride);
      return;
    }
#endif
    if (_channelFactory == null)
      _channelFactory = new ChannelFactory<T>("AlyzaServiceEndpoint");
    IClientChannel proxy = (IClientChannel)_channelFactory.CreateChannel();
    bool success = false;
    try
    {
      codeBlock((T)proxy);
      proxy.Close();
      success = true;
    }
    finally
    {
      if (!success)
      {
        proxy.Abort();
      }
    }
  }
}

Aggiungendo questo hook di test al servizio, sono in grado di intrufolarmi in qualsiasi oggetto che implementa T nei miei test:

MockWebsiteService mockmodel = new MockWebsiteService();
Service<WcfInterface.IServiceWebsites>.DebugOverride = mockmodel;
// run my tests here

Per me questo è un ottimo modo per deridere un servizio WCF!

PS: sono consapevole che a causa di #if DEBUG i test non verranno compilati in rilascio. Se non ti interessa, espellili.

Altri suggerimenti

Utilizziamo Dependency Injection per affrontare questo problema, iniettando il client di servizio (o forse una factory per i client di servizio) nel ViewModel. Qualcosa del genere:

interface IClientFactory
{
    TClient CreateClient<TClient>();
}

class ClientFactory : IClientFactory
{
    TClient CreateClient<TClient>() 
    {
       var channelFactory = new ChannelFactory<TClient>("AlyzaServiceEndpoint");
       var proxy = (TClient)channelFactory.CreateChannel();
       return proxy;
    }
}

public ViewModel 
{
    public ViewModel(IClientFactory clientFactory)
    {
       _clientFactory = clientFactory;
    }

    private void DoWcfStuff()
    {
        using (var proxy = _clientFactory.CreateClient<IClientChannel>())
        {
           var result = proxy.GetThings();
        }
    }
}

public ViewModelTests
{
    public void Setup()
    {
       _mockFactory = new MockClientFactory();
       _viewModel = new ViewModel(_mockFactory);
    }

    [Test]
    public void Test() 
    {
       var testResult = new Result();
       var mockClient = _mockFactory.CreateClient<IClientChannel>();

       mockClient.SetResultForGetThings(testResult);

       // put the viewmodel through its paces.
    }

    private class MockClientFactory : IClientFactory
    {
        MockClient _mockClient;

        public MockClientFactory()
        {
          _mockClient = new MockClient();
        }

        public TClient CreateClient<TClient>()
        {
           if (typeof(TClient) == typeof(IClientChannel))
           {
              return _mockClient;
           }
        }
    }

    private class MockClient : IClientChannel
    {
        void SetupGetThingsResult(Result result)
        {
           _result = result;
        }

        Result GetThings() 
        {
           return _result;
        }
    }
}

Ho mostrato un esempio usando simulazioni di codici manuali. Di solito userei un framework beffardo come Moq .

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top