趣味プログラミングで単体テストをやるようにする

趣味でのプログラミングでは特にテスト等考えておらず、動作確認をしてパッと見動けばOKみたいな感じにしていました。適当にしすぎなので、趣味でもテストするようにしようと目論んでみます。


yossy/ASUnit - Spark project


JUnitとかは聞いたことがあったので、まずはJUnitの目的や使い方を調べ、それを基にAS単体テストフレームワークのASUnitを使ってみます。

  • ASUnit 1.01 for ActionScript3 (asunit-1.01.swc)

をダウンロード。
xUnitでのテストやTestFirstでの開発経験0です。以下を参考にします。

課題ソース

TestFirst...とか言われても、今パッと作りたい汎用的なクラスは思いつかないので、自分用ライブラリから既存クラスを持ってきてテストしてみます。TestFirst開発は次新規で作るときやってみよう。
課題ソース

package mylib.geom 
{
	import mylib.utils.MathUtil;
	
	/**
	 * スピード
	 */
	public class Speed 
	{
		private var _x:Number; // x軸方向
		private var _y:Number; // y軸方向
		
		/** x軸方向 */
		public function get x():Number { return _x; }
		/** x軸方向 */
		public function set x(value:Number):void { _x = value; }
		
		/** y軸方向 */
		public function get y():Number { return _y; }
		/** y軸方向 */
		public function set y(value:Number):void { _y = value; }
		
		/**
		 * コンストラクタ
		 * 
		 * @param x x軸方向
		 * @param y y軸方向
		 */
		public function Speed(x:Number = 0, y:Number = 0) 
		{
			_x = x;
			_y = y;
		}
		
		/**
		 * スピード、角度により、x軸、y軸スピードを計算して設定する
		 * 
		 * @param speed スピード
		 * @param angle 角度(0度が右水平方向。右回転で角度は増える。)
		 */
		public function setSpeedByAngle(speed:Number, angle:Number):void
		{
			// ラジアン
			var rad:Number = MathUtil.getRadians(angle);
			
			_x = speed * Math.cos(rad);
			_y = speed * Math.sin(rad);
		}
		
		/**
		 * x軸、y軸スピードにより、角度を取得する
		 * 
		 * @return 角度(0度が右水平方向)
		 */
		public function getAngleBySpeed():Number
		{
			return MathUtil.getDegrees(Math.atan2(_y, _x));
		}
	}
}

x、y軸それぞれの方向のスピードを設定して、そのベクトルの角度を求めたり、角度とスピードを設定してx、y軸方向のスピードを計算したりするクラスです。MathUtilクラスは「角度⇔ラジアン」の変換に使っています。無視。

失敗するテスト

package tests.mylib.geom 
{
	import mylib.geom.Speed;
	import org.libspark.asunit.framework.TestCase;
	
	/**
	 * Speedクラステスト
	 */
	public class SpeedTest extends TestCase
	{
		/**
		 * コンストラクタテスト
		 */
		public function testConstructor():void
		{
			var speed1:Speed = new Speed(11, 22);
			assertEquals(13, speed1.x, "speed1.x");
		}
	}
}

本当はSpeedクラスのコンストラクタの実装によってテストを失敗させればいいっぽいのですが、テストケースをいじって誤らせてみます。とりあえずテスト環境ができているかを見るということで...。
実行結果

.F

