为 Adhoc Blackbox 测试类方法提供用户界面
使用 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
无法将用户输入转换为复杂类型,它会告知您。如果您的方法中有复杂类型参数,请不要将其标记为 AdhocUsable
,TestUIManager
将为该参数提供类型的默认值或 null。但是,有一些选项可以使复杂类型可用
5.2.1 禁用并提供默认值
您已经了解了这一点。不幸的是,只能将静态值分配给该属性。
5.2.2 向类型添加静态 TryParse 实现
如果参数类型是自定义类型,您可以实现一个 TryParse
方法。考虑一个名为 Person
的类型,它有两个成员 Name
和 GivenName
。按如下方式设置 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
一起使用时,可以参考其实现。如果您愿意,可以随意使用此解决方案,甚至对其进行增强,但请留下评论。