有时候,在开发环境中测试核心业务职能,可能需要花很长而宝贵的时间。
幸运地是有很多简单而且自动化的测试平台。尽管如此,这个操作却不会总是能提供预期的结果。有时,它需要人为进行检查和评价。
此外,在一个大型项目中,编译几十个类,每一点变动是非常耗费时间和精力的,自动测试将不会达到预期的行为。OBP控制台测试框架是一个非常简单,轻量级的类库,它用于测试你的业务对象,但它没有一个像windows forms,web, silverlight 、WPF的图形界面。
OBP控制台测试库的主要目的是:在将代码整合到相关项目中之前,检查它的核心业务功能。
在测试类有下列行为:
在运行时,通过自定义属性和反射找出测试类和测试方法
提供测试人员提供一个简单菜单式的控制台的用户界面
允许重复使用菜单(控制台上的)
便利地显示的业务对象的集合,并能便利从中选择单独的业务对象进行测试。
文章中的一些定义
测试方法 :能通过执行一些动作来验证开发项目的行为、动作、过程。
测试类 :是包含一个或多个测试方法的类。它也可能还包含测试类成员来执行重叠测试。
菜单 :是在控制台上显示一系列的活动,允许测试人员使用键盘调用一个给定的测试方法。
结构:
整个类库是建立在一个简单的类上:CustomConsoleTest。这个类完成以下行动:
1、它使用反射浏览所有子类的方法并找出有“TestMethod”属性的方法。
2、使用“TestMethod”属性,它能正确显示在菜单上使用的方法的名称。
测试时,简化了业务对象集合的显示,以及简化在集合中选择单独的业务对象。
3、为了进行测试,你需要实例化一个CustomConsoleTest子类(这个类是抽象的)和调用方法“Execute”方法。
在这个简单的类中,神奇之处是一个测试类可以嵌入其他的测试类,执行父类中的测试方法能调用子类的Execute方法。因此,你能很快建立一个分层控制台菜单,用于在整合之前专注于核心业务功能的测试。
实现:
所有的测试类都要继承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"。代码如下:
{
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>。
{
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方法使在控制台模式下选择测试方法更加容易:
{
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类中
{
internal MethodItem(string aDisplayText, MethodInfo aMethod)
{
DisplayText = aDisplayText;
Method = aMethod;
}
internal string DisplayText { get; private set; }
internal MethodInfo Method { get; private set; }
}
示例:
将所有的功能展示在一个机器和零配件的示例中,应用程序包括两种类型的业务对象:Machine(机器)和SparePart(零配件)。
零配件类:
要实现我的测试操作,我需要三个类:MachineTest测试Machine,PartTest测试spare部分,SoftwareTest中嵌入这两个的测试。
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; }
}
{
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; }
}
/// 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();
}
}
{
[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方法为基础。
{
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);
}
}