かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

DIコンテナみたいなのは使えないけど、少しでもテストしやすいクラス構造にしたいね

Java界隈ではSpring FrameworkやSeasar2などが割と市民権を得ている感じ(残念ながら自分の仕事で見聞きする範囲では使われてるという噂を聞きませんが・・・)のDIコンテナですが、最近.NET界隈でもマイクロソフトがUnityをCodePlexで提供したりMEFなんていう拡張可能なアプリケーションを作るためのフレームワークといって実態はDIコンテナの機能を有したフレームワークを.NET Framework 4の標準ライブラリの一部としてリリースしたりしています。OSSではSpringやSeasarの.NETへの移植ものなんかもあります。


DIコンテナとインターフェースをうまいこと使えばクラス間の依存関係を分離できて単体テストがしやすいというメリットを得ることが出来ます。
たとえば以下のような感じです。

// 挨拶をするインターフェース
interface IGreeter
{
  string Greet();
}
class Greeter : IGreeter
{
  // 本来はDBなんかにアクセスしてデータとってきたり・・・
  public string Greet() { return "こんにちは"; }
}

// Greeterを使用するクラス
class Client
{
  // GreeterはDIコンテナにインジェクションしてもらう
  public IGreeter Greeter { get; set; }
  // 引数で受け取った名前の人に
  public string Execute(string name)
  {
    return string.Format("{0}さん,{1}", name, Greeter.Greet());
  }
}


DIコンテナが導入できない環境(というか私の周辺)では、上記のような例のコードは以下のように書かれています。

// インターフェースはない
public class Greeter
{
  // 本来はDBなんかにアクセスしてデータとってきたり・・・
  public string Greet() { return "こんにちは"; }
}

class Client
{
  public string Execute(string name)
  {
    var greeter = new Greeter();
    return string.Format("{0}さん,{1}", name, greeter.Greet());
  }
}

Clientのテストをしようとすると芋づる式にGreeterへの排除できない依存関係がついてくるという悲しい感じになっています。GreeterがDBアクセスしたりWebサービス呼び出ししてたら目もあてられない感じです。こういう感じのコードを書いて「単体テストってどうやるんですか?」って言われることもあり、ちょっとどう言っていいのか困ることもあります。

DIコンテナが使えないにしても、最低以下のような構造にしてほしいと思ったりします。

// きちんとインターフェースを使って依存関係の境界線を作る
interface IGreeter
{
  string Greet();
}
class Greeter : IGreeter
{
  string Greet() { return "こんにちは"; }
}

class Client
{
  // テスト用に差し替えするための口を作っておく
  private IGreeter greeter;
  public Client(IGreeter greeter)
  {
    this.greeter = greeter;
  }
  // 本番で使う時にめんどくさくないようにデフォルトのインスタンスは用意しておく
  public Client() : this(new Greeter()) {}

  public string Execute(string name)
  {
    return string.Format("{0}さん,{1}", name, this.greeter.Greet());
  }
}

こうしとけば、単体テストようにMockGreeterみたいなクラスを作って

var client = new Client(new MockGreeter());

みたいに単体テストの中でインスタンス化できるようになります。単体テストしにくい構造でものを作ってテストどうやるのか考えるよりも、単体テストのしやすさを考えて設計をするというのは重要なことだと思う今日この頃でした。