4

Model / View / ViewModel パターンとそのバリエーション (DataModel / View / ViewModel、または Model / View / Presenter) を学習しているところです。

私が疑問に思っているのは、このパターンを WCF サービスで使用する場合、サービスはモデル (DataModel) ですか、それとも WCF サービス レイヤーをカプセル化するために別のモデルが必要ですか??

WCF を DataModel として使用すると、WCF への呼び出しで接続を管理する必要があるため、WCF サービス全体をモックしないと ViewModel をテストできません。この 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 を使用して別の DataModel レイヤーを View / ViewModel アプリケーションに追加すると、多くの作業が追加されるように見えますが、テスト容易性は向上しません。

4

2 に答える 2

4

Woot、この問題に 2 日間取り組んだ後、私は一緒に暮らすことができる解決策を見つけました。

上記のコード例に見られるように、このヘルパー クラスを使用して 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 が原因で、リリース時にテストがコンパイルされないことは承知しています。気にするなら追い出してください。

于 2009-01-08T15:41:04.173 に答える
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のようなモッキング フレームワークを使用します。

于 2010-04-28T12:03:27.933 に答える