Pregunta

Estoy aprendiendo el patrón Modelo / Vista / Modelo de vista y sus variaciones (Modelo de datos / Vista / Modelo de vista o Modelo / Vista / Presentador).

Lo que me pregunto es: si uso este patrón con un servicio WCF, ¿es el servicio el Modelo (DataModel), o necesito un Modelo separado para encapsular la capa de servicio WCF?

Cuando uso WCF como DataModel, mi ViewModel no es comprobable sin burlarse de todo el servicio WCF, ya que las llamadas a WCF deben administrar la conexión. Las llamadas en este ViewModel se ven así:

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

Para que mi ViewModel sea comprobable, intenté agregar un DataModel separado para abstraer la conexión WCF. Después de esto, el ViewModel fue comprobable, las llamadas se veían así:

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

Problema: la mayor parte del código que debería probarse ahora se había trasladado al DataModel, que, de nuevo, necesitaba WCF para probarse. Lo que quedó en el ViewModel fue un caparazón delgado, que se puede probar. Pero dado que el código principal se movió al DataModel, probar el ViewModel fue bastante inútil.

Entonces, para mí parece que agregar una capa de DataModel por separado a una aplicación View / ViewModel usando WCF agrega mucho trabajo, pero la capacidad de prueba no mejora.

¿Fue útil?

Solución

Woot, después de dos días de resolver este problema, encontré una solución con la que puedo vivir:

Como se ve en el ejemplo de código anterior, uso esta clase auxiliar para administrar mi conexión WCF (debido al manejo adecuado de Cerrar vs Abortar):

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();
      }
    }
  }
}

Como se ve en mi pregunta, esta es la forma en que esta clase se usa en mi ViewModel:

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

Para simular la interfaz WCF, necesitaría crear y alojar un servicio WCF simulado, cambiar todas las cadenas de conexión. Mucho trabajo solo para agregar algunas pruebas.

Encontré una manera más fácil: Es simple crear un servicio simulado que implemente la interfaz:

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;
  }
}

El único problema es: ¿cómo hago para que ViewModel llame a esta clase simulada en lugar del servicio?
Una solución: Service.Use () gestiona la conexión. Al agregar funcionalidad para anular la administración de la conexión, puedo introducir mi propio objeto simulado WCF en Service.Use ().
Para esto, necesito una forma de hacer que Service.Use () llame a algo diferente a WCF (mira las secciones #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();
      }
    }
  }
}

Al agregar este gancho de prueba al Servicio, puedo colarse en cualquier objeto que implemente T en mis pruebas:

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

¡Para mí esta es una muy buena manera de burlarse de un servicio WCF!

PD: Soy consciente de que debido a #if DEBUG, las pruebas no se compilarán en el lanzamiento. Simplemente sáquelos si le importa.

Otros consejos

Utilizamos la Inyección de dependencias para abordar este problema, inyectando el cliente de servicio (o tal vez una fábrica para clientes de servicio) en ViewModel. Algo como esto:

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;
        }
    }
}

He mostrado un ejemplo usando simulacros de código de mano. Por lo general, usaría un marco de Mocking como Moq .

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top