此空间已闲置,个人主页已转到http://www.hixk.net

测试对象串行化

上一篇 / 下一篇  2007-08-30 11:20:14 / 个人分类:测试知识

51Testing软件测试网 TJPS+uW~

Elliotte Harold(elharo@metalab.unc.edu), 副教授, Polytechnic University
7[Q h\WL1YK051Testing软件测试网:h#AQLI bN^7x#r9J

/LHy%l0~?02006 年 7 月 06 日

b Jecfys#s?0
即使最杰出的开发人员有时也会忘记测试对象串行化,但那并不能作为您犯下同一错误的借口。在这篇文章中,Elliotte Rusty Harold 将解释对对象串行化进行单元测试的重要性,并为您展示一些应牢记的测试。
51Testing软件测试网A0w#KK7[

测试驱动的开发的总体原则之一就是应测试一个类已发布的所有接口。如果客户机能够调用方法或访问字段,那么就测试它。但在 Java™ 语言中,许多类都有一个已发布的接口容易被遗漏:通过类实例生成的串行化对象。有时这些类显式实现Serializable。而有时则是直接从超类继承这一特性。在任何一种情况下,您都应该测试其串行化形式。本文将介绍几种测试对象串行化的方法。

w4~XS:],z051Testing软件测试网^j)Z2Z6GyT3@Gwq

测试串行化

&s8n*Z\^/w!d} ID051Testing软件测试网:vdd|/O?

对串行化来说,测试极其重要,因为串行化非常非常容易出错。在修复 bug 或优化类时,非常容易破坏所有已有串行化对象。如果您在更改代码时未考虑串行化,几乎可以肯定您必将破坏原有对象。若您正在为任何形式的持久性存储使用串行化,那么这将是一个严重的 bug。即便仅为流程间的瞬时消息传递(如在 RMI 中)使用对象串行化,更改串行化格式也会使那些各类的版本不完全相同的系统无法顺利交换数据。

"_qi$d3S\Y0

1F5Sw;[Gq(R%C5h e5B0幸运的是,若您谨慎对待串行化问题,在处理类时通常可以避免不兼容的更改。Java 语言提供了多种方法,可维护一个类的不同版本之间的兼容性,包括:51Testing软件测试网JL0uV2t

  • serialVersionUID
  • transient修饰符
  • readObject()writeObject()
  • writeReplace()readResolve()
  • serialPersistentFields
51Testing软件测试网/VOCrrrE

对于这些解决方案来说,最大的问题就在于程序员未使用它们。当您将精力集中在修复 bug、添加特性或解决性能问题时,往往不会停下来思考您的更改对串行化造成的影响。然而串行化是一个涉及范围极广的问题 —— 跨越一个系统的多个不同层。几乎所有更改都会涉及对串行化有某种影响的一个类的实例字段。这正是单元测试发挥作用的时机。在本文后续各节中,我将为您展示一些简单的单元测试,这些单元测试能确保您不会不经意地更改可串行化类的串行格式。51Testing软件测试网Z LJ} ?.NO'J,em

51Testing软件测试网 D%J ]m4m|~;[G

lk ]0@*N_}7A [0
51Testing软件测试网 T%x~g?R'~*@%w0K.l
51Testing软件测试网&} Y~o5zy,n0u
51Testing软件测试网U ET6m`B(I#n

EE U;u1M@k5k^0

fS` ]F/e6qx }0我能否将其串行化?

