【问题标题】:How do I generate the source code to create an object I'm debugging?如何生成源代码来创建我正在调试的对象?
【发布时间】:2010-07-14 22:55:31
【问题描述】:

我的典型场景:

  1. 我处理的遗留代码有一个只有生产中的客户端才有的错误
  2. 我附加了一个调试器,并找出如何在 他们的 系统上重现该问题(给定 他们的 输入)。但是,我不知道为什么会发生错误。
  3. 现在我想在我的本地系统上编写一个自动化测试来尝试重现然后修复错误

最后一步真的很难。输入可能非常复杂,并且包含大量数据。手动创建输入(例如:P p = new P(); p.setX("x"); p.setY("x"); 想象这样做 1000 次以创建对象)非常繁琐且容易出错。事实上,您可能会注意到我刚才给出的示例中有一个错字。

是否有一种自动化的方法可以从我的调试器中的断点获取字段并生成将创建该对象的源代码,并以相同的方式填充?

我想出的唯一办法就是序列化这个输入(例如,使用 Xstream)。我可以将其保存到文件中并在自动化测试中将其读回。这有一个主要问题:如果类以某些方式更改(例如:重命名字段/getter/setter 名称),我将无法再反序列化对象。换句话说,测试非常脆弱。

【问题讨论】:

  • 你能解释一下为什么像 XStream 这样的东西不适合,为什么你绝对需要生成源代码吗?这肯定有助于获得更好的答案。
  • 对于您现在要解决的现场测试事件,测试用例长期脆弱的问题是什么?
  • @IraBaxter 短期内没有什么问题。但是,如果有一些很酷的库/技术/策略可以比我现在更好地处理这个问题,我很想听听。
  • @DanielKaplan 你能解释一下你不喜欢反射方法的什么地方吗?
  • @assylias 很多原因。一方面,它没有正确获取设置器(m[i].toString() 将以公共/私有开始,而不是方法名称),它实际上将“(值)”传递给设置器,它不遍历对象树和我怀疑它不能正确处理集合。

标签: java testing code-generation automated-tests


【解决方案1】:

众所周知,Java 标准序列化在对象更改其版本(内容、字段命名)时不是很有用。它适用于快速演示项目。

更适合您的需求,是 objetcs 支持您自己的(二进制)自定义序列化的方法:
这个不难,用DataOutputStream写出一个对象的所有字段。但现在介绍 versiong,首先写出 versionId。只有一个版本的对象,写出 versionId 1。这样以后,当您必须在对象中引入更改时,删除字段、添加字段、提高版本号。

这样的ICustomSerializable 将首先在 readObject() 方法中从输入流中读取版本号,并根据版本 ID 调用 readVersionV1() 或例如 readVersionV2().

public Interface ICustomSerializable {
  void writeObject(DataOutputStream dos);
  Object readObject(DataInputStream dis);
}

public Class Foo {
  public static final VERSION_V1 = 1;
  public static final VERSION_V2 = 2;

  public static final CURRENT_VERSION = VERSION_V2;
  private int version;

  private int fooNumber;
  private double fooDouble;

  public void writeObject(DataOutputStream dos) {
     dos.writeInt(this.version);
      if (version == VERSION_V1) {
          writeVersionV1(dos);
      } else (version == VERSION_V2) {
          writeVersionV2(dos);
      } else {
         throw new IllegalFormatException("unkown version: " + this.version);
      }
  }
  public void writeVersionV1(DataOutputStream dos) {
        writeInt(this.fooNumber);
        writeDouble(this.fooValue);
  }
}

需要更多的 getter 和 setter,以及将版本初始化为 CURRENT_VERSION 的构造函数。

如果您更改或添加适当的读写版本,这种序列化是安全的重构。对于使用不受您控制的外部库中的类的复杂对象,它可以做更多的工作,但字符串、列表很容易序列化。

【讨论】:

  • 这是一个有趣的方法。事实上,我正在考虑编写自己的 xstream 序列化程序来写出源代码(如果这甚至是一个选项)。我不喜欢这个建议的一点是构建中断可能会通知我我必须实施writeVersionV2。我不想在那种情况下写它。
【解决方案2】:

我认为您想要做的是存储“状态”,然后在您的测试中恢复它以确保错误得到修复。

简短回答:afaik 没有这样的通用代码生成工具,但只要保留几个约束,编写这样的工具是小工作。

长评: 有一些限制条件可以发挥作用。如果一切都只是你需要的所有字段的带有 getter 和 setter 的 bean,那么为此生成代码并不是那么困难。是的,如果您将生成的代码与普通代码一起重构,那么重命名是安全的。如果缺少 setter,那么这种方法将不起作用。这只是为什么这不是通用解决方案的一个例子。

重构还可以例如将字段移动到其他类。您想如何引入该类其他字段的值?你以后怎么知道他们改变了你保存的状态是否仍然反映了关键数据?或者更糟糕的是,想象重构赋予同一个字段不同的含义。

错误本身的性质也是一个约束。例如,想象一下错误发生是因为字段/方法具有这个和那个名称。如果现在重构更改了名称,则无论您的状态如何,该错误都不会再出现。

这些只是随意的例子,可能与您的真实案例完全无关。但这是个案决定,而不是一般策略。无论如何,如果您知道您的代码中的错误和重构都表现得足够好,那么制作这样一个工具可以在不到一天的时间内完成,可能要少得多。

使用 xstream,您也可以部分获得此功能,但您必须自己更改 xml。如果您使用例如 db4o,您将不得不告诉它这个和那个字段现在有这个和那个名称。

【讨论】:

    猜你喜欢
    • 2016-08-05
    • 2020-11-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-23
    • 1970-01-01
    • 1970-01-01
    • 2021-09-17
    相关资源
    最近更新 更多