有時候,在開發環境中測試核心業務職能,可能需要花很長而寶貴的時間。
幸運地是有很多簡單而且自動化的測試平台。盡管如此,這個操作卻不會總是能提供預期的結果。有時,它需要人為進行檢查和評價。
此外,在一個大型項目中,編譯幾十個類,每一點變動是非常耗費時間和精力的,自動測試將不會達到預期的行為。OBP控制台測試框架是一個非常簡單,輕量級的類庫,它用於測試你的業務對象,但它沒有一個像windows forms,web, silverlight 、WPF的圖形界面。
OBP控制台測試庫的主要目的是:在將代碼整合到相關項目中之前,檢查它的核心業務功能。
在測試類有下列行為:
在運行時,通過自定義屬性和反射找出測試類和測試方法
提供測試人員提供一個簡單菜單式的控制台的用戶界面
允許重復使用菜單(控制台上的)
便利地顯示的業務對象的集合,並能便利從中選擇單獨的業務對象進行測試。
文章中的一些定義
測試方法 :能通過執行一些動作來驗證開發項目的行為、動作、過程。
測試類 :是包含一個或多個測試方法的類。它也可能還包含測試類成員來執行重疊測試。
菜單 :是在控制台上顯示一系列的活動,允許測試人員使用鍵盤調用一個給定的測試方法。
結構:
整個類庫是建立在一個簡單的類上:CustomConsoleTest。這個類完成以下行動:
1、它使用反射浏覽所有子類的方法並找出有“TestMethod”屬性的方法。
2、使用“TestMethod”屬性,它能正確顯示在菜單上使用的方法的名稱。
測試時,簡化了業務對象集合的顯示,以及簡化在集合中選擇單獨的業務對象。
3、為了進行測試,你需要實例化一個CustomConsoleTest子類(這個類是抽象的)和調用方法“Execute”方法。
在這個簡單的類中,神奇之處是一個測試類可以嵌入其他的測試類,執行父類中的測試方法能調用子類的Execute方法。因此,你能很快建立一個分層控制台菜單,用於在整合之前專注於核心業務功能的測試。
實現:
所有的測試類都要繼承CustomConsoleTest。下面是構造函數:
CustomConsoleTest
public CustomConsoleTest()
{
var t = this.GetType();
int i = 0;
string name = null;
// find the methods
foreach (var m in t.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance))
{
if (IsValidMethod(m, ref name))
{
var item = new MethodItem(name, m);
_methods.Add(_menuKeys[i], item);
++i;
}
}
}
當CustomConsoleTest子類被實例化,執行以下操作:
1、使用反射去查找有自定義屬性TestMethod的方法。
2、當一個方法有TestMethod屬性,它被添加到一個類名為MethodItem的集合,這個集合包含MethodInfo實例和方法的名稱。
一旦測試方法發現,它將顯示在菜單上,允許測試人員通過鍵盤上的鍵調用的測試方法。
為了完成這些,測試人員必須調用方法“Execute”的方法,這個方法浏覽方法集合和在菜單上顯示測試方法友好名稱(如[TestMethod("List Machines")]中就顯示List Machines)。
為了重復的測試,因此能實現重復菜單,測試類可以嵌入其他測試類,調用測試方法也將調用嵌入類的"Execute"。代碼如下:
Execute
public void Execute()
{
char c = '*';
while ((c != 'q') && (c != 'Q'))
{
foreach (var p in _methods)
Console.WriteLine("{0} - {1}", p.Key, p.Value.DisplayText);
Console.WriteLine("q - Quit");
Console.WriteLine();
c = Console.ReadKey(true).KeyChar;
if (_methods.ContainsKey(c))
try
{
var m = _methods[c];
m.Method.Invoke(this, null);
}
catch (Exception ex)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("An exception has occured when invoking the method {0}",
ex);
}
}
}
為了方便地顯示和選擇集合中的項,我添加一個泛型函數來簡化集合處理,這假設集合實現了通用接口IEnumerable<T>。
DisplayCollection
protected void DisplayCollection<T>(IEnumerable<T> aCollection)
{
if (aCollection.Count() == 0)
Console.WriteLine("No item in the collection");
int i = 1;
_elts.Clear();
foreach (var e in aCollection)
{
Console.WriteLine("{0} - {1}", i, GetDescription(e));
_elts.Add(i, e);
++i;
}
Console.WriteLine();
}
SelectItem方法使在控制台模式下選擇測試方法更加容易:
代碼
protected T SelectItem<T>(IEnumerable<T> aCollection) where T : class
{
DisplayCollection<T>(aCollection);
while (true)
{
Console.WriteLine("Type the element number or q to exit");
var text = Console.ReadLine();
if ((text == "q") || (text == "Q"))
return null;
try
{
int index = Convert.ToInt32(text);
if (_elts.ContainsKey(index))
return (T)_elts[index];
else
Console.WriteLine("The list does not contain that number, try again !");
}
catch
{
Console.WriteLine("Wrong value");
}
}
}
為了顯示菜單,我將尋找到的測試方法放在MethodItem類中
代碼
class MethodItem
{
internal MethodItem(string aDisplayText, MethodInfo aMethod)
{
DisplayText = aDisplayText;
Method = aMethod;
}
internal string DisplayText { get; private set; }
internal MethodInfo Method { get; private set; }
}
示例:
將所有的功能展示在一個機器和零配件的示例中,應用程序包括兩種類型的業務對象:Machine(機器)和SparePart(零配件)。
代碼
class Machine : Asset
{
//機器類中有添加零配件AddPart方法。CreateInstances靜態的構建方法,零配件列表:List<SparePart>
internal void AddPart(SparePart aPart)
{
Parts.Add(aPart);
AddWeight(aPart.Weight);
}
public Machine(string aName)
: base(aName)
{
Parts = new List<SparePart>();
}
public static void CreateInstances()
{
Machines = new List<Machine>();
for (int i = 0; i < new Random().Next(10) + 1; i++)
Machines.Add(new Machine(string.Format("Machine{0}", i)));
}
public static List<Machine> Machines
{
get;
private set;
}
public List<SparePart> Parts { get; private set; }
}
零配件類:
代碼
class SparePart : Asset
{
public SparePart(Machine aMachine, string aName, double aWeight):
base(aName)
{
Machine = aMachine;
Weight = aWeight;
aMachine.AddPart(this);
}
public static void CreateInstances()
{
Parts = new List<SparePart>();
var random = new Random();
foreach(var m in Machine.Machines)
for (int i = 0; i < random.Next(5); i++)
{
var part = new SparePart(m, string.Format
("{0}-part{1}", m.Name, i),
random.NextDouble());
Parts.Add(part);
}
}
public Machine Machine { get; private set; }
public static List<SparePart> Parts
{ get; private set; }
}
要實現我的測試操作,我需要三個類:MachineTest測試Machine,PartTest測試spare部分,SoftwareTest中嵌入這兩個的測試。
代碼
/// <summary>
/// this class is used to test software
/// </summary>
class SoftwareTest : CustomConsoleTest
{
/// <summary>
/// machine test
/// </summary>
private MachineTest _machineTest = new MachineTest();
/// <summary>
/// part test
/// </summary>
private PartTest _partTest = new PartTest();
[TestMethod("Machines")]
private void ExecuteMachineTest()
{
_machineTest.Execute();
}
[TestMethod("Parts")]
private void ExecutePartTest()
{
_partTest.Execute();
}
static SoftwareTest()
{
Machine.CreateInstances();
SparePart.CreateInstances();
}
}
代碼
class MachineTest : CustomConsoleTest
{
[TestMethod("List Machines")]
private void ListMachines()
{
DisplayCollection<Machine>(Machine.Machines);
}
protected override string GetDescription(object e)
{
if(e is Machine)
{
var m = e as Machine;
return string.Format("Machine {0} | {1:0.00} | Parts : {2}",
m.Name, m.Weight, m.Parts.Count);
}
return base.GetDescription(e);
}
}
為了更精細顯示的業務對象,我重寫的方法GetDescription,它本身是以ToString方法為基礎。
代碼
class PartTest : CustomConsoleTest
{
protected override string GetDescription(object e)
{
if (e is SparePart)
{
var p = e as SparePart;
return string.Format("Machine :
{0} - {1}", p.Machine.Name, p.Name);
}
return base.GetDescription(e);
}
/// <summary>
/// selection
/// </summary>
[TestMethod("Select A Part")]
protected void TestSelect()
{
var p = SelectItem<SparePart>(SparePart.Parts);
if (p != null)
Console.WriteLine("You have selected the part {0}", p.Name);
}
}
總結:
以我個人開發大型軟件系統的經驗,自動化測試是很好,但還往往不夠,在非常特別的情況,要耗很多時間去測試。我的做法很重要一點是沒有軟件界面部分,在控制台項目中進行測試非常的快。
出處:http://zhuqil.cnblogs.com