Javaテスト設計

コードをテストする際に直面する依存関係
特にグローバル変数やシングルトンなどに依存する場合、
テストする際に苦労してしまいます。

特定クラスの挙動が、それらの見えにくい状態によって挙動が変化してしまう可能性があるからです。

Javaではstatic変数がそれに当たるでしょう、
実行時に変更されない定数定義であれば問題ありませんが、
一般的には、グローバル変数は限られた状況下以外での使用は推奨されません。

シングルトンもどこからでもアクセスしやすくする為の、
グルーバル変数のようなものと云えます。

単体テストとは

  • 単一の関数をテストするもの
  • 単一のクラスをテストするもの

テストは多数になるので、並列で実行される事になります。

テスト対象の関数・クラスはもちろんテスト可能な設計でなければならない。

テスト可能な設計

依存性の排除

テスト対象が、
他のクラス実装に密接に依存していると、
テストの際にそれらのクラス実装がなければ動作しない状況になってしまいます。

簡単に生成できるクラスであれば生成してしまえば良いですが、
簡単ではない場合は、困ってしまいます。

例えば、その依存クラスも更に他クラスの実装に依存している場合や、
DB接続など実際にクエリを発行して投げちゃうようなものはテスト時に使用することはできないでしょう。

偽装クラスを渡せるように外部パラメータ化するか、
インターフェイスを抽出してクラス実装ではなく、インターフェイスに依存するようにすべきです。

べき等性 (再現可能であること)

テスト対象の関数・クラスは、
何度実行しても再現可能である必要があります。

テストするたびに実行結果が違うとテストにならないですし、
テストされる順番によって結果が変わってしまうでもテストになりません。

グローバル変数や、シングルトンの状態によって挙動が変わり再現性がなくるようなものではダメという事です。

シングルトン設計は必要か?

後述する方法を使う事で、グローバル変数、シングルトンへの依存性を排除することはできますが、
そもそも、シングルインスタンスという設計が本当に必要なのかは見直しておいた方が良さそうです。

下記のような正当な理由がある場合シングルトンは有効と云えますが、
ただグローバル変数のように扱いたいだけの理由でシングルトン設計を利用する意味はないでしょう。

実装のたびにグローバルインスタンスを考慮する必要性がでてきてしまいます。

シングルインスタンスを使う主な理由

  • 現実に則した設計で、実際に一つしかないものを表現する場合
  • 2つ以上存在することでハードウェア制御などで物理的に不都合が生じる場合
  • 生成に多くのリソースが必要な場合

依存性の排除方法

依存性の排除の仕方は、
様々な方法があるので場合によって使い分けて利用します。

コンストラクタのパラメータ化

テスト対象のクラス内で利用する依存クラスのインスタンスを
コンストラクタから取得してメンバー変数に保持しておき利用する方法です。

テスト対象のクラスは依存クラスの生成処理から解放されることになりますし、
テスト時には外部から偽装クラスのインスタンスを渡す事も可能になります。

メソッドのパラメータ化

依存クラスのインスタンスをメソッド実行時に外部から受け取るスタイルです。

コンストラクタのパラメータ化と同じように、
テスト対象クラスは依存クラスの生成処理から解放され、
テスト時には偽装クラスに差し替えることができます。

呼び出しの抽出とオーバライド

依存クラスを利用している部分だけ、テスト対象クラスのメソッドとして抽出してしまう方法です。

一見、無駄に見えますが、
テスト対象クラスを継承することで、依存クラスの利用メソッドをオーバーライドすることが可能になります。

▼修正前

class Person {
  private String _name;
  ...

  public String say() {
    // シングルトンのメソッドを直接使っている
    return String.format("%s, I'm %s",
      Script.getInstance().helloMessage(),
      _name);
  }
}

▼修正後

class Person {
  private String _name;
  ...

  public String say() {
    // ラップしたメソッドを利用するようにする
    return String.format("%s, I'm %s",
      helloMessage(),
      _name);
  }

  // シングルトンへのアクセスをメソッドでラップする
  protected String helloMessage(){
    return Script.getInstance().helloMessage();
  }
}

Personクラスを継承して、helloMessageメソッドをオーバーライドすれば、シングルトンのScriptクラスに依存せずにテストすることが可能になります。

グローバル参照の置き換え

前述の呼び出しの抽出とオーバライドで、大抵は事足りますが、
依存クラスのメソッドコール箇所が多い場合はその分メソッド実装を用意する事になるため、依存クラスのインスタンス取得メソッドを用意するほうが良いでしょう。

テスト対象クラスからは、
そのメソッドで取得した依存クラスを利用します。

▼修正前

class Person {
  private String _name;
  ...
  public String sayHello() {
    // Scriptインスタンスを直接取得している
    return String.format("%s, I'm %s",
      Script.getInstance().helloMessage(),
      _name);
  }

  public String sayGoodbye() {
    // Scriptインスタンスを直接取得している
    return Script.getInstance().helloMessage();
  }
}

▼修正後

class Person {
  private String _name;
  ...
  // Scriptインスタンスの取得メソッドを用意する
  protected Script getScript() {
    return Script.getInstance();
  }

  public String sayHello() {
    // 取得メソッドを使って利用する
    return String.format("%s, I'm %s",
      getScript().helloMessage(),
      _name);
  }

  public String sayGoodbye() {
    // 取得メソッドを使って利用する
    return getScript().goodbyeMessage();
  }
}

getScriptメソッドをオーバーライドすることで、
Scriptインスタンスを偽装クラスに差し替える事が可能になります。
※Scriptクラスを継承してオーバーライドする事になるので、コンストラクタをprotected以上に変更する必要はあります。

まとめ

グローバル変数、シングルトンに依存していたとしても、
以下の方法で依存性の排除を行うことができる。

  • コンストラクタのパラメータ化
  • メソッドのパラメータ化
  • 呼び出しの抽出とオーバライド
  • グローバル参照の置き換え

正当な理由がない場合は、
シングルトン設計自体を見直すのもあり。

コメントを残す