模型-视图-视图模型 & WCF - WCF是模型吗?

4

我正在学习模型/视图/视图模型(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中,而这个DataModel又需要用WCF进行测试。ViewModel中所剩下的只是一个薄壳,可以进行测试。但由于主要代码已经移动到DataModel中了,所以测试ViewModel就变得相当无用了。
因此,在我的看来,在使用WCF为View/ViewModel应用程序添加一个单独的DataModel层确实增加了很多工作量,但可测试性并没有得到任何改善。
2个回答

4

哇,经过两天的艰苦努力,我终于找到了一个令我满意的解决方案:

如上面的代码示例所示,我使用这个辅助类来管理我的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调用这个模拟类而不是服务?
解决方案: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
我们使用依赖注入来解决这个问题,将服务客户端(或者是服务客户端工厂)注入到ViewModel中。类似于这样的代码:

我们使用依赖注入来解决这个问题,将服务客户端(或者是服务客户端工厂)注入到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;
        }
    }
}

我已经展示了一个手写模拟的例子。通常我会使用像Moq这样的模拟框架。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接