0^UG-` aIl7Y0

BXXf A \0通常您编写的第一个串行化测试就是用于验证串行化是否可行的测试。即使一个类实现了Serializable,依然不能保证它能够串行化。例如,如果一个可串行化的容器(如ArrayList)包含一个不可串行化的对象(如Socket),则在您尝试串行化此容器时,将抛出NotSerializableException

.D$cEJzj&BH9UZ051Testing软件测试网"|vt*d)xUz,@hTo

通常,对此测试,您只需在ByteArrayOutputStream上写入数据。若未抛出任何异常,测试即通过。如果您愿意,还可测试一些已写入的输出。例如,清单 1 所示代码片段用于测试 Jaxen 的BaseXPath类是否可串行化:

H9D;f8RG{ug)LTu;g0
K6as;B)w7@0清单 1. 此类是否可串行化?
[7qW[pl {0
public void testIsSerializable() 
   throws JaxenException, IOException {

    BaseXPath path = new BaseXPath("//foo", new DocumentNavigator());
    ByteArrayOutputStream ōut = new ByteArrayOutputStream();
    ObjectOutputStream ōos = new ObjectOutputStream(out);
    oos.writeObject(path);
    oos.close();
    assertTrue(out.toByteArray().length > 0);

    }
51Testing软件测试网M9e!f1F6ke*h
51Testing软件测试网U)r d"_4v

tY?2}J/}0
51Testing软件测试网zy#?)Opp Y6H
51Testing软件测试网F&c4FXHyS
51Testing软件测试网(f QT-[S2`
51Testing软件测试网x S+ONcc1T`

测试串行化形式51Testing软件测试网 p ~,Cyk n

l^5~8]!Q$K^\/b}0接下来,您想要编写一个测试,不仅要验证输出得到了显示,还要验证输出是正确的。您可通过两种方式完成这一任务:51Testing软件测试网l`NyT+\x

  • 反串行化对象,并将其与原始对象相比较。
  • 逐字节地将其与参考 .ser 文件相比较。
51Testing软件测试网:P8h)g*GN4Of

我通常会从第一种选择入手,因为它还提供了一个反串行化的简单测试,而且编码和实现相对来说比较容易。例如,清单 2 所示代码片段将测试 Jaxen 的SimpleVariableContext类是否可写入并在之后重新读回:51Testing软件测试网tw p'K2MtbX

51Testing软件测试网"F$n&zV`X"C X
清单 2. 反串行化对象,并将其与原始对象相比较51Testing软件测试网ZQ8YVKeO J-n%R!Wxo
public void testRoundTripSerialization()
   throws IOException, ClassNotFoundException, UnresolvableException {

    // construct test object
    SimpleVariableContext ōriginal = new SimpleVariableContext();
    original.setVariableValue("s", "String Value");
    original.setVariableValue("x", new Double(3.1415292));
    original.setVariableValue("b", Boolean.TRUE);

    // serialize
    ByteArrayOutputStream ōut = new ByteArrayOutputStream();
    ObjectOutputStream ōos = new ObjectOutputStream(out);
    oos.writeObject(original);
    oos.close();

    //deserialize
    byte[] pickled = out.toByteArray();
    InputStream in = new ByteArrayInputStream(pickled);
    ObjectInputStream ōis = new ObjectInputStream(in);
    Object o = ois.readObject();
    SimpleVariableContext copy = (SimpleVariableContext) o;

    // test the result
    assertEquals("String Value", copy.getVariableValue("", "", "s"));
    assertEquals(Double.valueOf(3.1415292), copy.getVariableValue("", "", "x"));
    assertEquals(Boolean.TRUE, copy.getVariableValue("", "", "b"));
    assertEquals("", "");

  }
51Testing软件测试网Bi2r"zPmU
51Testing软件测试网+N'VE0SL^

让我们再试一次……51Testing软件测试网`5l y3[-G1l6C*P

~\VI;l,Y0在测试代码基础中那些此前从未测试过的部分时,几乎总是会发现 bug,对象串行化也是这样。在我第一次运行清单 2 中的测试时,测试失败了,输出结果如清单 3 所示:51Testing软件测试网)^ehi"^


ML4Dh2b`j L0清单 3. 不可串行化
D%[3{&Pm0
java.io.NotSerializableException:
org.jaxen.QualifiedName
             at
java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1075)
             at
java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:291)
             at java.util.HashMap.writeObject(HashMap.java:984)
             at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
             at
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)

             at
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)

             at java.lang.reflect.Method.invoke(Method.java:585)
             at
java.io.ObjectStreamClass.invokeWriteObject(ObjectStreamClass.java:890)
             at
java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1333)
             at
java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1284)

             at
java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1073)
             at
java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1369)
             at
java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1341)
             at
java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1284)

             at
java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1073)
             at
java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:291)
             at
org.jaxen.test.SimpleVariableContextTest.testRoundTripSerialization
  (SimpleVariableContextTest.java:90)
