一個簡單的自動化測試架構的實現(C#)


      翻看自己以前寫的代碼,突然間看到以前模擬的一個自動化測試架構的實現。幸好自己有寫學習筆記的習慣,整理一下,貼出來,以備忘。

特性會作為元數據嵌入到程序集,通過反射機制能夠得到這些元數據信息。程序員可以自己定義特性,對特性進行某種格式的定義,並利用特性來影響自己編寫代碼編譯后的程序集(assmbly)自定義特性在編譯時作為元數據被編譯到程序集中,運行時通過反射機制被讀取。這應該是這個自動化測試架構的立足點。
特性的應用:特性一般會在設計框架時很有用。
  1. 利用反射機制,作為特性的元數據可以反過來在運行時影響代碼的運行配置項,(例如:Windows Form程序中[STAThread]和[MTAThread用於表明應用程序的線程模型是單線程單元還是多線程--是否可以這樣歸屬有待商榷)或者為特殊的操作方法以特性作標記,以便在運行時做特殊處理(在P/Invoke中DLLImport特性標記方法轉化為托管方法)。
  2. 可以用於構建管理項目程序集工具:特性表現為某種特殊注釋,而注釋內容可以在編譯后從程序集(例如TestCaseClass.DLL)中讀取出來,從而可以通過特性內容的注釋和讀取實現對程序集中各種類型和方法進行管理(例如可以篩選特定特性的方法執行)。
該框架定義了[ClassInitiative][ClassCleanup][TestMethod]等特性以標記測試函數,而[TestMethod]中還可以定義一些特性參數[TestProperty]去將testmethod分類。在運行時要從待測程序集(dll)中讀取相應函數,並保證不同函數的運行順序。該框架有一系列函數來完成這項工作,這些函數負責運行待測程序集中特定特性標記所標記的函數。如InvokeInitiative()運行標記有[ClassInitiative]的函數;InvokeTestMethod()運行標記[TestMethod]的函數,並用這個InvokeXXX()函數調用先后順序保證這幾種特性函數運行順序。而這幾個InvokeXXX函數中利用反射機制去篩選相應的函數去運行。其中InvokeTestMethod()應該有參數,通過主函數的開關傳入,以篩選特定特性的方法。
 
自定義特性時要注意的
1. 自定義特性繼承自System.Attribute.
2. 特性參數分為環境參數和命名參數,如果特性類有自己的構造函數,則參數是必選的,為命名參數;剩下的參數為命名參數。在特性中應用命名參數必須加上參數名稱。可以重載構造函數以滿足不同組合。
部分定義特性的代碼:
namespace AttributesClass
{
    [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
    public sealed class ClassCleanupAttribute:Attribute
    {
        public ClassCleanupAttribute()
        { }
    }
}


namespace AttributesClass
{
    [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
    public sealed class ClassInitializeAttribute:Attribute
    {
        public ClassInitializeAttribute()
        { }
    }
}


namespace AttributesClass
{
    [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
    public sealed class DescriptionAttribute:Attribute
    {
        private string description;
        public string Description
        {
            get { return this.description; }
            set { this.description = value; }
        }
        public DescriptionAttribute(string text)
        {
            this.description = text;
        }
    }
}

namespace AttributesClass
{
    [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
    public sealed class PriorityAttribute:Attribute
    {
        private int priorityLevel;
        public int Level
        {
            get { return this.priorityLevel; }
            set { this.priorityLevel = value; }
        }
        public PriorityAttribute(int level)
        {
            this.priorityLevel = level;
        }
    }
}
namespace AttributesClass
{
    [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
    public sealed class TestPropertyAttribute:Attribute
    {
        #region Fileds
        private string propertyName = null;
        private string propertyValue = null;
        #endregion 

        #region Proerties
        public string Name
        {
            get { return this.propertyName; }
            set { this.propertyName = value; }
        }
        public string Value
        {
            get { return this.propertyValue; }
            set { this.propertyValue = value;}
        }
        #endregion

        #region Constructors
        public TestPropertyAttribute(string strPropertyName, string strPropertyValue)
        {
            this.propertyName = strPropertyName;
            this.propertyValue = strPropertyValue;
        }
        #endregion
    }
}

 

RunTest函數是重中之重。它完成功能如下:
查找TestProperty限定的方法,找到后查找所在類是否有ClassInitialize和ClassCleanup方法,如果有則先執行前者然后執行該方法最后執行后者。
 
public void RunTest(string name, string value)
        {
            List<string> selectedMethods = SearchMethodByTestProperty(name, value);
            foreach (string method in selectedMethods)
            {
                string[] strSplitMethod = method.Split('.');
                string nameSpaceName = strSplitMethod[0];
                string className = strSplitMethod[1];
                string methodName = strSplitMethod[2];
                Console.WriteLine(nameSpaceName + '.' + className);
                Console.WriteLine("-------------------------------------------------------");
                Type typeClass = assmeblyObject.GetType(nameSpaceName + '.' + className);
                MethodInfo testMethod = typeClass.GetMethod(methodName);
                object classObject = assmeblyObject.CreateInstance(nameSpaceName+'.'+className);
                
                MethodInfo[] methodArray = typeClass.GetMethods();
                foreach (MethodInfo objMethod in methodArray)
                {
                    object[] classInitializeAttributes = objMethod.GetCustomAttributes(typeof(AttributesClass.ClassInitializeAttribute), true);
                    if (classInitializeAttributes.Length == 1)
                    {
                        Console.WriteLine("You are invoking the classInitialize method...");
                         Console.WriteLine(objMethod.Name + @"() ");
                         objMethod.Invoke(classObject, null);
                        //InvokeIntialize(classObject, objMethod);
                    }
                }
                //InvokeTestMethod(classObject, method);
                Console.WriteLine("You are invoking the Select method...");
                Console.WriteLine(methodName + @"() ");
                testMethod.Invoke(classObject, null);

                foreach (MethodInfo objMethod in methodArray)
                {
                    object[] classCleanupAttributes = objMethod.GetCustomAttributes(typeof(AttributesClass.ClassCleanupAttribute), true);
                    if (classCleanupAttributes.Length == 1)
                    {
                        //InvokeCleanup(classObject, objMethod);
                        Console.WriteLine("You are invoking the classCleanup method...");
                        Console.WriteLine(objMethod.Name + @"() ");
                        objMethod.Invoke(classObject, null);
                    }
                }
                Console.WriteLine("");
            }                      
        }

 

模擬實現架構代碼筆記:

1. 特性定義應該為public Sealed 以避免繼承和便於命名空間外的類或函數調用(自定義特性通常情況下很少被繼承,並且定義好的特性大多數情況下還是命名空間外引用的)
2. 定義特性時應該使用[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]來指定該特性是用於標記方法,類還是成員變量特性的。
3. 別調用的測試類DemoTest由於會被主調用函數調用也應該聲明為public。
4. 約定測試程序集里所有方法種類一致便於管理.(倘若測試Assmbly里既有靜態方法又有非靜態方法的話,那么在主調用函數反射時必須考慮到不同種類的方法,invoke 時的不同,非靜態方法必須得先實例化才能invoke)
5. 該架構里所有方法都是非靜態的,主調用函數實現非靜態方法必須得先實例化一個類,顯然比較麻煩。 為什么不寫成非靜態的呢。(因為倘若用到某個類中的方法,必須默認得去調用Initialize和Cleanup函數[如果這些函數存在的話],如果用靜態方法的話就必須的自己去判定查找調用這些方法,顯然實現起來比較麻煩)

不足:
1 並沒有完成對多限定條件下的查詢方法
2 目前為止所有的測試方法必須不包含參數(包含參數的方法很容易實現,可以考慮在運行時XML傳遞參數)
3 PropertyDemoTest類結構不太清晰,可以考慮重構一下
4 可以增加更多的自定義特性完成更為復雜的操作

由於只是簡單模擬框架,實現功能較為簡單,更為復雜的功能可以以后再加,這個架構最大的好處是耦合度小,可擴展性強。

全部源碼可去csdn下載,連接:http://download.csdn.net/detail/fl815824/4340631

      很多優秀的架構其基本原理很相似,無非是在易用性和用戶習慣或者考慮到成本等問題上增加或者裁減了某些特性功能。例如VS單元測試功能,LEAF,Nuint。這個自動化測試架構應該和NUint框架是一致的。另外VS中已經集成了單元測試的控件。
基本實現原理跟這個差不多少參看鏈接: http://hi.baidu.com/widebright/blog/item/04759058143725db9c82041b.html
利用.net自定義特性實現元數據擴展,可以為我們設計架構時添加新的思路和解決方法,不過讀取元數據必將牽涉到反射機制而反射機制性能比較低。
有關於反射相關信息參考以前寫的筆跡和以下連接
http://msdn.microsoft.com/zh-cn/library/f7ykdhsy(v=VS.80).aspx
 
 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM