19

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

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,使用哪些库)?

4

11 回答 11

4

您似乎需要的是您的语言 (Java) 的解析器,以及名称和类型解析器。(“符号表构建器”)。

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

通常,名称/类型解析器在找不到定义时会抱怨。您希望它做的是找到导致问题的“未定义”事物,并为其推断类型。

为了

 IPerson p = new Person();

名称解析器知道未定义“Person”和“IPerson”。如果是

 Foo  p =  new Bar();

不知道您想要一个接口,只是 Foo 是 Bar 的某种抽象父级(例如,类或接口)。因此,工具必须知道它的决定(“无论何时找到这样的构造,都假设 Foo 是一个接口......”)。您可以使用启发式方法: IFoo 和 Foo 意味着 IFoo 应该是一个接口,并且有人必须将 Foo 定义为实现该接口的类。一旦该工具做出此决定,它就需要更新其符号表,以便它可以继续执行其他语句:

为了

 p.Name = "Sklivvz";

假设 p 必须是一个接口(根据前面的推论),那么 Name 必须是一个字段成员,并且它的类型似乎是赋值中的 String。

有了这个,声明:

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

名称和类型在没有进一步问题的情况下解析。

IFoo 和 Foo 实体的内容取决于您;您不必使用 get 和 set ,但这是个人喜好。

当您在同一语句中有多个实体时,这不会很好地工作:

 x = p.a + p.b ;

我们知道 a 和 b 可能是字段,但是如果它们确实是数字,或者它们是否是字符串,您无法猜测它们是什么数字类型(这对于 Java 中的字符串是合法的,不知道 C# 是什么)。对于 C++,你甚至不知道“+”是什么意思;它可能是 Bar 类的运算符。因此,您要做的是收集约束,例如,“a 是一些不定数或字符串”等,并且随着工具收集证据,它会缩小可能的约束集。(这就像那些文字问题:“乔有七个儿子。杰夫比山姆高。哈利不能躲在山姆后面。......谁是杰夫的双胞胎?”你必须收集证据并消除不可能)。您还必须担心最终会出现矛盾的情况。

您可以排除 p.a+pb 的情况,但是您不能不受惩罚地编写单元测试。如果您想要不受惩罚,那里有标准的约束求解器。(什么概念)。

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

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

我们的DMS Software Reengineering Toolkit及其Java 前端可能可以做到这一点。DMS 是一个工具构建器的工具,适用于想要构建以任意方式处理计算机语言的工具的人。(想想“用程序片段而不是数字计算”)。

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

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

我在这里推荐 Java,因为我们的 Java 前端有名称和类型解析。我们的 C# 前端没有。这只是野心的“单纯”问题;必须有人写一个,但这是相当多的工作(至少它是用于 Java 的,我无法想象 C# 真的不同)。

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

您可以使用其他一些基础设施来做到这一点,这些基础设施可以让您访问解析器以及可弯曲的名称和类型解析器。对于 C# 来说,这可能并不容易获得;我怀疑 MS 可能会给你一个解析器,并且可以访问名称和类型解析,但没有任何方法可以改变它。也许 Mono 是答案?

您仍然需要一个 was 来生成代码片段并组装它们。您可以尝试通过字符串破解来做到这一点;我将程序位粘合在一起的(长期)经验是,如果你用字符串来做,你最终会把它弄得一团糟。您真的想要代表已知类型的代码片段的片段,这些片段只能以语法允许的方式组合;DMS 做到这一点,因此没有混乱。

于 2012-04-02T01:39:27.873 回答
3

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

我不知道答案,但我会给出我的想法。

如果我自己尝试写这样的东西,我可能会看到一个 resharper 插件。我这么说的原因是因为正如你所说,resharper 可以做到,但需要单独的步骤。所以我会逐行编写一些东西,并将适当的重新生成器创建方法链接在一起。

现在我什至不知道该怎么做,因为我从来没有为 resharper 构建过任何东西,但这就是我会尝试做的。可以做到这一点是合乎逻辑的。

如果您确实编写了一些代码,请发布它,因为我发现这也很有用,能够一步生成整个骨架。很有用。

于 2008-09-27T09:47:28.623 回答
1

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

我以前在代码生成器中使用过这些,发现它们使工作变得容易得多。

于 2008-09-18T11:13:12.850 回答
1

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

于 2009-01-07T14:35:23.450 回答
1

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

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

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

我看到的一些缺点:

  • Need to run the code in "two" modes, which is annoying.
  • In order for the proxy to "see" and record calls, they must be executed; so code in a catch block, for example, has to run.
  • You have to change the way you create your object under test.
  • You have to use dynamic; you'll lose compile-time safety in subsequent runs and it has a performance hit.

The only advantage that I see is that it's a lot cheaper to create than a specialized parser.

于 2012-04-08T17:39:16.223 回答
0

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

于 2008-09-20T10:06:09.037 回答
0

尝试查看 Pex ,一个关于单元测试的微软项目,它仍在研究中

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

于 2009-08-22T14:47:48.797 回答
0

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

我从来没有使用过,你可以给 Randoop.NET 一个生成“单元测试”的机会http://randoop.codeplex.com/

于 2012-04-08T09:03:15.330 回答
-1

Visual Studio 附带了一些对您有帮助的功能:

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

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

  • Ctrl-。 (打开智能标签)
  • ENTER (生成存根)
  • F12 (去定义,带你去新方法)

仅当 IDE 认为没有匹配的方法时,才会出现智能标记。如果要在智能标签未启动时生成,可以转到Edit->Intellisense->Generate Method Stub

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

另请参阅“如何更改“生成方法存根”以在 VS 中抛出 NotImplementedException? ”以获取 GMS 上下文中的信息片段。

自动道具。请记住,属性可以简单得多:

public string Name { get; set; }

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

实现接口。当您希望类实现接口时,编写接口名称部分,激活智能标记,然后选择任一选项为接口成员生成存根。

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

于 2008-09-18T14:57:14.170 回答
-2

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

在了解编写此类测试的动机是什么之前,我会避免编写(甚至使用)这样的工具。

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

于 2008-09-18T11:09:49.253 回答
-2

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

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

于 2008-09-18T11:15:23.250 回答