3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

自分が作ったクラスのインスタンスメソッドをスタブに置き換える

Posted at

概要

自分が作ったクラスのインスタンスを使っているメソッドを, テスト可能にする.

問題のコード

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プロパティが呼ばれるだろう.
本記事の目的は達成された.

class.png

スタブをもう少し便利に

ここまでで, 当初の目的は達成することができた.
ここからは, スタブクラスをちょっと便利に改造する.

前節で作ったスタブのクラスは, いつも定数値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を返すようにしたいとか, 複雑なことをさせたいこともある.
本記事では説明しないが, またどこかで説明するかもしれない.
ちなみに, "どんな呼ばれ方をするか"とか, "こういう値を返したときの処理"とかを確認したい場合は, "スタブ"ではなく"モック"になる.

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?