从单元测试自动生成类?

时间:2008-09-18 10:58:57

标签: c# unit-testing code-generation

我正在寻找一种可以进行单元测试的工具,比如

IPerson p = new Person();
p.Name = "Sklivvz";
Assert.AreEqual("Sklivvz", p.Name);

并自动生成相应的存根类和接口

interface IPerson         // inferred from IPerson p = new Person();
{
    string Name 
    { 
        get;              // inferred from Assert.AreEqual("Sklivvz", p.Name);
        set;              // inferred from p.Name = "Sklivvz";
    }
}

class Person: IPerson     // inferred from IPerson p = new Person();
{
    private string name;  // inferred from p.Name = "Sklivvz";

    public string Name    // inferred from p.Name = "Sklivvz";
    {
        get
        {
            return name;  // inferred from Assert.AreEqual("Sklivvz", p.Name);
        }
        set
        {
            name = value; // inferred from p.Name = "Sklivvz";
        }
    }

    public Person()       // inferred from IPerson p = new Person();
    {
    }
}

我知道ReSharper和Visual Studio会做其中的一些,但我需要一个完整的工具 - 命令行或诸如此类的东西 - 它会自动推断需要完成的工作。 如果没有这样的工具,你会如何编写它(例如,从头开始使用哪些库扩展ReSharper)?

11 个答案:

答案 0 :(得分:4)

您需要的是一种语言解析器(Java),以及名称和类型解析器。 ("符号表构建器")。

解析源文本后,编译器通常有一个名称解析器,它试图记录名称及其对应类型的定义,以及一个类型检查器,用于验证每个表达式是否具有有效类型。

通常,名称/类型解析器会在无法找到定义时发出抱怨。你想要它做的是找到" undefined"引起问题的事情,并推断出它的类型。

有关

 IPerson p = new Person();

名称解析器知道" Person"和#34; IPerson"没有定义。如果是

 Foo  p =  new Bar();

没有线索你想要一个接口,只是说Foo是Bar的某种抽象父类(例如,类或接口)。因此决定必须为工具所知("每当你找到这样的结构时,假设Foo是一个接口......")。您可以使用启发式:IFoo和Foo意味着IFoo应该是一个接口,并且有人必须将Foo定义为实现该接口的类。一旦 工具做出了这个决定,它需要更新它的符号表,以便它可以 转到其他陈述:

有关

 p.Name = "Sklivvz";

假设p必须是一个接口(通过前面的推断),那么Name必须是一个字段成员,并且它的类型是赋值的字符串。

有了这个,声明:

 Assert.AreEqual("Sklivvz", p.Name);

名称和类型解决没有进一步的问题。

IFoo和Foo实体的内容取决于您;你没有必要使用get and set,而是个人品味。

当你在同一个声明中有多个实体时,这不会很有效:

 x = p.a + p.b ;

