如何生成源代码来创建我正在调试的对象?
我的典型场景:
- 我处理的遗留代码有一个只有生产中的客户端才有的错误
- 我附加了一个调试器,并找出如何在 他们的 系统上重现该问题给 他们的 输入.但是,我还不知道为什么会发生错误.
- 现在我想在我的本地系统上编写一个自动化测试来尝试重现然后修复错误
- The legacy code I work on has a bug that only a client in production is having
- I attach a debugger and figure out how to reproduce the issue on their system given their input. But, I don't know why the error is happening yet.
- Now I want to write an automated test on my local system to try and reproduce then fix the bug
最后一步真的很难.输入可能非常复杂,并且包含大量数据.手动创建输入(例如:P p = new P(); p.setX("x"); p.setY("x");
想象这样做 1000 次来创建对象) 非常繁琐且容易出错.事实上,您可能会注意到我刚才给出的示例中有一个错字.
That last step is really hard. The input can be very complex and have a lot of data to it. Creating the input by hand (eg: P p = new P(); p.setX("x"); p.setY("x");
imagine doing this 1000 times to create the object) is very tedious and error prone. In fact you may notice there's a typo in the example I just gave.
是否有一种自动化的方法可以从我的调试器中的断点获取字段并生成将创建该对象的源代码,并以相同的方式填充?
我想出的唯一方法就是序列化这个输入(例如,使用 Xstream).我可以将其保存到文件中并在自动化测试中将其读回.这有一个主要问题:如果类以某些方式更改(例如:重命名字段/getter/setter 名称),我将无法再反序列化对象.换句话说,测试非常脆弱.
The only thing I've come up with is to serialize this input (using Xstream, for example). I can save that to a file and read it back in in an automated test. This has a major problem: If the class changes in certain ways (eg: a field/getter/setter name is renamed), I won't be able to deserialize the object anymore. In other words, the tests are extremely fragile.
推荐答案
众所周知,Java 标准序列化在对象更改其版本(内容、字段命名)时不是很有用.它适用于快速演示项目.
Java standard serialisation is well know to be not very usefull when objects change their version ( content, naming of fields). Its fine for quick demo projects.
更适合您的需求,是 objetcs 支持您自己的(二进制)自定义序列化的方法:
这并不难,使用 DataOutputStream
写出一个对象的所有字段.但是现在引入versiong,首先写出一个versionId
.只有一个版本的对象,写出 versionId 1.这样以后,当您必须在对象中引入更改时,删除字段、添加字段、提高版本号.
More suitable for your needs, is the approach that objetcs support your own (binary) custom serialisation:
This is not difficult, use DataOutputStream
to write out all fields of an object. But now introduce versiong, by first writing out a versionId
. Objects that have only one version, write out versionId 1. That way you can later, when you have to introduce a change in your objetcs, remove fields, add fields, raise the version number.
这样的 ICustomSerializable
将首先在 readObject() 方法中从输入流中读取版本号,并根据版本 ID 调用 readVersionV1() 或例如 readVersionV2().
Such a ICustomSerializable
will then first read out the version number from the input stream, in a readObject() method, and depending on the version Id call readVersionV1() or e.g 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 的构造函数.
Further getter and setter, and a constructor with initialised the version to CURRENT_VERSION is needed.
如果您更改或添加适当的读写版本,这种序列化是安全的重构.对于使用不受您控制的外部库中的类的复杂对象,它可以做更多的工作,但字符串、列表很容易序列化.
This kind of serialisazion is safe to refactoring if you change or add also the appropriate read and write version. For complex objects using classes from external libs not und your controll, it can be more work, but strings, lists are easily serialized.
相关文章