65.9K
CodeProject 正在变化。 阅读更多。
Home

为 Adhoc Blackbox 测试类方法提供用户界面

starIconstarIconstarIconstarIconstarIcon

5.00/5 (2投票s)

2012年10月9日

CPOL

12分钟阅读

viewsIcon

16227

downloadIcon

75

使用 TestUI 将一个简单的类库(或一组类库)发布为一个独立的 UI 应用程序,用于测试或管理目的。您可以自行通过自己的 TestUIRenderer 来增强用户体验。

 目录 

1. 引言
2. 背景
3. 第一个例子 - 保持简单
3.1 公开一个方法
3.2 创建控制台应用程序
3.3 启动应用程序
4. 扩展示例 - 使其更复杂
4.1 显式定义临时项源
4.2 优化 UI 表示
4.3 启动应用程序
5. 参数类型支持 
5.1 原始类型
5.2 复杂类型
5.3 输出参数
5.4 引用参数
6. 构建您自己的 TestUIRenderer
6.1 实现 TestUIRenderer
6.2 与 TestUIManager 交互
6.3 将渲染器公开给 TestUIManager
7. 结论

1. 引言 

在专业的开发生命周期中,测试方法始终是一个问题,有许多工具可以用来自动化方法测试。可能您不理解为什么能够在以后调用方法,例如在生产环境中。但方法在某些情况下(尤其是在涉及 Web 服务等外部资源时)可能会表现出不同的行为。

本文面向所有有兴趣拥有一个智能用户界面的人,该界面能够通过提供所有参数来调用程序集中的方法。该界面还将显示方法的输出,从而为您提供一种非常详细地监控应用程序功能的方法。

阅读本文后,您将知道如何准备一个类及其方法,使其成为临时调用解决方案的一部分。此外,您将能够使用内置的控制台应用程序来启动具体的测试场景。对于更具好奇心的读者,本文还提供了详细的介绍
如何将解决方案包装到您自己的 UI 中(例如,Web 界面或 Windows Forms)。

2. 背景 

当应用程序在生产或暂存环境中出现异常行为时,除了日志或前端用户体验,几乎没有其他方法可以帮助您猜测错误原因。有些人可能已经构建了一个小辅助工具来维护或监控应用程序。我也有,因为我想能够黑盒查看不同的功能。我的解决方案规划或结构不佳,每当我对其应用程序的另一部分产生新的兴趣时,我都必须对其进行扩展。于是我有了制作一个能够处理我所有兴趣的工具的想法。它应该考虑我定义的应用程序的每一个部分。由于应用程序通常由类和方法组成,因此这些是我的关注点。

3. 第一个例子 - 保持简单 

在深入研究代码之前,我将向您展示如何开始使用控制台 UI。它为准备好的示例程序集的成员提供了一个测试环境。

3.1 公开一个方法 

在您的 Visual Studio 项目中添加对程序集 Lerch.TestUI.Core 的引用,并使用 AdhocAccessible 属性将一个类标记为可供 TestUI 访问。拥有此属性的每个类都将被考虑。接下来,选择一个方法使其可以通过 TestUI 调用,并为其添加 AddhocInvokable 属性。这已经
公开了方法,但我们也想通过参数调用该方法。最后,为每个应由用户通过 TestUI 设置的参数添加一个属性。
[AdhocAccessible]
public class Calculator
{
    [AdhocInvokable]
    public double Add(
	[AdhocUsable(Caption = "Addend 1")] double dNum1,
	[AdhocUsable(Caption = "Addend 2")] double dNum2))
    {
	return dNum1 + dNum2;
    }
}
AdhocUsable 属性中的 Caption 属性为参数在 TestUI 中提供了一个更具描述性的外观。稍后您将了解许多其他属性。

3.2 创建控制台应用程序 

由于 TestUI 解决方案只是一个小型 API 而不是一个独立的应用程序,因此您必须自己创建它。在 Visual Studio 中创建一个新的控制台应用程序项目,添加对 Lerch.TestUI.Core 的程序集引用,以及包含已公开方法的程序集的引用(在我的例子中是 Lerch.Project.X)。添加一些代码。
static void Main(string[] args)
{
    // load assemblies considered in our test console
    var oAssembly = Assembly.Load(new AssemblyName("Lerch.Project.X"));
    // create the manager
    var oTestUIManager = new TestUIManager(oAssembly, typeof(TestUIRendererConsole)) { Name = "Project Test Console" };
    // start the engine
    oTestUIManager.Start();
}
 