我们知道a和b可能是字段,但你不能猜测它们是数字的数字类型,或者它们是字符串(这对于Java中的字符串是合法的,关于C#的dunno是合法的)。 对于C ++,你甚至不知道" +"手段;它可能是Bar类的运算符。 所以你需要做的是收集约束,例如," a是一些不定数或字符串"等等,当工具收集证据时,它缩小了可能的约束集。 (这就像那些字问题一样:"乔有七个儿子。杰夫比萨姆高。哈利不能躲在萨姆身后......谁是杰夫的双胞胎?"你必须收集证据并删除不可能性)。您还必须担心最终会出现矛盾的情况。

你可以排除p.a + p.b案例,但是你不能编写你的单元测试而不受惩罚。如果你想要有罪不罚,那里有标准的约束求解器。 (这是一个什么概念)。

好的,我们有想法,现在,这可以用实际的方式完成吗?

第一部分需要解析器和可弯曲的名称和类型解析器。您需要一个约束求解器或至少一个"定义的值流向未定义的值"操作(平凡约束求解器)。

我们的DMS Software Reengineering Toolkit及其Java Front End可能会这样做。 DMS是一个工具构建工具,适用于那些想要构建以任意方式处理计算机语言的工具的人。 (想想"用程序片段计算而不是数字")。

DMS提供了通用的解析机制,并且可以为它给出的任何前端构建一个树(例如,Java,以及那里的C#前端)。 我选择Java的原因是我们的Java前端具有所有名称和类型解析机制,并且它以源代码形式提供,因此可以弯曲。如果你坚持使用普通的约束求解器,你可能会弯曲Java名称解析器来找出类型。 DMS将允许您组装与代码片段对应的树,并将它们合并为更大的树;当您的工具收集符号表的事实时,它可以构建原始树。

在某个地方,你必须决定你已经完成了。该工具必须查看多少单元测试 在它知道整个界面之前? (我想它会吃你提供的所有东西吗?)。 完成后,它会为各个成员组装片段并为接口构建AST; DMS可以使用它的prettyprinter将AST转换回源代码,就像你已经显示的那样。

我建议使用Java,因为我们的Java前端具有名称和类型解析。我们的C#前端没有。这只是一个"纯粹的"野心的问题;有人必须写一个,但这相当多的工作(至少是Java,我无法想象C#真的不同)。

但这个想法原则上使用DMS工作正常。

您可以使用其他一些基础结构来执行此操作,该基础结构可让您访问解析器以及可弯曲的名称和类型解析程序。对于C#而言,这可能并不容易;我怀疑MS可能会给你一个解析器,并且可以访问名称和类型解析,但不能改变它。也许Mono就是答案?

你仍然需要一个生成代码片段并组装它们。您可能会尝试通过字符串黑客来做到这一点;我将程序位粘合在一起的经验是,如果你用字符串来做,你最终会搞得一团糟。你真的想要代表已知类型的代码片段的片段,它们只能以语法允许的方式组合; DMS确实没那么乱。

答案 1 :(得分:3)

令人惊讶的是,没有人真正对你提出的要求做出任何贡献。

我不知道答案,但我会对此发表看法。

如果我自己尝试写这样的东西,我可能会看到一个resharper插件。我说的原因是因为正如你所说,resharper可以做到,但是在个别步骤中。所以我会写一些逐行的东西并应用链接在一起的相应的resharper创建方法。

现在我决不知道怎么做,因为我从来没有为resharper建造任何东西,但这就是我想要做的。从逻辑上说它可以完成。

如果你写了一些代码,请发布它,因为我发现它也很有用,能够一步生成整个骨架。非常有用。

答案 2 :(得分:1)

如果您打算编写自己的实现,我肯定会建议您查看NVelocity(C#)或Velocity(Java)模板引擎。

我之前在代码生成器中使用过它们,并且发现它们使工作变得更容易。

答案 3 :(得分:1)

这是可行的 - 至少在理论上是这样。我会做的是使用csparser之类的东西来解析单元测试(不幸的是你不能编译它),然后从那里取出它。我能看到的唯一问题是你所做的事情在方法论方面是错误的 - 从实体类生成单元测试更有意义(事实上,Visual Studio确实这样做)而不是反过来。

答案 4 :(得分:1)

我认为这个问题的真正解决方案是一个非常专业的解析器。由于这不是那么容易做到的,我有一个更便宜的想法。不幸的是,你必须改变编写测试的方式(即只创建对象):

dynamic p = someFactory.Create("MyNamespace.Person");
p.Name = "Sklivvz";
Assert.AreEqual("Sklivvz", p.Name);

将使用工厂对象。如果它可以找到命名对象,它将创建它并返回它(这是正常的测试执行)。如果它没有找到它,它将创建一个记录代理(一个DynamicObject),它将记录所有的调用,最后(可能是拆除)可以发出反映的类文件(可能基于一些模板)是什么“看到”被称为。

我看到的一些缺点:

  • 需要以“两种”模式运行代码,这很烦人。
  • 为了使代理“看到”并记录呼叫,必须执行它们;因此,例如,catch块中的代码必须运行。
  • 您必须更改您在测试中创建对象的方式。
  • 您必须使用dynamic;你将在后续的运行中失去编译时的安全性并且性能受到影响。

我看到的唯一优势是创建比专业解析器便宜

答案 5 :(得分:0)

我喜欢DevExpress的CodeRush。他们有一个巨大的可定制模板引擎。对我来说最好的是他们没有对话框。它们还具有从不存在的接口创建方法和接口以及类的功能。

答案 6 :(得分:0)

尝试查看Pex,一个关于单元测试的微软项目,目前仍处于研究阶段

research.microsoft.com/en-us/projects/Pex/

答案 7 :(得分:0)

我认为您正在寻找的是一个模糊测试工具包(https://en.wikipedia.org/wiki/Fuzz_testing)。

我从未使用过的艰难,你可能会让Randoop.NET有机会产生'单元测试'http://randoop.codeplex.com/

答案 8 :(得分:-1)

Visual Studio附带了一些可以为您提供帮助的功能:

生成方法存根。当您编写对不存在的方法的调用时,您将在方法名称上获得一个智能标记,您可以使用该标记根据您传递的参数生成方法存根。

如果您是键盘人(我是),那么在输入右括号后,您可以执行以下操作:

  • Ctrl - 。(打开智能标记)
  • ENTER (生成存根)
  • F12 (转到定义,转到新方法)

仅当IDE认为没有匹配的方法时,才会显示智能标记。如果您想在智能标记未启动时生成,您可以转到编辑 - >智能感知 - >生成方法存根

<强>片段即可。小代码模板,可以轻松生成一些通用代码。有些很简单(尝试“if [TAB] [TAB]”)。有些很复杂('switch'会为枚举生成个案)。你也可以自己写。对于您的情况,请尝试“class”和“prop”。

有关GMS上下文中的信息片段,另请参阅“How to change “Generate Method Stub” to throw NotImplementedException in VS?”。

<强>自动属性即可。请记住,属性可以更简单:

public string Name { get; set; }

创建课程。在解决方案资源管理器中,单击项目名称或子文件夹,选择添加 - >类。输入新班级的名称。点击 ENTER 。您将在正确的命名空间中获得类声明等。

实施界面。如果希望类实现接口,请编写接口名称部分,激活智能标记,并选择任一选项以为接口成员生成存根。

这些不是您正在寻找的100%自动化解决方案,但我认为这是一个很好的缓解措施。

答案 9 :(得分:-2)

我发现每当我需要像这样的代码生成工具时,我可能会编写一些可以更通用的代码,所以我只需要编写一次。在您的示例中,那些getter和setter似乎没有向代码添加任何值 - 事实上,它实际上只是断言C#中的getter / setter机制有效。

在了解编写这类测试的动机之前,我不会编写(甚至使用)这样的工具。

顺便说一句,你可能想看看NBehave

答案 10 :(得分:-2)

当我只需要一个简单的存根时,我会使用Rhino Mocks。

http://www.ayende.com/wiki/Rhino+Mocks+-+Stubs.ashx