UnitTest書いてみた
リフレクションの勉強をかねてUnitTestツールを書いてみた。
Formにはテスト一覧表示用のTreeViewとテスト結果出力用のTextBox、テスト実行ボタンButtonを配置する。
CompareToメソッドをGetMethod()で取得する際、目的のクラスとObjectクラスにCompareToメソッドが存在するので注意すること。
Formソース
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using System.Reflection; using System.Threading; namespace TestUnitSample { /// <summary> /// テストフォーム /// </summary> public partial class TestRunnerForm : Form, ITestRunner { //Todo:ノードのチェックボックス初期値をtrueにする。 //Todo:親にチェックがついたら、すべての子もチェックをつける。 //Todo:すべての子のチェックがはずれたら、親のチェックも外す。 public TestRunnerForm() { InitializeComponent(); loadAssembly(new Assembly[] { this.GetType().Assembly }); } //Runボタン押下 private void button1_Click(object sender, EventArgs e) { run(); } /// <summary> /// テスト対象アセンブリのロード /// </summary> /// <param name="assemblys"></param> public void loadAssembly(System.Reflection.Assembly[] assemblys) { //loop assemblys // if assemblys[i] == UnitTestBase実装クラス // then // Node addNode = new Node // addNode.Checked = false // addnode.Tag = assemblys[i].Type // loop assembly[0].GetType().GetMethods().Length // if メソッド名が"test_"で始まっている // then // Node childNode = new Node // childNode.Checked = false // childNode.Name = メソッド名 // childNode.Tag = MethodInfo // addNodeの子にchildNode追加 // end // end // TreeViewにaddNodeを追加 // end //end for (int i = 0; i < assemblys.Length; i++) { for (int j = 0; j < assemblys[i].GetTypes().Length; j++) { Type type = assemblys[i].GetTypes()[j]; if (type.BaseType != null && type.BaseType.Equals(typeof(UnitTestBase))) { TreeNode addNode = new TreeNode(); addNode.Checked = false; addNode.Name = type.Name; addNode.Text = type.Name; addNode.Tag = type; for (int k = 0; k < type.GetMethods().Length; k++) { MethodInfo mi = type.GetMethods()[k]; if (mi.Name.StartsWith("test_") == true) { TreeNode childNode = new TreeNode(); childNode.Checked = false; childNode.Name = mi.Name; childNode.Text = mi.Name; childNode.Tag = mi; addNode.Nodes.Add(childNode); } } treeView1.Nodes.Add(addNode); } } } } /// <summary> /// テストの実行 /// </summary> public void run() { //loop ノード数 // if node[i].Checked == true // then // obj = node[i].TagからType情報を取り出し、インスタンスを生成する。 // obj.initalize() // loop node[i].Nodes.Length // if node[i].Nodes[j].Checked == true // then // スレッド作成。testThreadが実行されるように設定し、実行 // end // end // obj.end() //end String nl = System.Environment.NewLine; textBox1.Text += "Test Start. ==========================" + nl; for (int i = 0; i < treeView1.Nodes.Count; i++) { if (treeView1.Nodes[i].Checked == true) { Type type = (Type)treeView1.Nodes[i].Tag; UnitTestBase test_obj = (UnitTestBase)type.Assembly.CreateInstance(type.FullName); test_obj.initalize(); for (int j = 0; j < treeView1.Nodes[i].Nodes.Count; j++) { if (treeView1.Nodes[i].Nodes[j].Checked == true) { MethodInfo mi = (MethodInfo)treeView1.Nodes[i].Nodes[j].Tag; Thread thread = new Thread(new ParameterizedThreadStart(testThread)); thread.Start(new object[] { test_obj, mi }); while (thread.IsAlive == true) Application.DoEvents(); } } } } } //テスト実行 private void testThread(object param) { //パラメタはobject[] 先頭要素にTestオブジェクト、次要素にMethodInfoが格納されている。 //Object obj = object[0] //MethodInfo mi = object[1] //try // obj.setup() // mi.invoke(obj, null) // obj.tearDown() //error // ログTextBoxに例外詳細内容を表示 object[] paramm = (object[])param; UnitTestBase test_obj = (UnitTestBase)paramm[0]; MethodInfo mi = (MethodInfo)paramm[1]; String nl = System.Environment.NewLine; try { textBox1.Invoke((MethodInvoker)delegate{textBox1.Text += "[" + DateTime.Now.ToString() + "] " + mi.Name + " setup" + nl;}); test_obj.setup(); textBox1.Invoke((MethodInvoker)delegate { textBox1.Text += "[" + DateTime.Now.ToString() + "] " + mi.Name + " start test" + nl; }); mi.Invoke(test_obj, null); textBox1.Invoke((MethodInvoker)delegate { textBox1.Text += "[" + DateTime.Now.ToString() + "] " + mi.Name + " end test" + nl; }); } catch (Exception e) { textBox1.Invoke((MethodInvoker)delegate { //textBox1.Text += "[" + DateTime.Now.ToString() + "] " + e.InnerException + nl + e.StackTrace + nl; textBox1.Text += "[" + DateTime.Now.ToString() + "] " + e.InnerException + nl; }); } finally { test_obj.tearDown(); } } } /// <summary> /// TestRunnerインターフェイス /// </summary> public interface ITestRunner { /// <summary> /// テスト対象アセンブリのロード /// </summary> /// <param name="assemblys"></param> void loadAssembly(Assembly[] assemblys); /// <summary> /// テストの実行 /// </summary> void run(); } }
UnitTestソース
using System; using System.Collections.Generic; using System.Text; using System.Reflection; namespace TestUnitSample { /// <summary> /// ユニットテスト基本クラス /// </summary> public class UnitTestBase { /// <summary> /// 初期化 /// </summary> public virtual void initalize() { } /// <summary> /// 終了処理 /// </summary> public virtual void end() { } /// <summary> /// テスト毎の初期化処理 /// </summary> public virtual void setup() { } /// <summary> /// テスト毎の終了処理 /// </summary> public virtual void tearDown() { } /// <summary> /// パラメタ値が等しいことを確認する /// </summary> /// <param name="val1">期待する値</param> /// <param name="val2">実際の値</param> /// <remarks>等しく無い場合、IFComparableインターフェイスを実装していない場合は例外が発生する。</remarks> public void assertEqual(object val1, object val2) { //if 双方の値が同一型 //then // Type type = val1.GetType() // if type.equals(val1, val2) == true // then // 正常終了 // else // 例外投げる。エラーメッセージに 期待する値、実際の値を含める。 // end //else // 例外投げる。エラーメッセージに型が一致しなかったことを含める。 if(val1.GetType().Equals(val2.GetType())) { Type type = val1.GetType(); if (type.GetInterface("System.IComparable") != null) { MethodInfo mi = type.GetMethod("CompareTo", new Type[] { typeof(object) }); object result = mi.Invoke(val1, new object[] { val2 }); if ((int)result != 0) throw new UnitTestAssertException("[assertEqual] expected value is " + val1 + " but " + val2 + "."); } else { //IComparable実装クラスではない。 throw new UnitTestAssertException("[assertEquals] parameter object not implement IComparable interface."); } } } /// <summary> /// パラメタ値がNullであることを確認する /// </summary> /// <param name="val1"></param> /// <remarks>Nullで無い場合は例外が発生する。</remarks> public void assertNull(object val1) { if (val1 != null) { throw new UnitTestAssertException("[assertNull] parameter is not null."); } } } /// <summary> /// ユニットテスト実行エラー /// </summary> public class UnitTestAssertException : ApplicationException { /// <summary> /// コンストラクタ /// </summary> /// <param name="message"></param> public UnitTestAssertException(String message) : base(message) { } } /// <summary> /// テスト実装サンプルクラス /// </summary> public class SampleTest : UnitTestBase { private int val = -1; /// <summary> /// 初期化 /// </summary> public override void initalize() { val = 0; } /// <summary> /// テスト毎の初期化処理 /// </summary> public override void setup() { val = 0; } /// <summary> /// 足し算テスト /// </summary> public void test_addTest() { for (int i = 1; i <= 10; i++) val += 0; assertEqual(100, val); } /// <summary> /// 引き算テスト /// </summary> public void test_subTest() { val = 10; for (int i = 1; i <= 10; i++) val -= 1; assertEqual(0, val); } } }