WCF サービスクラスの単体テストを楽にする ServiceScope クラス

私の WCF サービスクラスの単体テストコードは、いつも大体次のようになっています。

ServiceHost host = new ServiceHost(typeof(FooService));
string address = "net.tcp://localhost/FooService";
host.AddServiceEndpoint(
    typeof(IFooService),
    new NetTcpBinding(),
    address);
host.Open();

IFooService proxy = ChannelFactory<IFooService>.CreateChannel(
    new NetTcpBinding(),
    new EndpointAddress(address));
proxy.GetFoo();

((IClientChannel)proxy).Close();
host.Close();

単純なメソッドのテストでも、これだけのコード量です。これが複雑な処理を行うメソッドのテストになったら…。考えただけでもゾッとします。腱鞘炎になってしまうよ!

WCF サービスの場合、どんなテストでも、次の処理は必ず必要です。

  1. ServiceHost にサービスクラスを登録
  2. エンドポイントを登録
  3. サービス起動
  4. プロキシ生成
  5. サービスの呼び出し
  6. プロキシを閉じる
  7. サービスを閉じる

これらのコードを毎回記述するのはすごく面倒。そこで、「サービスの呼び出し」以外をやってくれるクラスを作ってみました。

public class ServiceScope<TService, TChannel> : IDisposable
    where TService : class, new()
    where TChannel : class
{
    private ServiceHost _host;
    private Binding _binding;
    private string _address;
    private TChannel _channel;

    public ServiceScope(Binding binding)
    {
        _host = new ServiceHost(typeof(TService));
        _binding=binding;
        _address = string.Format("{0}://localhost/{1}/{2}",
            binding.Scheme,
            typeof(TService).Name,
            typeof(TChannel).Name);

        _host.AddServiceEndpoint(typeof(TChannel),
            binding,
            _address);

        _host.Open();
    }

    public TChannel CreateProxy()
    {
        _channel = ChannelFactory<TChannel>.CreateChannel(
            _binding,
            new EndpointAddress(_address));
        return _channel;
    }

    public void Dispose()
    {
        if (_channel != null)
        {
            ((IClientChannel)_channel).Close();
        }

        if (_host != null)
        {
            _host.Close();
            _host = null;
        }
    }
}

使い方は次の通り。

using (ServiceScope<FooService, IFooService> scope
    = new ServiceScope<FooService, IFooService>(new NetTcpBinding()))
{
    // プロキシ作成
    var proxy = scope.CreateProxy();

    // サービスのメソッド呼び出し
    proxy.GetFoo();
}

最初のサンプルと比較して、コード量が半分以下になりました。手にも優しいですよ。