モデル-ビュー-ViewModel& WCF-WCFはモデルですか?
-
05-07-2019 - |
質問
Model / View / ViewModelパターンとそのバリエーション(DataModel / View / ViewModel、またはModel / View / Presenter)を学習しています。
私が疑問に思うのは、このパターンをWCFサービスで使用する場合、そのサービスはモデル(DataModel)ですか、それともWCFサービスレイヤーをカプセル化するために別のモデルが必要ですか?
WCFをDataModelとして使用する場合、WCFへの呼び出しは接続を管理する必要があるため、ViewModelは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レイヤーを追加すると多くの作業が追加されるように見えますが、テスト容易性は向上しません。
解決
すみません、この問題に2日間気づいた後、私は一緒に暮らせる解決策を見つけました:
上記のコード例に見られるように、このヘルパークラスを使用して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がサービスの代わりにこのモッククラスを呼び出すようにする方法
解決策: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により、リリースでテストがコンパイルされないことを認識しています。気にしたらそれらを追い出します。
他のヒント
依存性注入を使用してこの問題に取り組み、サービスクライアント(またはサービスクライアントのファクトリ)を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 のようなモックフレームワークを使用します。