1
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.

ローカル変数のインスタンスをスタブに入れ替える

Last updated at Posted at 2016-10-11

概要

メソッドの内部で生成(new)したローカル変数を, スタブに入れ替える.

問題のコード

次のようなコードがあるとする.

/// <summary>
/// ポート (ポートAとポートBの2つがある)
/// </summary>
enum Port
{
	PortA,
	PortB
}

interface IMotor
{
	int Speed { get; set; }
}

class Motor : IMotor
{
	/// <summary>
	/// 現在の速度を取得または設定する
	/// </summary>
	public int Speed
	{
		get
		{
			// このコードは, 実機にモーターが
			// 繋がっていないと動作しない.
			// (テスト環境では動作させることはできない)
		}
		set
		{
			// このコードも上と同様, テスト環境では動かせない
		}
	}

	/// <summary>
	/// 指定したポートのモーターに接続し, Motorインスタンスを作成するコンストラクタ
	/// </summary>
	public Motor(Port port)
	{
		// モーターへの接続処理 (テスト環境では動作させることはできない)
		// 今回は省略
	}
}

class SampleClass
{
	/// <summary>
	/// テスト対象のメソッド
	/// </summary>
	public void SampleMethod()
	{
		IMotor motor = new Motor(Port.PortA);
		int currentSpeed = motor.Speed;

		// currentSpeedを取得して何か処理する.
		// 省略する.
	}
}

このコードは, 特別なハードウェア(以降, 実機と呼ぶ)上で実行するとする.
実機には, ポートAとポートBにモーターが接続されており, Motorコンストラクタの引数に指定してインスタンスを作成することにより, そのポートのモーターを制御できるようになる(とする).
しかし, モーターが接続されていないテスト環境では, Motorクラスのコンストラクタやメソッドは動作させることはできない.

このコードでは, SampleMethodメソッドの中で, Speedプロパティを呼んで, currentSpeedの値を取得している.
インスタンスをIMotorで持ち, IMotor.Speedを呼んでいるあたり, 前回の"自分が作ったクラスのインスタンスメソッドをスタブに置き換える"の"インタフェースに依存させる"ということを意識して作られている.
しかし前回の記事と違うのは, motorは引数で渡されるのではなく, メソッド内で作成しているところである.

メソッド内でnew Motor(Port.PortA)と書いてしまっては, いくらIMotorで受けようと, 中身は結局Motorインスタンスである.
以降で, これをスタブに入れ替える方法を説明する.

インスタンスの生成を行う部分をメソッド化

"入れ替える方法を説明する"といっても, やるのは"静的メソッドのスタブ置き換え"で説明した"メソッドを格納できる変数"を使用するだけだ.
"メソッドを格納できる変数"に, Motorインスタンスを作成する部分をメソッド化して格納しておく.
Motorインスタンスを作成する部分は, 特定のインスタンスに関わらずインスタンスを作成できるようにするため, Motorクラスの静的メソッドとして定義することにする.

class Motor : IMotor
{
	// ~ 中略 ~

	/// <summary>
	/// Motorインスタンスを作成するメソッドを格納する変数
	/// </summary>
	public static Func<Port, IMotor> CreateMotor = CreateMotorBody;	// 下に定義してあるメソッドを格納する

	/// <summary>
	/// Motorインスタンスを作成するメソッド
	/// </summary>
	private static IMotor CreateMotorBody(Port port)
	{
		return new Motor(port);
	}
}

class SampleClass
{
	/// <summary>
	/// テスト対象のメソッド
	/// </summary>
	public void SampleMethod()
	{
		// メソッドを格納する変数を通して, インスタンス作成メソッドを実行
		IMotor motor = Motor.CreateMotor(Port.PortA);
		int currentSpeed = motor.Speed;

		// currentSpeedを取得して何か処理する.
		// 省略する.
	}
}

スタブに入れ替えてみる

まず, スタブのクラスを定義する.
もちろん, IMotorを実現させる.
スタブのインスタンスを作成するメソッドも定義しておく.

class MotorStub : IMotor
{
	public int Speed
	{
		get
		{
			// ただ定数値を返す
			return 0;
		}
		set
		{
			// 何もしない
		}
	}

	/// <summary>
	/// スタブ用Motorインスタンスを作成するメソッド
	/// </summary>
	public static IMotor CreateMotorBody(Port port)
	{
		return new MotorStub();
	}
}

次に, SampleMethodメソッドをテストするコードを示す.

[TestClass]
public class SampleClassTest
{
	[TestMethod, Description("SampleMethodメソッドのテスト")]
	public void SampleMethodTest()
	{
		// Arrange (準備)
		SampleClass instance = new SampleClass();
		Motor.CreateMotor = MotorStub.CreateMotorBody;	// インスタンス生成メソッドを入れ替える

		// Act (実行)
		instance.SampleMethod();

		// Assert (検証)
		// 何か処理が行われたことを確認する
		// 今回は省略
	}
}

これで, SampleMethodメソッド内では, インスタンス生成時にMotorStub.CreateMotorBodyメソッドを使ってインスタンスを生成するようになる.

さて, "静的メソッドのスタブ置き換え"の"静的な変数の注意点"でも述べたように, 静的メソッドの値を入れ替えると, インスタンスが破棄されても入れ替わったままである.
テストメソッドが終わったときに静的メソッドの値が元に戻るようにするには, 次のようにするとよいだろう.

[TestClass]
public class SampleClassTest
{
	/// <summary>
	/// もともとのMotor.CreateMotorの値を退避させておくための変数
	/// </summary>
	private Func<Port, IMotor> originalMethod;

	[TestInitialize]    // ひとつのテストが始まる前に呼ばれるメソッド
	public void InitializeTest()
	{
		originalMethod = null;
	}

	[TestCleanup]   // ひとつのテストが終わるたびに呼ばれるメソッド
	public void CleanupTest()
	{
		if (originalMethod != null)
		{
			// 実行が終わったら, もとのメソッドに戻す
			Motor.CreateMotor = originalMethod;
		}
	}

	[TestMethod, Description("SampleMethodメソッドのテスト")]
	public void SampleMethodTest()
	{
		// Arrange (準備)
		SampleClass instance = new SampleClass();
		Motor.CreateMotor = MotorStub.CreateMotorBody;	// インスタンス生成メソッドを入れ替える

		// Act (実行)
		instance.SampleMethod();

		// Assert (検証)
		// 何か処理が行われたことを確認する
		// 今回は省略
	}
}

さらなる改善を求めて

例によって, 前述までのコードは次のような改善点が考えられる.

  • Motor.CreateMotorを, publicなフィールドでなく, Getterのみのプロパティにする.
  • インスタンス生成メソッドの定義を, ラムダ式を使って簡略化

そして例によって, 説明は省略する.
余裕があれば記事書くかも.

1
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
1
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?