我只是在学习Model / View / ViewModel模式及其变体(DataModel / View / ViewModel,或Model / View / Presenter)。
我想知道的是:如果我将此模式与WCF服务一起使用,那么服务是Model(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是没用的。
所以对我而言,似乎使用WCF向View / ViewModel应用程序添加单独的DataModel层确实增加了很多工作,但可测试性没有任何改善。
答案 0 :(得分:4)
Woot,经过两天的解决这个问题,我找到了一个可以忍受的解决方案:
如上面的代码示例所示,我使用此帮助程序类来管理我的WCF连接(因为正确处理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();
}
}
}
}
正如我的问题所示,这是我的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-class而不是服务?
解决方案: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,测试不会在发布时编译。如果你在意的话,就把它们踢掉。
答案 1 :(得分:2)
我们使用依赖注入来解决此问题,将服务客户端(或服务客户端的工厂)注入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这样的Mocking框架。