レガシーコード改善ガイド5

このメソッドをテストハーネスで動かすことができません

隠れたメソッド

privateなメソッドをテストしたい場合の対処方法。
・publicなメソッドにしてしまう。*1
・そのメソッドの責務を別クラスに分離する。
テストクラスからそのメソッドを呼び出せるようにリファクタリングする。
→ publicにすることもできない。依存性が複雑に絡まっているのでクラスの分離も納期的に難しい。 場合の最終的な方法である。

※テストが行い易いように設計する。テストが容易な設計は良い設計である。

//「テストクラスからそのメソッドを呼び出せるようにリファクタリングする。」 サンプル

//元クラス
public class CCAImage
{
  //テスト対象なメソッド
  private void setSnapRegion(int x, int y, int dx, int dy){
    ...
  }

  public void snap(){
    ...
  }

  ...
}

//テストクラス
public class TestingCCAImage : CCAImage
{
  //元クラスのテスト対象メソッドをprotectedに変更し、それをオーバーライド。
  protected override void setSnapRegion(int x, int y, int dx, int dy){
    vase.setSnapRegion(...);
  }

  ...
}

言語の「便利」機能

「sealedなクラスが絡んでいるレガシーコードをテスト可能にする」ケース

//本書より参照。
//HttpFileCollection(http://goo.gl/yVXB1), 
//HttpPostedFile(http://goo.gl/ZrQsw) は.NET標準クラス。sealedクラス且つpublicなコンストラクタが存在しない。
public class TestTarget
{
  public IList getKSRStreams(HttpFileCollection files){
    ArrayList list = new ArrayList();
    foreach(string name in files){
      HttpPostedFile file = files[name];
      if(file.FileName.EndsWith(".ksr") || (file.FileName.EndsWith(".txt") && file.ContentLength > MIN_LEN)) {
        ...
        list.Add(file.InputStream);
      }
    }
    return list;
  }
}

1,HttpFileCollectionについて調査
・クラス階層: NameObjectCollectionBase抽象クラスを継承している。
・filesの処理: foreachループに使用。
2,HttpPostedFileについて調査
・クラス階層: 自分がルートなsealedなクラス。
・fileの処理: FileName、ContentLength、InputStreamプロパティを呼び出して値取得を行う。

//テストが書けるように対応後

public class OurHttpFileCollection : NameObjectCollectionBase
{
  ...
}

public interface IHttpPostedFile
{
  string FileName();
  int ContentLength();
  Stream InputStream();
}

public class HttpPostedFileWrapper : IHttpPostedFile
{
  private HttpPostedFile source;
  public HttpPostedFileWrapper(HttpPostedFile source){ this.source = source; }
  public string FileName(){ get{return source.FileName;} }
  public int ContentLength(){ get{return source.ContentLength;} }
  public Stream InputStream(){ get{return source.InputStream;} }
}

public class FakeHttpPostedFile : IHttpPostedFile
{
  public FakeHttpPostedFile(string fileName, string contentLength, Stream inputStream){
    this.FileName = filename;
    this.InputStream= inputStream;
    this.contentLength = contentLength;
  }
  public string FileName(){ private set; get; }
  public int ContentLength(){ private set; get; }
  public Stream InputStream(){ private set; get; }
}

public class TestTarget
{
  public IList getKSRStreams(OurHttpFileCollection files){
    ArrayList list = new ArrayList();
    foreach(string name in files){
      IHttpPostedFile file = files[name];
      if(file.FileName.EndsWith(".ksr") || (file.FileName.EndsWith(".txt") && file.ContentLength > MIN_LEN)) {
        ...
        list.Add(file.InputStream);
      }
    }
    return list;
  }
}

*1:そのメソッドがクラスの状態に関わる場合は、publicにしてはいけない。