您只需要做的是实例化 TestUIManager,为它提供要考虑的程序集和对 ITestUIRenderer 实现的类型引用。渲染器负责 UI 显示的所有工作。TestUIRendererConsole 已为您内置,但通过您自己的实现,您可以将测试环境渲染到网页或 Windows 窗体中。另一方面,TestUIManager 负责所有反射,调用方法并将结果馈送给渲染器。如果保持简单,就这样。

3.3 启动应用程序  

它看起来会像这样: 


包含所有具有 AdhocAccessible 属性的类的菜单。  


包含所选类中所有具有 AdhocInvokable 属性的方法的菜单。 


为所选方法中所有具有 AdhocUsable 属性的参数的输入选项。


输入参数后,调用方法。向用户显示结果。  

4. 扩展示例 - 使其更复杂

4.1 显式定义临时项源  

TestUIManager 也接受多个程序集来搜索 AdhocInvokable。此示例添加了第二个程序集 Lerch.Project.Y。另一个选项是将 AddhocInvokable 的考虑范围限制在特定命名空间。示例将这样做,并告诉 TestUIManager 在每个子命名空间中搜索(构造函数的第三个参数)。 
static void Main(string[] args)
{
    // load assemblies considered in our test console
    var lstAssembies = new List<Assembly>()
    {
        Assembly.Load(new AssemblyName("Lerch.Project.X")),
        Assembly.Load(new AssemblyName("Lerch.Project.Y"))
    };
 
    var lstNamespaces = new List<string>()
    {
        "Lerch.Project.X.Folder.Entities",
        "Lerch.Project.Y"
    };
 
    // create the manager
    var oTestUIManager = new TestUIManager(lstAssembies, lstNamespaces, true, typeof(TestUIRendererConsole)) { Name = "Project Test Console" };
    // start the engine
    oTestUIManager.Start();
} 

4.2 优化 UI 表示 

由于 TestUI 访问生产类和方法,用户通常难以理解方法签名并提供有效的参数输入。

属性的属性不仅可以为您准备更合适的控制台外观,还可以控制参数输入。

4.2.1 AdhocAccessible 属性是一个类属性,具有以下属性:

Caption:为类提供描述性名称。

Comment:为类提供简短注释。

Display:控制类的字符串表示形式。(选项:Caption、NameWithCaption、FullNameWithCaption、FullName、Name)
[AdhocAccessible(Caption="Calculate something.", Comment="Main operations", Display=TestUIManager.EntityDisplayOption.Caption)]
public class Calculator
{
	// ...
}
 

4.2.2 AdhocInvokable 属性是一个方法属性,具有以下属性:

Caption:为方法提供描述性名称。

Comment:为方法提供简短注释。

Display:控制方法的字符串表示形式。(选项:同上)
[AdhocInvokable(Caption = "Add two numbers.", Comment = "Or make it on your mind.", Display = TestUIManager.EntityDisplayOption.Caption)]
public double Add(...)
{
    // ...
}
 

4.2.3 AdhocUsable 属性是一个参数属性,具有以下属性:

Caption:为参数提供描述性名称。

Comment:为参数提供简短注释。

Display:控制参数的字符串表示形式。(选项:同上)

Disabled:控制参数是否从用户输入获取值。

DefaultValue:如果 Disabled 为 true,则此属性提供一个值。如果未设置,则值为默认类型值或 null。

MinInclusive:如果参数类型为数字,则设置最小值。

MaxInclusive:如果参数类型为数字,则设置最大值。
[AdhocInvokable]
public double Add(
    [AdhocUsable(Caption="Addend 1", Comment="Greater than 0", MinInclusive=1)] double dNum1,
    [AdhocUsable(Disabled=true, DefaultValue=1)] double dNum2)
{   // ...
}
重新启动应用程序并查看差异。通过合适的名称、有用的注释和默认值,用户界面可以发生变化,让用户忘记他们实际上是在处理类和方法。 

5. 参数类型支持

由于用户输入始终是 string 类型,因此参数类型存在一些限制。

5.1 原始类型

所有类型都受支持,即使它们是可空的。这些类型是 bool、byte、char、DateTime、decimal、double、Int16、Int32、Int64、int、sbyte、single、string、UInt16、UInt32、UInt64。TestUIManager 使用每种类型的 TryParse() 方法将用户输入转换为适当的类型。

5.2 复杂类型 

通常不支持。如果 TestUIManager 无法将用户输入转换为复杂类型,它会告知您。如果您的方法中有复杂类型参数,请不要将其标记为 AdhocUsableTestUIManager 将为该参数提供类型的默认值或 null。

