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

レガシーコードの定義

"テストの無いコード"すべてをレガシーコードと呼ぶ。

フィードバックを得ながらの作業

システム変更の方法は大きく二種類に分けることができる。
1,編集して祈る → レガシーコード
2,保護して編集する → テストで保護されたコード

単体テストとは

1,単体テストは早く走る必要がある。そうでないと繰り返しテストができない。
(1テストに0.1秒は時間がかかりすぎ。1クラス10件のテストとして、それが3000クラスあると、50分かかる。。。)

2,振る舞いが正しいことを素早く確認するのが単体テストの目的なので、
下記(引用)処理がテストコードに含まれていると目的を達成できない恐れあり。

>他の種類のテストが単体テストの仮面を被っていることもよくある。次に当てはまるものは単体テストではない。
>1,データベースとやり取りする
>2,ネットワークを介した通信をする
>3,ファイルシステムにアクセスする
>4,実行するために特別な環境設定を必要とする(環境設定ファイルの編集など)

※これらのテストが不要なことはなく、むしろ必要である。しかしコード変更毎に走らせるテストコードに含めるのではなく
別管理すべきである。

レガシーコードをテストコード作成できるようにするには?

リファクタリングを行い、データベースやファイルシステムなどへの依存性を取り除く。
テストできるようにするにあたり、不本意な構成に変更する必要性に迫られる場合がある。しかしそれは仕方が無いこととして受け入れるしかない。(今までよりは良くなっているだろう。)

レガシーコードの変更手順

1,変更点を洗い出す。
→ 仕変・機能追加による、振る舞いの変更を実現するために必要な対応と対象箇所一覧を調査する。
2,テストを書く場所を見つける。
3,依存関係を排除する。
→ テストすべき対象(メソッド)を決定する。テストクラスで対象クラスインスタンスを生成できるようにリファクタリングする。
4,テストを書く。
→ 振る舞いを満たしていることを確認するテストケースを生成し、パスすることを確認する。

検出と分離

依存関係の排除に役立つ手法の紹介

擬装オブジェクト(FakeObject)

Strategyパターンを適用して組み込む、テスト用のクラス。

モックオブジェクト(MockObject)

振る舞いを外から設定することができるテスト用のユーティリティクラス。
大抵の言語には対応するモックフレームワークが存在するはず。

接合モデル

接合部:その場所を直接変更しなくても、プログラムの振るまいを変えることの出来る場所である。
種類としては、プリプロセッサ接合部、オブジェクト接合部がある。
許容点:接合部でどの振る舞いを行うか決定するところ。

下記のソースを例に上げると、Hoge.InitializeメソッドでFooLib.CallFooFunction呼び出しだけを行うことをやめたい。

  public class Hoge
  {
    private FooLib foo;  //外部ライブラリ

    public Hoge()
    {
    }

    public void Initialize()
    {
      ....
      foo.CallFooFunction();  //外部ライブラリよびだし。DBアクセスなどの依存関係を含むため、このままではテストコードが作成できない。
      ...
    }
  }

この場合の対処例としては以下のようになる。

  public Interface IFoo
  {
    void CallFooFunction();
  }

  public NothingFoo : IFoo
  {
    public void CallFooFunction(){;}
  }

  public WrappedFoo : IFoo
  {
    private FooLib foo;

    public WrappedFoo(FooLib foo)
    {
      this.foo = foo;
    }

    public void CallFooFunction()
    {
      foo.CallFooFunction();
    }
  }

  public class Hoge
  {
    private IFoo foo = new NothingFoo();

    public Hoge()
    {
    }

    public Hoge(IFoo foo)
    {
      this.foo = foo;
    }

    public void Initialize()
    {
      ....
      foo.CallFooFunction();
      ...
    }
  }

この場合の接合部は、foo.CallFooFunction()となる。(オブジェクト接合部)
外部から処理を取り替えれるように対応することで依存性を取り除いた。