Time: 0.003
There was 1 failure:
0) testConstructor(tests.mylib.geom::SpeedTest)
Error: speed1.xテスト expected:<13> but was:<11>
at org.libspark.asunit.framework::Assert$/fail()[C:\DataFiles\Projects\Spark\asunit\as3\src\org\libspark\asunit\framework\Assert.as:51]
at org.libspark.asunit.framework::Assert$/failNotEquals()[C:\DataFiles\Projects\Spark\asunit\as3\src\org\libspark\asunit\framework\Assert.as:199]
at org.libspark.asunit.framework::Assert$/assertEquals()[C:\DataFiles\Projects\Spark\asunit\as3\src\org\libspark\asunit\framework\Assert.as:79]
at tests.mylib.geom::SpeedTest/testConstructor()[D:\MyLib\src\tests\mylib\geom\SpeedTest.as:17]
at org.libspark.asunit.framework::TestCase/runTest()[C:\DataFiles\Projects\Spark\asunit\as3\src\org\libspark\asunit\framework\TestCase.as:150]
at org.libspark.asunit.framework::TestCase/runBare()[C:\DataFiles\Projects\Spark\asunit\as3\src\org\libspark\asunit\framework\TestCase.as:134]
at TestProtect/protect()[C:\DataFiles\Projects\Spark\asunit\as3\src\org\libspark\asunit\framework\TestResult.as:224]
at org.libspark.asunit.framework::TestResult/runProtected()[C:\DataFiles\Projects\Spark\asunit\as3\src\org\libspark\asunit\framework\TestResult.as:156]
at org.libspark.asunit.framework::TestResult/run()[C:\DataFiles\Projects\Spark\asunit\as3\src\org\libspark\asunit\framework\TestResult.as:146]
at org.libspark.asunit.framework::TestCase/run()[C:\DataFiles\Projects\Spark\asunit\as3\src\org\libspark\asunit\framework\TestCase.as:124]
at org.libspark.asunit.framework::TestSuite/runTest()[C:\DataFiles\Projects\Spark\asunit\as3\src\org\libspark\asunit\framework\TestSuite.as:160]
at org.libspark.asunit.framework::TestSuite/run()[C:\DataFiles\Projects\Spark\asunit\as3\src\org\libspark\asunit\framework\TestSuite.as:154]
at org.libspark.asunit.framework::TestSuite/runTest()[C:\DataFiles\Projects\Spark\asunit\as3\src\org\libspark\asunit\framework\TestSuite.as:160]
at org.libspark.asunit.framework::TestSuite/run()[C:\DataFiles\Projects\Spark\asunit\as3\src\org\libspark\asunit\framework\TestSuite.as:154]
at org.libspark.asunit.textui::TestRunner/doRun()[C:\DataFiles\Projects\Spark\asunit\as3\src\org\libspark\asunit\textui\TestRunner.as:57]
at org.libspark.asunit.textui::TestRunner$/run()[C:\DataFiles\Projects\Spark\asunit\as3\src\org\libspark\asunit\textui\TestRunner.as:49]
at tests::MyLibTest()[D:\MyLib\src\tests\MyLibTest.as:16]

FAILURES!!!
Tests run: 1, Failures: 1, Errors: 0

失敗!思い通り。

成功するテスト

package tests.mylib.geom 
{
	import mylib.geom.Speed;
	import org.libspark.asunit.framework.TestCase;
	
	/**
	 * Speedクラステスト
	 */
	public class SpeedTest extends TestCase
	{
		/**
		 * コンストラクタテスト
		 */
		public function testConstructor():void
		{
			var speed1:Speed = new Speed(11, 22);
			assertEquals(11, speed1.x, "speed1.xテスト");
			assertEquals(22, speed1.y, "speed1.yテスト");
		}
	}
}

実行結果

.

Time: 0.002

OK (1test)

成功!思い通り。

こういう場合どうやるのか