但是,有一些选项可以使复杂类型可用

5.2.1 禁用并提供默认值

您已经了解了这一点。不幸的是,只能将静态值分配给该属性。

5.2.2 向类型添加静态 TryParse 实现

如果参数类型是自定义类型,您可以实现一个 TryParse 方法。考虑一个名为 Person 的类型,它有两个成员 NameGivenName。按如下方式设置 AddhocUsable 参数:

[AdhocUsable(Caption="Person 1", Comment="Format: Givenname, Name")] Person oPerson
[AdhocUsable(Disabled=true, DefaultValue="Kay,Lerch")] ref Person oPerson2

第一个期望逗号分隔值的用户输入。第二个参数获取一个默认值 - 也采用预期格式。现在实现逻辑,将 string 输入转换为 Person。其次,提供将 Person 转换为 string 的逻辑,因为第二个参数是引用,并且在方法调用后将输出到控制台。

public class Person
{
    // ...
    public static bool TryParse(string strIn, out Person oPerson)
    {
	oPerson = new Person();
	var arrStrings = strIn.Split(new char[] { ',' });
		
	if (arrStrings.Length > 1)
	{
	     oPerson.GivenName = arrStrings[0].Trim();
	     oPerson.Name = arrStrings[1].Trim();
	}
        return (arrStrings.Length > 1);
}
    public override string ToString()
    {
        return string.Format("{0} {1}", GivenName, Name);
    }
}

5.2.3 在渲染器中转换用户输入

这是您可以在自己的渲染器中执行的操作。因为您在渲染输入对话框时知道参数类型,所以可以根据不同类型响应不同的输入控件。对于字符串数组,您可能会在网页中渲染一个文本区域,并将每一行视为一个数组项。TestUIManager 期望对象值,因此无论您在渲染器中创建什么,TestUIManager 都将直接将其传递给方法。

5.3 输出参数 

支持输出参数。即使您将这些参数声明为 AdhocUsable,TestUI 上也不会有输入选项。相反,在方法调用后,您将在 TestUI 的结果视图中看到该值。无论如何,将输出参数标记为 AdhocUsable 没有问题,因为您可以通过 Caption 属性为其提供描述性名称。

5.4 引用参数 

支持引用参数。与输出参数一样,它们的值将出现在结果视图中。如果引用参数是 AdhocUsable,您可以为其提供一个值,因为 TestUI 会在方法调用前为用户提供输入选项。下图显示了一个具有 Person 类型输出参数和 string 类型引用参数的方法的结果视图。您还可以看到引用参数的输入选项。


6. 构建您自己的 TestUIRenderer

控制台应用程序并不美观。如果您想要自己的用户界面,此解决方案将为您提供所需的一切。有三个重要的事情您需要了解:

- 如何实现 ITestUIRenderer 接口
- 如何与 TestUIManager 交互
- 如何将您的 UI 公开给 TestUIManager 

架构如下所示:


 

6.1 实现 TestUIRenderer

您可以在 Lerch.TestUI.Core 库中找到该接口。您可以在任何您想要的地方实现您自己的 TestUI。考虑一个 Windows 窗体应用程序,您可以将实现放在窗体项目中。首先,您需要添加一个公共成员,它持有对 TestUIManager 的引用。这很简单,只需查看 TestUIRendererConsole 示例中的代码即可。
public class TestUIRendererConsole : ITestUIRenderer
{
    public TestUIManager TestUIManager
    {
        get;
        set;
    }
    //...
}

该接口有四个您必须实现的方法。以下方法应该渲染 AdhocAccessible 类的列表,并且还应该为用户提供输入选项。当您调用 Start() 时,TestUIManager 将调用此方法。调用附带的枚举包含代表 AdhocAccessible 类及其所有反射属性以及在 AdhocAccessible 属性中设置的那些属性的项。

public void RenderTestUIClassPicker(IEnumerable<AdhocAccessibleClassItem> lstClassItems)
{
    // render input dialogue 
    // ...
    // go on with loading the method picker
    TestUIManager.LoadMethodPicker(lstClassItems.FirstOrDefault());
}
  

下一个方法应该渲染所选类的 AdhocInvokable 方法列表,并且还应该为用户提供输入选项。当您调用 LoadMethodPicker() 时,TestUIManager 将调用此方法。调用附带的枚举包含代表 AdhocInvokable 方法及其所有反射属性以及在 AdhocInvokable 属性中设置的属性的项。此外,还会提供当前选定的 AdhocAccessible 类项。

