Модель - Вид - ViewModel и WCF - это модель WCF? - PullRequest
4 голосов
/ 07 января 2009

Я только изучаю модель Model / View / ViewModel и ее варианты (DataModel / View / ViewModel или Model / View / Presenter).

Что мне интересно: если я использую этот шаблон с сервисом WCF, это сервис Модель (DataModel) или мне нужна отдельная Модель для инкапсуляции уровня сервиса WCF ??

Когда я использую WCF в качестве DataModel, моя ViewModel не может быть протестирована без проверки всей службы WCF, поскольку вызовы WCF должны управлять соединением. Вызовы в этой ViewModel выглядят так:

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

Чтобы получить тестируемый мой ViewModel, я попытался добавить отдельную DataModel к абстрактному соединению WCF. После этого ViewModel был тестируемым, вызовы выглядели так:

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

Проблема: большая часть кода, который должен был быть протестирован, теперь переместилась в DataModel, которая, опять же, нуждалась в WCF для тестирования. В ViewModel осталась тонкая оболочка, которую можно протестировать. Но поскольку основной код переместился в DataModel, тестирование ViewModel оказалось совершенно бесполезным.

Так что мне кажется, что добавление отдельного слоя DataModel в приложение View / ViewModel с использованием WCF добавляет много работы, но тестируемость не улучшается.

Ответы [ 2 ]

4 голосов
/ 08 января 2009

Woot, после двух дней безумного обращения к этой проблеме я нашел решение, с которым я могу жить:

Как видно из приведенного выше примера кода, я использую этот вспомогательный класс для управления моим соединением WCF (из-за правильной обработки Close против 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();
      }
    }
  }
}

Как видно из моего вопроса, этот класс используется в моей ViewModel:

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

Чтобы смоделировать интерфейс WCF, мне нужно создать и разместить фиктивную службу WCF, изменить все строки подключения. Много работы, чтобы добавить несколько тестов.

Я нашел более простой способ: Создать виртуальный сервис, реализующий интерфейс, просто:

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

Единственная проблема: как заставить ViewModel вызывать этот mock-класс вместо службы?
Решение: Service.Use () управляет соединением. Добавив функциональность для переопределения управления соединениями, я могу добавить свой собственный объект-макет WCF в Service.Use ().
Для этого мне нужен способ заставить Service.Use () вызывать что-то отличное от WCF (посмотрите разделы #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();
      }
    }
  }
}

Добавив этот тестовый хук в Service, я могу проникнуть в любой объект, реализующий T в моих тестах:

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

Для меня это очень хороший способ издеваться над сервисом WCF!

PS: я знаю, что из-за #if DEBUG тесты не будут компилироваться в релизе. Просто выгони их, если тебе не безразлично.

2 голосов
/ 28 апреля 2010

Мы используем Dependency Injection для решения этой проблемы, внедряя клиент сервиса (или, возможно, фабрику для клиентов сервиса) в ViewModel. Примерно так:

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

Я показал пример с использованием ложных кодов. Обычно я бы использовал Mocking Framework, как Moq .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...