package tests.mylib.geom 
{
	import mylib.geom.Speed;
	import org.libspark.asunit.framework.TestCase;
	
	/**
	 * Speedクラステスト
	 */
	public class SpeedTest extends TestCase
	{
		/**
		 * コンストラクタ
		 */
		public function SpeedTest()
		{
			super("Speedクラステスト");
		}
		
		/**
		 * コンストラクタテスト
		 */
		public function testConstructor():void
		{
			var speed1:Speed = new Speed(11, 22);
			assertEquals(11, speed1.x, "speed1.x初期値");
			assertEquals(22, speed1.y, "speed1.y初期値");
			
			var speed2:Speed = new Speed(23, 45);
			assertEquals(23, speed2.x, "speed2.x初期値");
			assertEquals(45, speed2.y, "speed2.y初期値");
			
			var speed3:Speed = new Speed();
			assertEquals(0, speed3.x, "speed3.x初期値");
			assertEquals(0, speed3.y, "speed3.y初期値");
		}
		
		/**
		 * セッターテスト
		 */
		public function testSetter():void
		{
			var speed:Speed = new Speed();
			
			speed.x = 66;
			assertEquals(66, speed.x, "speed.x設定");
			speed.x = 1121;
			assertEquals(1121, speed.x, "speed.x設定");
			
			speed.y = 41;
			assertEquals(41, speed.y, "speed.y設定");
			speed.y = 655;
			assertEquals(655, speed.y, "speed.y設定");
		}
		
		/**
		 * スピードを角度指定して設定するテスト
		 */
		public function testSetSpeedByAngle():void
		{
			var speed:Speed = new Speed();
			
			// スピード2、角度60度でスピード設定
			speed.setSpeedByAngle(2, 60);
			assertEquals(1, speed.x, "speed.x設定");
			assertEquals(Math.sqrt(3), speed.y, "speed.y設定");
		}
		
		/**
		 * スピードから角度取得テスト
		 */
		public function testGetAngleBySpeed():void
		{
			var speed:Speed = new Speed(1, Math.sqrt(3));
			assertEquals(60, speed.getAngleBySpeed(), "スピードから角度取得");
		}
	}
}

実行結果

..F.F.