public void RenderTestUIMethodPicker(AdhocAccessibleClassItem oClass, IEnumerable<AdhocInvokableMethodItem> lstMethodItems)
{
    // render input dialogue 
    // ...
    // go on with loading the method picker
    TestUIManager.LoadParameterValuePicker(lstMethodItems.FirstOrDefault());
} 

继续实现应该渲染所选方法的 AdhocUsable 参数列表的方法。它还应该为每个参数为用户提供输入选项。当您调用 LoadParameterValuePicker() 时,TestUIManager 将调用此方法。调用附带的枚举包含代表 AdhocUsable 参数及其所有反射属性以及在 AdhocUsable 属性中设置的属性的项。您必须考虑到,已禁用的 AdhocUsable 也在此枚举中。此外,还会提供当前选定的 AdhocInvokable 方法项。 

public void RenderTestUIParameterPicker(AdhocInvokableMethodItem oMethod, IEnumerable<AdhocUsableParameterItem> lstParameterItems)
{
    var dictParamterWithValues = new Dictionary<AdhocUsableParameterItem>();
    // render input dialogue 
    // ...
    foreach (var oTestParameter in lstParameterItems.Where(x => x.Disabled == false))
    {
	// save input values
        dictParamterWithValues.Add(oTestParameter, "some value");
    }
 
    try
    {
	// go on with preparing method invokation
        TestUIManager.Invoke(oMethod, dictParamterWithValues);
    }
    catch (TestUIInvokationException ex)
    {
        // react on invokation exceptions ...
    }
} 

在这里,您可以为复杂类型添加自定义转换。而不是 "some value",您可以为字典提供任何对象。

最后有一个方法用于渲染结果视图。当您调用 Invoke() 时,TestUIManager 将调用此方法。调用附带的枚举包含代表方法结果的项。调用的 AdhocInvokable 方法项也一并提供。 

Ipublic void RenderTestUIInvokationResult(AdhocInvokableMethodItem oMethod, IEnumerable<AdhocInvokationReturnItem> lstReturnItems, long lRuntime)
{
    foreach (var oReturn in lstReturnItems)
    {
        switch (oReturn.ItemType)
        {
            case AdhocInvokationReturnItem.ReturnItemType.Out: /* render output */ ; break;
            case AdhocInvokationReturnItem.ReturnItemType.Ref: /* render output */ ; break;
        }
	// render output
    }
}
 

6.2 与 TestUIManager 交互 

您可能已经看到,我们在渲染器中与 TestUIManager 进行了交互。这并非必需,如果您遵循 MVC 模式,也可以从控制器调用 TestUIManager。但无论如何,您都必须将用户输入提供给 TestUIManager,这通过方法调用完成。查看核心库中的方法 - 它们都有简短的描述。

由于 TestUIManager 也调用渲染器,因此存在循环依赖关系,并且有无限递归的风险。例如,TestUIManager 不允许渲染器在 RenderTestUIMethodPicker() 中调用 LoadMethodPicker()。然后 TestUIManager 会抛出异常,而不是让
无限递归发生。 

6.3 将渲染器公开给 TestUIManager

最后,渲染器需要公开给 TestUIManager。其他所有内容都在后台完成。最好的地方是在您的 UI 项目的某个启动代码中。如果您的渲染器名为 TestUIRendererForms,代码应该如下所示:
// load assemblies considered in our test console
var lstAssembies = new List<Assembly>()
{
    Assembly.Load(new AssemblyName("Lerch.Project.X"))
};
 
// create the manager
var oTestUIManager = new TestUIManager(lstAssembies, null, false, typeof(TestUIRendererForms)) { Name = "Project Test Console" };
// start the engine
oTestUIManager.Start(); 

7. 结论 

TestUI 核心库包含了将类和方法包装到智能用户界面所需的一切。它使管理员能够参数化调用您的程序功能并监控其行为。TestUI 还可以用于提供手动黑盒测试的环境,而无需编写测试方法。这对于临时测试非常有用。

在另一种场景下,现在可以使用 TestUI 将一个简单的类库(或其集合)发布为一个独立的 UI 应用程序。您可以自行通过自己的 TestUIRenderer 来增强用户体验。 

演示项目演示了 TestUI 在控制台应用程序中的使用。当您想提供自己的用户界面与 TestUIManager 一起使用时,可以参考其实现。如果您愿意,可以随意使用此解决方案,甚至对其进行增强,但请留下评论。

© . All rights reserved.