51Testing软件测试网-Y/]k3I~?6^
51Testing软件测试网 g]k'N;|aK!e"K'n

这表明,SimpleVariableContext包含一个对QualifiedName对象的引用,QualifiedName类未标记为Serializable。我为QualifiedName的类签名添加了implements Serializable,这一次测试顺利通过。

{6[d;^ Xhpa051Testing软件测试网 N%q r6ox

注意,此测试实际上并未验证串行化格式是否正确 —— 只是验证出对象能够来回转换。为测试正确性,您需要生成一些参考文件,以便与类的所有未来版本的输出相比较。

d I3z2U j'qC051Testing软件测试网f2_#zX8xvJJ'g/s
51Testing软件测试网] {2Ya$@ T
51Testing软件测试网 L3B$Dl`q

c:]g p4T2\ a6I0

4|,i jt3I1wD:j+xI j0测试反串行化

h wY&n,rf#I051Testing软件测试网#Gk#n_GcZ3_ {

通常,您不能依赖默认串行化格式来保持类的不同版本间的文件格式兼容性。您必须使用serialPersistentFieldsreadObject()writeObject()方法和/或transient修饰符,通过各种方式进行定制。如果您确实对类的串行化格式做出了不兼容的更改,应相应更改serialVersionUID字段,以指出您这样做了。

d~U w S{o051Testing软件测试网u&S$Fi6HWN

正常情况下,您不会过分关注串行化对象的详细结构。而只是关注最初使用的那种格式随着类的发展得到了维护。一旦类基本上具备了恰当的形式,即可写入一些类的串行化实例,并存储在随后可将其作为参考使用的位置处。(您很可能确实希望多多少少地考虑如何串行化才能确保足够的灵活性,以便应对未来的发展。)

1{)M.e2C:wdn5d0

&l]T A-[0编写串行化实例的程序是临时代码,只需使用一次。实际上,您根本就不应该多次运行这段代码,因为您不希望获得串行化格式中的任何意外更改。例如,清单 4 展示了用于串行化 Jaxen 的SimpleVariableContext类的程序:

\1l%Q8kTi)w"R&H.^051Testing软件测试网)kVt iY
清单 4. 写入串行化实例的程序51Testing软件测试网(C wZ2u*D^i
import org.jaxen.*;
import java.io.*;

public class MakeSerFiles {

  public static void main(String[] args) throws IOException {

    OutputStream fout = new FileOutputStream("xml/simplevariablecontext.ser");
    ObjectOutputStream ōut = new ObjectOutputStream(fout);

    SimpleVariableContext context = new SimpleVariableContext();
    context.setVariableValue("s", "String Value");
    context.setVariableValue("x", new Double(3.1415292));
    context.setVariableValue("b", Boolean.TRUE);

    out.writeObject(context);
    out.flush();
    out.close();

  }

}

g'ZD*` H0

n Rr2k|(n0您只需将一个串行化对象写入文件 —— 而且只需一次。这是您希望保存的文件,而不是用于写入的代码。清单 5 展示了 Jaxen 的SimpleVariableContext类的兼容性测试:

i]'E8~_yi8K051Testing软件测试网m:x*c_;rp r
清单 5. 确保文件格式未被更改
y'Si)Sea7B+j0
public void testSerializationFormatHasNotChanged()
   throws IOException, ClassNotFoundException, UnresolvableException {

    //deserialize
    InputStream in = new FileInputStream("xml/simplevariablecontext.ser");
    ObjectInputStream ōis = new ObjectInputStream(in);
    Object o = ois.readObject();
    SimpleVariableContext context = (SimpleVariableContext) o;

    // test the result
    assertEquals("String Value", context.getVariableValue("", "", "s"));
    assertEquals(Double.valueOf(3.1415292), context.getVariableValue("",
"", "x"));
    assertEquals(Boolean.TRUE, context.getVariableValue("", "", "b"));
    assertEquals("", "");

  }

,j%c"l0EM051Testing软件测试网)smT1N3sg~
51Testing软件测试网\8P"?4D X-v

#B.S ^0E+K!P/x&kBv%a0

}&pX"IPm.L,fj0
51Testing软件测试网 gqyRc4T.A

K8Ud2q._~6S8]#s0

