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);
        }
    }
}