概要
自分が作ったクラスのインスタンスを使っているメソッドを, テスト可能にする.
問題のコード
class Motor
{
/// <summary>
/// 現在の速度を取得または設定する
/// </summary>
public int Speed
{
get
{
// このコードは, 実機にモーターが
// 繋がっていないと動作しない.
// (テスト環境では動作させることはできない)
}
set
{
// このコードも上と同様, テスト環境では動かせない
}
}
}
class SampleClass
{
/// <summary>
/// テスト対象のメソッド
/// </summary>
/// <param name="motor">処理したいモーター</param>
public void SampleMethod(Motor motor)
{
int currentSpeed = motor.Speed;
// currentSpeedを取得して何か処理する.
// 省略する.
}
このコードは, 特別なデバイス(実機)上で動作させるものとする.
実機にはモーターが接続されており, Motor
クラスを通じて制御をおこなう.
Motor
クラスは自分で定義したものであり, SampleMethod
メソッドはそのインスタンスを使って, 何か処理する.
このSampleMethod
メソッドのテストを作成し, CIツール(Travis CIとかAppVeyorとか)で自動テストさせたい.
でも, CIツールの環境では, モーターが接続されていないので, Speed
プロパティは動作しない.
テストの時は, Motor.Speed
は呼ばず, 代わりのメソッドを呼ぶようにしたい.
以降で, Motor
インスタンスをスタブに置き換えて, SampleMethod
メソッドをテストできるようにしてみる.
クラスではなくインタフェースに依存させる
問題のメソッドでは, モーターをMotor
インスタンスとして受け取り, Motor.Speed
プロパティを呼んでいる.
Motor
インスタンスとして受け取っているので, Motor
クラスのプロパティが呼ばれるのは当たり前である.
これを, スタブのメソッドが呼ばれるようにするためには,
クラスではなくインタフェースに依存させる
ということをする.
具体的には, Motor
クラスを, IMotor
インタフェースの実装クラスとする.
interface IMotor
{
int Speed { get; set; }
}
class Motor : IMotor
{
/// <summary>
/// 現在の速度を取得または設定する
/// </summary>
public int Speed
{
get
{
// このコードは, 実機にモーターが
// 繋がっていないと動作しない.
// (テスト環境では動作させることはできない)
}
set
{
// このコードも上と同様, テスト環境では動かせない
}
}
}
そして, テスト対象のメソッドは次のようにする.
/// <summary>
/// テスト対象のメソッド
/// </summary>
/// <param name="motor">処理したいモーター</param>
public void SampleMethod(IMotor motor) // Motorではなく, IMotorとして受け取る
{
int currentSpeed = motor.Speed;
// currentSpeedを取得して何か処理する.
// 省略する.
}
どこが変わったか分かりにくいかもしれないが, モーターをMotor
インスタンスではなく, IMotor
インタフェース(を持つインスタンス)として受け取っている.
そして, IMotor
インタフェースのSpeed
プロパティを呼んでいる.
こうすることで, IMotor
インタフェースを実装しているものなら何でも受け取ることができる.
実機で動かすときはMotor
インスタンスを受け取り, テストの時にはスタブインスタンスを受け取る, ということができるのである.
そしてSampleMethod
メソッド内では, 実際の実装がどのようになっているかを気にすることなく, IMotor.Speed
プロパティを呼び出せば, それぞれの実装に合わせた処理が実行される.
スタブに入れ替えてみる
まずはスタブを用意する.
もちろんIMotor
インタフェースを実装させる.
class MotorStub : IMotor
{
public int Speed
{
get
{
// ただ定数値を返す
return 0;
}
set
{
// 何もしない
}
}
}
テストコードは次のようになる.
[TestMethod]
public void SampleMethodTest()
{
// Arrange (準備)
SampleClass instance = new SampleClass();
IMotor inputMotor = new MotorStub();
// Act (実行)
instance.SampleMethod(inputMotor);
// Assert (検証)
// 何か処理が行われたことを確認する
// 今回は省略
}
これで, SampleMethod
メソッド内では, なにもせずただ0を返すだけのMotorStub.Speed
プロパティが呼ばれるだろう.
本記事の目的は達成された.
スタブをもう少し便利に
ここまでで, 当初の目的は達成することができた.
ここからは, スタブクラスをちょっと便利に改造する.
前節で作ったスタブのクラスは, いつも定数値0を返す.
しかし, テストによっては0を返してほしいテストとか, 1を返してほしいテストとか, いろいろある.
そのたびに新たなスタブクラスを作るのはめんどくさいので, 返す値を指定することができるようにしてみる.
class MotorStub : IMotor
{
public int SpeedValue;
public int Speed
{
get
{
// ただSpeedValueを返す
return SpeedValue;
}
set
{
// 何もしない
}
}
}
テストをするときは, 返す値を設定することになる.
[TestMethod]
public void SampleMethodTest()
{
// Arrange (準備)
SampleClass instance = new SampleClass();
IMotor inputMotor = new MotorStub();
((MotorStub)inputMotor).SpeedValue = 1; // Speedプロパティが返す値を設定
// Act (実行)
instance.SampleMethod(inputMotor);
// Assert (検証)
// 何か処理が行われたことを確認する
// 今回は省略
}
さらにテストによっては, Speed
プロパティに特定の例外を発生させたいとか, 1回目に呼ばれたときは0で2回目に呼ばれたときは1を返すようにしたいとか, 複雑なことをさせたいこともある.
本記事では説明しないが, またどこかで説明するかもしれない.
ちなみに, "どんな呼ばれ方をするか"とか, "こういう値を返したときの処理"とかを確認したい場合は, "スタブ"ではなく"モック"になる.