Time: 0.004
There were 2 failures:
0) testSetSpeedByAngle(tests.mylib.geom::SpeedTest)
Error: speed.x設定 expected:<1> but was:<1.0000000000000002>
at org.libspark.asunit.framework::Assert$/fail()[C:\DataFiles\Projects\Spark\asunit\as3\src\org\libspark\asunit\framework\Assert.as:51]
at org.libspark.asunit.framework::Assert$/failNotEquals()[C:\DataFiles\Projects\Spark\asunit\as3\src\org\libspark\asunit\framework\Assert.as:199]
at org.libspark.asunit.framework::Assert$/assertEquals()[C:\DataFiles\Projects\Spark\asunit\as3\src\org\libspark\asunit\framework\Assert.as:79]
at tests.mylib.geom::SpeedTest/testSetSpeedByAngle()[D:\MyLib\src\tests\mylib\geom\SpeedTest.as:64]
at org.libspark.asunit.framework::TestCase/runTest()[C:\DataFiles\Projects\Spark\asunit\as3\src\org\libspark\asunit\framework\TestCase.as:150]
at org.libspark.asunit.framework::TestCase/runBare()[C:\DataFiles\Projects\Spark\asunit\as3\src\org\libspark\asunit\framework\TestCase.as:134]
at TestProtect/protect()[C:\DataFiles\Projects\Spark\asunit\as3\src\org\libspark\asunit\framework\TestResult.as:224]
at org.libspark.asunit.framework::TestResult/runProtected()[C:\DataFiles\Projects\Spark\asunit\as3\src\org\libspark\asunit\framework\TestResult.as:156]
at org.libspark.asunit.framework::TestResult/run()[C:\DataFiles\Projects\Spark\asunit\as3\src\org\libspark\asunit\framework\TestResult.as:146]
at org.libspark.asunit.framework::TestCase/run()[C:\DataFiles\Projects\Spark\asunit\as3\src\org\libspark\asunit\framework\TestCase.as:124]
at org.libspark.asunit.framework::TestSuite/runTest()[C:\DataFiles\Projects\Spark\asunit\as3\src\org\libspark\asunit\framework\TestSuite.as:160]
at org.libspark.asunit.framework::TestSuite/run()[C:\DataFiles\Projects\Spark\asunit\as3\src\org\libspark\asunit\framework\TestSuite.as:154]
at org.libspark.asunit.framework::TestSuite/runTest()[C:\DataFiles\Projects\Spark\asunit\as3\src\org\libspark\asunit\framework\TestSuite.as:160]
at org.libspark.asunit.framework::TestSuite/run()[C:\DataFiles\Projects\Spark\asunit\as3\src\org\libspark\asunit\framework\TestSuite.as:154]
at org.libspark.asunit.textui::TestRunner/doRun()[C:\DataFiles\Projects\Spark\asunit\as3\src\org\libspark\asunit\textui\TestRunner.as:57]
at org.libspark.asunit.textui::TestRunner$/run()[C:\DataFiles\Projects\Spark\asunit\as3\src\org\libspark\asunit\textui\TestRunner.as:49]
at tests::MyLibTest()[D:\MyLib\src\tests\MyLibTest.as:16]
1) testGetAngleBySpeed(tests.mylib.geom::SpeedTest)
Error: スピードから角度取得 expected:<60> but was:<59.99999999999999>
at org.libspark.asunit.framework::Assert$/fail()[C:\DataFiles\Projects\Spark\asunit\as3\src\org\libspark\asunit\framework\Assert.as:51]
at org.libspark.asunit.framework::Assert$/failNotEquals()[C:\DataFiles\Projects\Spark\asunit\as3\src\org\libspark\asunit\framework\Assert.as:199]
at org.libspark.asunit.framework::Assert$/assertEquals()[C:\DataFiles\Projects\Spark\asunit\as3\src\org\libspark\asunit\framework\Assert.as:79]
at tests.mylib.geom::SpeedTest/testGetAngleBySpeed()[D:\MyLib\src\tests\mylib\geom\SpeedTest.as:74]
at org.libspark.asunit.framework::TestCase/runTest()[C:\DataFiles\Projects\Spark\asunit\as3\src\org\libspark\asunit\framework\TestCase.as:150]
at org.libspark.asunit.framework::TestCase/runBare()[C:\DataFiles\Projects\Spark\asunit\as3\src\org\libspark\asunit\framework\TestCase.as:134]
at TestProtect/protect()[C:\DataFiles\Projects\Spark\asunit\as3\src\org\libspark\asunit\framework\TestResult.as:224]
at org.libspark.asunit.framework::TestResult/runProtected()[C:\DataFiles\Projects\Spark\asunit\as3\src\org\libspark\asunit\framework\TestResult.as:156]
at org.libspark.asunit.framework::TestResult/run()[C:\DataFiles\Projects\Spark\asunit\as3\src\org\libspark\asunit\framework\TestResult.as:146]
at org.libspark.asunit.framework::TestCase/run()[C:\DataFiles\Projects\Spark\asunit\as3\src\org\libspark\asunit\framework\TestCase.as:124]
at org.libspark.asunit.framework::TestSuite/runTest()[C:\DataFiles\Projects\Spark\asunit\as3\src\org\libspark\asunit\framework\TestSuite.as:160]
at org.libspark.asunit.framework::TestSuite/run()[C:\DataFiles\Projects\Spark\asunit\as3\src\org\libspark\asunit\framework\TestSuite.as:154]
at org.libspark.asunit.framework::TestSuite/runTest()[C:\DataFiles\Projects\Spark\asunit\as3\src\org\libspark\asunit\framework\TestSuite.as:160]
at org.libspark.asunit.framework::TestSuite/run()[C:\DataFiles\Projects\Spark\asunit\as3\src\org\libspark\asunit\framework\TestSuite.as:154]
at org.libspark.asunit.textui::TestRunner/doRun()[C:\DataFiles\Projects\Spark\asunit\as3\src\org\libspark\asunit\textui\TestRunner.as:57]
at org.libspark.asunit.textui::TestRunner$/run()[C:\DataFiles\Projects\Spark\asunit\as3\src\org\libspark\asunit\textui\TestRunner.as:49]
at tests::MyLibTest()[D:\MyLib\src\tests\MyLibTest.as:16]

FAILURES!!!
Tests run: 4, Failures: 2, Errors: 0

誤差でエラーがああぁぁ...。角度60の三角形の辺の長さの関係「1:2:ルート3」にのっとってやってみましたが、やっぱりコンピュータ。誤差が出てしまいました。

Error: speed.x設定 expected:<1> but was:<1.0000000000000002>
Error: スピードから角度取得 expected:<60> but was:<59.99999999999999>

そもそも仕様的に丸めた結果を返すようにすべきなんでしょうか?ある程度の誤差を含めたテストケースを設定すれば良いのでしょうか。
「誤差は仕様です。」
と顔色一つ変えずに言えば。

まとめ

結局全部テストできてないのですが...。
まずは経験を積んで、上げるべきケース、誤差ありで正確なリターンを得られない場合の対応を知らなければいけません。