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

可插入式示例数据生成器框架和 GUI

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.56/5 (4投票s)

2006 年 11 月 7 日

CPOL

8分钟阅读

viewsIcon

41143

downloadIcon

606

一个用于编写示例数据生成器的框架和一个允许有效使用这些生成器的 GUI。

Pluggable Sample Data Generator GUI

引言

我想编写一个框架和 GUI,以便能够快速编写和使用各种示例数据生成器。我不断地编写小型控制台应用程序来创建随机但真实世界的示例数据,用于在测试期间预填充数据库应用程序,或用于编写单元测试。

本项目旨在实现以下目标

  1. 定义接口规范,以便所有示例数据生成器都可以以类似的方式使用
  2. 允许通过编程方式调用新的数据生成器,例如在我的单元测试中
  3. 允许将数据生成器插入 GUI 应用程序,我可以在其中设置属性并格式化输出结果

IDataGenerator<T> 接口

所有符合此框架的示例数据生成器都实现了通用 interface IDataGenerator<T>

public interface IDataGenerator<T>
{
    void Setup();
    void TearDown();
    T Next();
    List<T> Next(int count);
    string ToString(T value);
    string[] ToStringArray(List<T> values);
}

每种方法在此处进行描述

安装

此方法应设置好数据生成器,以便随时调用。例如,在姓名生成器中,Setup 从资源加载所有名字和姓氏;每次调用 next 时执行此操作将带来巨大的性能损失且不必要。此方法在 GUI 框架调用任何 Next 调用之前调用,或者如果从代码中调用,则在调用 NextNext(count) 之前应调用 Setup

Teardown

此方法应清理 Setup 中可能已初始化的数据生成器可能占用的内存和资源。此方法在 GUI 框架调用任何 Next 调用之后调用,或者如果从代码中调用,则在不再需要数据生成器时应调用 TearDown

下一篇

这是生成单个示例数据实例的主要方法。return 类型是 <T> 类型,在编译时在数据生成器类中声明。

Next(count)

此方法在 for 循环中调用 Next() count 次,并将结果添加到强类型泛型 List<T>。此方法几乎总是很简单,就像

public List<T> Next(T count)
{
     List<T> result = new List<T>(count);

     for (int i = 0; i < count; i++)
        result.Add(Next());
            
     return result;
}

ToString(value)

此方法允许您格式化并控制每个数据生成器将如何作为 string 值返回。例如,在 Integer 数据生成器中,它会调用数值类型的 .ToString()。在几乎所有情况下,此方法都会调用 ToString(),但如果需要基于数据生成器中公开的属性进行进一步格式化,则可以在此处编写该代码。

ToStringArray(List<T>)

List<T> 返回 string[]。这是一个辅助方法,它将迭代 List<T> 并使用 ToString(value) 方法调用格式化后 return 一个 string 数组。此方法总是很简单,就像

public string[] ToStringArray(List<int> values)
{
    List<string> result = new List<string>(values.Count);
            
    foreach(int i in values)
        result.Add(ToString(i));

    return result.ToArray();
}

通过遵守此接口,使用数据生成器遵循调用 Setup设置属性、调用 Next(count),并在完成后调用 TearDown 的模式。以下是调用 IntegerGenerator 创建 100 个介于 5001000 之间的 int 的示例

public List Get100RandomInts()
{
    IntegerGenerator generator = new IntegerGenerator();
    generator.Setup();
    generator.MinValue = 500;
    generator.MaxValue = 1000;
    return generator.Next(100);
}

这对于单元测试可能很有用,但我还希望能够从图形用户界面应用程序中轻松运行它们,以便可以轻松添加生成器。

我写了一份分步文档,介绍了如何编写新的插件数据生成器,该文档将引导您完成从头开始构建 IntegerGenerator 的过程,您可以在我的网站 此处 获取。

示例数据生成器 GUI 应用程序

我的 GUI 应用程序的目标是允许在新生成器被放置到 GUI 应用程序运行时所在的同一文件夹时,能够发现它们。我不想编辑配置文件。我还需要 GUI 能够动态处理自定义生成过程的各种属性。例如,在生成 BirthDates 时,您需要指定所需的最小和最大年龄,或者在生成人名时,应该是男性还是女性,等等。

我采取了以下方法

  1. 应用程序启动时,它会在当前执行程序集的目录中查找所有 .dll 文件
  2. 加载每个 .dll 文件,并使用反射循环遍历每个类型,查找我们定义的自定义属性 DataGeneratorAttribute
  3. 用该属性装饰的那些类的列表显示在 listbox
  4. 当用户从列表中选择一个生成器时,我们使用反射构造该类型的一个实例,并将其附加到 .NET 中非常实用的 PropertyGrid 控件
  5. 当用户点击 Generate 时,我们调用 Setup,然后调用 Next(count),然后调用 TearDown,并将结果以选定的格式写入多行 TextBox

实际上,这一切的代码量非常少,这主要归功于功能齐全的 PropertyGrid,它与 Visual Studio IDE 类似,但也可以在我们自己的应用程序中创建和使用。它还支持属性,可以在属性下方的面板中显示文档,这大大提高了整个界面的可用性。

要将一个类暴露给 GUI 框架,我们只需将 DataGeneratorAttribute 属性添加到我们的类中,最好的方法是通过简单的 IntegerGenerator 示例来说明

[DataGeneratorAttribute(Name = "Integers", ReturnType = 
    typeof(int), Description = "Generates random integer values.")]
