Pergunta

Estou aprendendo o padrão Model / View / ViewModel e é variações (DataModel / View / ViewModel, ou Model / View / Presenter).

O que eu pergunto é: se eu usar esse padrão com um serviço WCF, é o serviço do Modelo (DataModel), ou eu preciso um modelo separado para encapsular a camada de serviço WCF ??

Quando eu uso WCF como DataModel minha ViewModel não é testável sem zombando todo o serviço WCF, uma vez que as chamadas para WCF necessidade de gerenciar a conexão. As chamadas neste olhar ViewModel como esta:

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

Para obter o meu ViewModel testável Eu tentei adicionar um DataModel separado para conexão WCF abstrair. Depois disso, o ViewModel era testável, as chamadas ficou assim:

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

Problema: a maior parte do código que precisam ser testados agora tinha se mudado para o DataModel, que, mais uma vez, precisava WCF para ser testado. O que permaneceu no ViewModel foi uma casca fina, que pode ser testado. Mas desde que o código principal se mudou para o DataModel, então testar o ViewModel foi completamente inútil.

Então, para mim, parece adicionar uma camada DataModel separado para uma aplicação Ver / ViewModel usando WCF não adicionar um monte de trabalho, mas a capacidade de teste não recebe qualquer melhor.

Foi útil?

Solução

Woot, após dois dias de wacking afastado neste problema que eu encontrei uma solução que eu posso viver com:

Como visto no exemplo de código acima, eu uso essa classe auxiliar para gerenciar minha conexão WCF (por causa do tratamento adequado dos Fechar 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();
      }
    }
  }
}

Como visto na minha pergunta, esta é a forma como esta classe é usada na minha ViewModel:

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

Para zombar do WCF interface I seria necessário para criar e hospedar um serviço WCF simulada, altere todas as seqüências de conexão. Um monte de trabalho apenas para adicionar alguns testes.

Eu encontrei uma maneira mais fácil: É simples de criar um serviço de simulação da implementação da interface:

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

O único problema é:? como faço para fazer o ViewModel chamar este mock-classe em vez do serviço
A solução: Service.Use () faz gerenciar a conexão. Ao adicionar funcionalidades para gerenciamento de conexão override eu sou capaz de esgueirar-se meu próprio objeto WCF simulada em Service.Use ().
Para isso eu preciso de uma maneira de fazer Service.Use () chamada algo diferente do WCF (olhada nas seções #Depurar):

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

Ao adicionar este gancho de teste em serviço, eu sou capaz de esgueirar-se em qualquer objeto implementando T em meus testes:

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

Para mim esta é uma maneira muito boa para zombar um serviço WCF!

PS: Estou ciente de que, devido à DEBUG #if os testes não irá compilar na liberação. Apenas expulsá-los se você se importa.

Outras dicas

Nós usamos Dependency Injection para resolver este problema, injetando o cliente de serviço (ou talvez uma fábrica para clientes de serviço) para o ViewModel. Algo parecido com isto:

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

Eu mostrei um exemplo usando simulações de mão-código. Normalmente eu usaria um quadro de zombaria como Moq .

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top