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

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)?

有帮助吗?

解决方案

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

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

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

为了

 IPerson p = new Person();

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

 Foo  p =  new Bar();

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

为了

 p.Name = "Sklivvz";

假设 p 必须是一个 Interface(根据前面的推论),那么 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+p.b 的情况,但这样您就无法编写单元测试而不受惩罚。如果您想不受惩罚,可以使用标准约束求解器。(什么概念)。

好的,我们有了想法,现在可以用实际的方式来实现吗?

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

我们的 DMS 软件再造工具包 以其 Java前端 也许可以做到这一点。DMS 是一个工具构建者的工具,适合那些想要构建以任意方式处理计算机语言的工具的人们。(想想“用程序片段而不是数字进行计算”)。

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

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

我在这里建议使用 Java,因为我们的 Java 前端具有名称和类型解析。我们的 C# 前端没有。这“仅仅”是一个野心问题;必须有人编写一个,但这是一项相当大的工作(至少对于 Java 来说是这样,我无法想象 C# 真的有什么不同)。

但原则上,使用 DMS 的想法效果很好。

您可以使用其他一些基础设施来完成此操作,这些基础设施使您可以访问解析器以及可弯曲的名称和类型解析器。对于 C# 来说,这可能不太容易实现;我怀疑微软可能会给你一个解析器,并访问名称和类型解析,但没有任何方法可以改变它。也许 Mono 就是答案?

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

其他提示

令人惊讶的是没有人真正满足你的要求。

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

如果我尝试自己写这样的东西,我可能会看到 resharper 插件。我这么说的原因是因为正如您所说,resharper 可以做到这一点,但需要单独的步骤。因此,我会逐行编写一些内容,并应用链接在一起的适当的 resharper 创建方法。

现在我什至不知道如何做到这一点,因为我从未为 resharper 构建过任何东西,但这就是我会尝试做的。从逻辑上讲,这是可以做到的。

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

如果您打算编写自己的实现,我绝对建议您看一下 NV速度 (C#) 或 速度 (Java) 模板引擎。

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

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

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

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

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

我看到的一些缺点:

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

我认为唯一的优点是 便宜很多 创建一个专门的解析器。

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

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

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

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

虽然我从未使用过,但您可能会给 Randoop.NET 一个生成“单元测试”的机会 http://randoop.codeplex.com/

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

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

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

  • Ctrl-. (打开智能标签)
  • 进入 (生成存根)
  • F12 (转到定义,带您了解新方法)

仅当 IDE 认为没有匹配的方法时,智能标记才会出现。如果你想在智能标签没有起来的时候生成,可以去 编辑->智能感知->生成方法存根.

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

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

自动螺旋桨. 。请记住,属性可以简单得多:

public string Name { get; set; }

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

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

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

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

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

顺便说一句,你可能想看看 行为规范?

当我只需要一个简单的存根时,我使用 Rhino Mocks 来实现此目的。

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

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top