public class IntegerGenerator : IDataGenerator<int>
{

这些属性大多不言自明,Namelistbox 中显示的名称,ReturnTypeNext() 将返回的类型,Description 是在此生成器被选中时将在 listbox 中显示的工具提示。

以下代码可查找 GUI 应用程序运行的同一目录中的所有 IDataGenerators,并将它们添加到列表中,我们只需将该列表绑定到 listbox 控件:-

private void findAllDataGenerators(string path)
{
    string[] files = Directory.GetFiles(path, "*.dll");
    foreach (string file in files)
    {
        Assembly a = Assembly.LoadFile(file);
        Type[] types = a.GetTypes();
        foreach(Type t in types)
        {
            object[] attributes = t.GetCustomAttributes
        (typeof(Aspiring.DataGenerator.DataGeneratorAttribute), true);
            if (attributes.Length == 1)
            {
                DataGeneratorInfo g = new DataGeneratorInfo();
                g.Assembly = a;
                g.DataGeneratorType = t;
                g.Attribute = (DataGeneratorAttribute)attributes[0];
                generators.Add(g);
            }
        }
    }
}

用户选择一个生成器并将其分配给 PropertyGrid 控件后,创建该数据生成器实例的代码非常简单,如下所示:

// construct the generator and connect it to the property grid

currentDataGeneratorInfo = 
  (DataGeneratorInfo)listBoxGenerators.Items[listBoxGenerators.SelectedIndex];
Type t = currentDataGeneratorInfo.DataGeneratorType;
currentGenerator = Activator.CreateInstance(t);
propertyGrid.SelectedObject = currentGenerator;

PropertyGrid 控件会自动显示选定对象的任何 public 属性。我们通过公开数据生成器上的属性来使用此功能,以允许进行特定控制。一个例子是我们 birthdate 生成器上的 MinAgeMaxAge。属性网格将显示这些值并允许用户更改它们。验证也通过抛出 ArgumentOutOfRangeException 异常来处理,该异常会被 PropertyGrid 控件捕获并强制用户输入范围内值。我们还可以通过用 Description 属性装饰属性来添加文档。一个正确装饰和验证的属性示例如下:

[Description("The minimum age allowed for the birthdate. 
                    Must be less than MaxAge.")]
public int MinAge
{
    get { return _minAge; }
    set 
    { 
        if (value > MaxAge)
            throw new ArgumentOutOfRangeException
        ("MinAge", "MinAge must be less than or equal to MaxAge." );

        _minAge = value;
    }
}

最后一步是让生成器真正发挥作用,创建示例数据并在屏幕上进行格式化。目前的格式化非常粗糙,我已经硬编码了几种常见的方式,但认为某种格式化器可能在这里很有用;凭我的直觉,我希望它能生成数据库脚本、XML 文件等。VB 支持可能也很有用,但目前,我的需求已经满足。我只需要 List<T>!实际生成数据的代码使用反射来调用当前实例化生成器上的方法。它并不漂亮,反射从来都不是,但看起来像这样

IEnumerable q;
currentGenerator.GetType().InvokeMember("Setup", BindingFlags.InvokeMethod | 
    BindingFlags.Instance | BindingFlags.Public, null, currentGenerator, 
    new object[] {});
try
{
   q = (IEnumerable)currentGenerator.GetType().InvokeMember
    ("Next", BindingFlags.InvokeMethod | BindingFlags.Instance | 
    BindingFlags.Public, null, currentGenerator, new object[] {count});
}
finally
{
   currentGenerator.GetType().InvokeMember("TearDown", 
    BindingFlags.InvokeMethod | BindingFlags.Instance | 
    BindingFlags.Public, null, currentGenerator, new object[] {});
}

需要注意的一点是,我将 Next(count) 调用包装在 try/finally 中,这样即使出现任何故障,我们也能确保 TearDown 中的清理代码始终被调用。

GUI 应用程序大量使用反射,但整个 GUI 主窗体不到 200 行。对列表框和属性网格内置的数据绑定功能使得我的工作变得非常容易。

当前数据生成器

我现在需要将我所有的示例数据生成器迁移到这个新接口,但我已经完成了前两个大型的。我确信将来还会有更多,如果您使用此接口和框架,我很想听听您的意见。请关注我的博客,我也会在这里发布评论。

出生日期 (或任何日期),您可以指定一个格式字符串 (基于 .NET DateTime 类的格式字符串样式),介于 MinAgeMaxAge 之间。

Lorem Ipsum:您知道,那种在页面布局时用作填充的随机文本。您可以指定要构建的段落数。

整数:数字,没有小数位,介于给定的最小和最大值之间。

人名。姓名生成器使用人口普查数据来构建真实世界的姓名和称谓,并且通过使用名为 FormatString 的属性,您可以精确指定结果姓名的外观。这使我能够轻松地以任何样式或大小写快速有效地构建姓名。

以下占位符将被姓名数据替换

  • {0} = 名 (首字母大写)
  • {1) = 名 (全大写)
  • (2} = 名 (全小写)
  • {3} = 中间名 (首字母大写)
  • {4) = 中间名 (全大写)
  • (5} = 中间名 (全小写)
  • {6} = 中间首字母 (全大写)
  • {7} = 中间首字母 (全小写)
  • {8} = 姓 (首字母大写)
  • {9) = 姓 (全大写)
  • {10} = 姓 (全小写)
  • {11} = 称谓 (首字母大写)
  • {12} = 称谓 (全大写)
  • {13} = 称谓 (全小写)
  • {14} = 性别 (首字母大写)
  • {15} = 性别 (全大写)
  • {16} = 性别 (全小写)

例如:{11} {9}, {0} {6}
对于 Mr Troy Thomas Magennis,将生成
Mr MAGENNIS, Troy T

此生成器很大,因为它包含人口普查数据,可以从中选择真实姓名。CodeProject 有大小限制,因此您需要从我的博客此处下载。

历史

  • 2006 年 11 月 7 日 - 初始帖子
© . All rights reserved.