9m E$i2eUI!j!l6d0测试不可串行性

k Of3jB4j-_K0g0

V!FCs_@U Fn?0默认情况下,类通常是可串行化的。例如,java.lang.Throwablejava.awt.Component的任何子类都会从其祖先继承可串行性。在某些情况下,这也是您希望的结果,但并非总是如此。有的时候,串行化可能会成为安全漏洞,使恶意程序员能够在不调用构造函数或 setter 方法的情况下创建对象,从而规避了您小心翼翼地在类中构建的所有约束性检查。

5A$tD6| Z.SR x#N051Testing软件测试网#z1C&d/W2i

若您希望类可串行化,就需要测试它,这与您需要测试一个直接实现了Serializable的类相同。如果您不希望类可串行化,则应重写writeObject()readObject(),使两者均抛出NotSerializableException,随后您也需要对其进行测试。

8f]}[Z H7M(r g0

&q,iLW2}0此类测试的实现方法与其他任何 JUnit 异常测试相似。只需在应抛出异常的语句两端包围一个try块即可,随后紧接欲抛出异常的语句之后添加一条fail()语句。如果愿意,您还可在catch中作出一些关于所抛出异常的断言。例如,清单 6 验证了FunctionContext是不可串行化的:51Testing软件测试网2k0b-o$y)P[ bK~ AQU


o7D%i5a\\0清单 6. 测试 FunctionContext 是不可串行化的
_rS{6~ A0
public void testSerializeFunctionContext() 
   throws JaxenException, IOException {

    DOMXPath xpath = new DOMXPath("/root/child");
    FunctionContext context = xpath.getFunctionContext();
    ByteArrayOutputStream ōut = new ByteArrayOutputStream();
    ObjectOutputStream ōout = new ObjectOutputStream(out);
    try {
        oout.writeObject(context);
        fail("serialized function context");
    }
    catch (NotSerializableException ex) {
        assertNotNull(ex.getMessage());
    }

  }
51Testing软件测试网.a-F!R:z4Bd*jL t

(x6i:\1uiZ9n2l R0Java 5 和 JUnit 4 使异常测试更为轻松。只需在@Test注释中声明所需异常即可,如清单 7 所示:51Testing软件测试网o1{N6T,NAP

51Testing软件测试网1_ u7uI'd}] |
清单 7. 带有注释的异常测试
2K[.cq sT/k{-{8g0
@Test(expected=NotSerializableException.class) public
void testSerializeFunctionContext()
  throws JaxenException, IOException {

    DOMXPath xpath = new DOMXPath("/root/child");
    FunctionContext context = xpath.getFunctionContext();
    ByteArrayOutputStream ōut = new ByteArrayOutputStream();
    ObjectOutputStream ōout = new ObjectOutputStream(out);
    oout.writeObject(context);

  }

*l+uK3A zT8V/X051Testing软件测试网2eIw/h6b U
51Testing软件测试网8S? LFR6@4G
51Testing软件测试网!G7GA A"gE2Py
51Testing软件测试网3Y|0Z0v ?NX[r

%A@9w K'O}0
^qQ&P,G;U,E051Testing软件测试网#[*[G'[!LXv

结束语51Testing软件测试网)oYO0C WG

51Testing软件测试网Vamf(o

串行化格式可以说是代码基础中最脆弱、健壮性最差的部分。有的时候,似乎只要以奇异的眼神盯着它,它就会被破坏。单元测试和测试驱动的开发这些出色的工具使您可以信心十足地管理此类脆弱系统 —— 但只有在您确实使用了这些工具时,它们才能发挥作用。

#z1A6ygJh R"U.V0

+O[,RY#K[j-b _0若您关注对象串行化,特别是希望为长期持久性存储使用串行化对象时,就必须对串行化进行测试。不要假设您的 Java 代码所做的一切都是正确的 —— 它很可能会出错!如果您将串行化测试作为测试套件的固定部分,则维护长期兼容性就会更轻松。您花费在对象串行化单元测试上的时间将为您带来成倍的回报,此后调试时您能节省的时间将数倍于投入时间。

8o)f ?B:`B^051Testing软件测试网sz Ii {O'N9l
51Testing软件测试网? Fu#S)G%x

?/q$i(nB| ^0参考资料

\$z4[rZ!V0学习51Testing软件测试网F4m#Z ]y,R_.v
  • 您可以参阅本文在 developerWorks 全球站点上的英文原文
    \'R:`]&tMc0
    ;~:c0`:aG O0
  • 利用 Ant 和 JUnit 进行增量开发”(Malcolm Davis,developerWorks,2000 年 11 月):介绍 Java 平台上的单元测试。51Testing软件测试网`;Q\,IgZe.pZ
    51Testing软件测试网9Z {mPnlDb
  • 揭开极端编程的神秘面纱: 测试驱动的编程”(Roy Miller,developerWorks,2003 年 4 月):介绍关于测试驱动编程的一切,更重要的是 —— 测试驱动编程与什么无关。
    m d,k-C]"XZ v$u051Testing软件测试网']U k/F&Z
  • Keeping critters out of your code”(David Carew,Sandeep Desai,Anthony Young-Garner,developerWorks,2003 年 6 月):介绍服务器端应用服务器环境的单元测试。
    Ht1J6S x3\8r0
    Qk,[VXjdps0
  • JUnit 4 抢先看”(Elliotte Rusty Harold,developerWorks,2005 年 9 月):介绍 JUnit 4 中基于注释的全新架构,需要 Java 5 或更新版本。
    )H#]k'u9i DA${ U A g051Testing软件测试网/l Zd1W _
  • Java I/O,第 2 版(Elliotte Rusty Harold;O'Reilly,2006 年 5 月):深入讨论对象串行化的新版图书。
    &k^a2L^ O051Testing软件测试网T.L0ha~ GX
  • Pragmatic Unit Testing(Dave Thomas 和 Andy Hunt;Pragmatic Programmer,2003 年 9 月):单元测试 Java 代码的完整介绍。
    g%jW9a3i0
    S _7zt D+h"E!t0
  • Java 技术专区:数百篇关于 Java 编程各个方面的文章。
51Testing软件测试网ax%o} x-RZ7m U
获得产品和技术
Y@.G)ygr8TD0
  • JUnit:影响您的测试。
    'E~2K0Ast1Ux051Testing软件测试网cO"Cv w r

5TcJ-aqVX#Vn0讨论
;g$El,j+{ l0
  • developerWorks blogs:加入 developerWorks 社区。
    9C%l;l1dv,^*n051Testing软件测试网!\#ROq3Y

YJf hpnn:@ T051Testing软件测试网)ymc*ZD#s
51Testing软件测试网A)n8c%PCw/`

关于作者51Testing软件测试网6PL6BN Yg

0Rd&JG3k ^0

#uux2Z CC{ z"c0
51Testing软件测试网q6Fn!z#_w7S/g

Elliotte Rusty Harold 来自新奥尔良, 现在他还定期回老家喝一碗美味的秋葵汤。不过目前,他和妻子 Beth 定居在纽约临近布鲁克林的 Prospect Heights,同住的还有他的猫咪 Charm(取自夸克)和 Marjorie(取自他岳母的名字)。他是 Polytechnic 大学计算机科学系的一名副教授,讲授 Java 和面向对象编程。他的Cafe au LaitWeb 站点已经成为 Internet 上最流行的独立 Java 站点之一,它的姊妹站点Cafe con Leche是最流行的 XML 站点之一。他编写的图书包括Effective XMLProcessing XML with JavaJava Network ProgrammingJava I/O。目前,他在从事处理 XML 的XOMAPI、JaxenXPath 引擎和Jester测试覆盖工具的开发工作51Testing软件测试网5I6i+f2T#n4f&T H


TAG: 测试知识

 

评分:0

我来说两句

日历

« 2024-05-05  
   1234
567891011
12131415161718
19202122232425
262728293031 

数据统计

  • 访问量: 23152
  • 日志数: 28
  • 图片数: 2
  • 文件数: 5
  • 建立时间: 2007-05-15
  • 更新时间: 2008-03-26

RSS订阅

Open Toolbar