測試對象串行化
即使最杰出的 開發 人員有時也會忘記測試對象串行化,但那并不能作為您犯下同一錯誤的借口。在這篇文章中,Elliotte Rusty Harold 將解釋對對象串行化進行 單元測試 的重要性,并為您展示一些應牢記的測試。 測試驅動的開發的總體原則之一就是應測試一個類已
即使最杰出的
開發人員有時也會忘記
測試對象串行化,但那并不能作為您犯下同一錯誤的借口。在這篇文章中,Elliotte Rusty Harold 將解釋對對象串行化進行
單元測試的重要性,并為您展示一些應牢記的測試。
測試驅動的開發的總體原則之一就是應測試一個類已發布的所有接口。如果客戶機能夠調用方法或訪問字段,那么就測試它。但在 Java™ 語言中,許多類都有一個已發布的接口容易被遺漏:通過類實例生成的串行化對象。有時這些類顯式實現 Serializable
。而有時則是直接從超類繼承這一特性。在任何一種情況下,您都應該測試其串行化形式。本文將介紹幾種測試對象串行化的方法。
測試串行化
對串行化來說,測試極其重要,因為串行化非常非常容易出錯。在修復 bug 或優化類時,非常容易破壞所有已有串行化對象。如果您在更改代碼時未考慮串行化,幾乎可以肯定您必將破壞原有對象。若您正在為任何形式的持久性存儲使用串行化,那么這將是一個嚴重的 bug。即便僅為流程間的瞬時消息傳遞(如在 RMI 中)使用對象串行化,更改串行化格式也會使那些各類的版本不完全相同的系統無法順利交換數據。
幸運的是,若您謹慎對待串行化問題,在處理類時通??梢员苊獠患嫒莸母?。Java 語言提供了多種方法,可維護一個類的不同版本之間的兼容性,包括:
serialVersionUID
transient
修飾符
readObject()
和 writeObject()
writeReplace()
和 readResolve()
serialPersistentFields
對于這些解決方案來說,最大的問題就在于程序員未使用它們。當您將精力集中在修復 bug、添加特性或解決性能問題時,往往不會停下來思考您的更改對串行化造成的影響。然而串行化是一個涉及范圍極廣的問題 —— 跨越一個系統的多個不同層。幾乎所有更改都會涉及對串行化有某種影響的一個類的實例字段。這正是單元測試發揮作用的時機。在本文后續各節中,我將為您展示一些簡單的單元測試,這些單元測試能確保您不會不經意地更改可串行化類的串行格式。


|
回頁首 |
|
我能否將其串行化?
通常您編寫的第一個串行化測試就是用于驗證串行化是否可行的測試。即使一個類實現了 Serializable
,依然不能保證它能夠串行化。例如,如果一個可串行化的容器(如 ArrayList
)包含一個不可串行化的對象(如 Socket
),則在您嘗試串行化此容器時,將拋出 NotSerializableException
。
通常,對此測試,您只需在 ByteArrayOutputStream
上寫入數據。若未拋出任何異常,測試即通過。如果您愿意,還可測試一些已寫入的輸出。例如,清單 1 所示代碼片段用于測試 Jaxen 的 BaseXPath
類是否可串行化:
清單 1. 此類是否可串行化?
public void testIsSerializable()
throws JaxenException, IOException {
BaseXPath path = new BaseXPath("//foo", new DocumentNavigator());
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(out);
oos.writeObject(path);
oos.close();
assertTrue(out.toByteArray().length > 0);
}
|


|
回頁首 |
|
測試串行化形式
接下來,您想要編寫一個測試,不僅要驗證輸出得到了顯示,還要驗證輸出是正確的。您可通過兩種方式完成這一任務:
- 反串行化對象,并將其與原始對象相比較。
- 逐字節地將其與參考 .ser 文件相比較。
我通常會從第一種選擇入手,因為它還提供了一個反串行化的簡單測試,而且編碼和實現相對來說比較容易。例如,清單 2 所示代碼片段將測試 Jaxen 的 SimpleVariableContext
類是否可寫入并在之后重新讀回:
清單 2. 反串行化對象,并將其與原始對象相比較
public void testRoundTripSerialization()
throws IOException, ClassNotFoundException, UnresolvableException {
// construct test object
SimpleVariableContext original = new SimpleVariableContext();
original.setVariableValue("s", "String Value");
original.setVariableValue("x", new Double(3.1415292));
original.setVariableValue("b", Boolean.TRUE);
// serialize
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(out);
oos.writeObject(original);
oos.close();
//deserialize
byte[] pickled = out.toByteArray();
InputStream in = new ByteArrayInputStream(pickled);
ObjectInputStream ois = 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("", "");
}
|
讓我們再試一次……
在測試代碼基礎中那些此前從未測試過的部分時,幾乎總是會發現 bug,對象串行化也是這樣。在我第一次運行清單 2 中的測試時,測試失敗了,輸出結果如清單 3 所示:
清單 3. 不可串行化
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.NativeMethodAclearcase/" target="_blank" >ccessorImpl.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)
|
這表明,SimpleVariableContext
包含一個對 QualifiedName
對象的引用,QualifiedName
類未標記為 Serializable
。我為 QualifiedName
的類簽名添加了 implements Serializable
,這一次測試順利通過。
注意,此測試實際上并未驗證串行化格式是否正確 —— 只是驗證出對象能夠來回轉換。為測試正確性,您需要生成一些參考文件,以便與類的所有未來版本的輸出相比較。


|
回頁首 |
|
測試反串行化
通常,您不能依賴默認串行化格式來保持類的不同版本間的文件格式兼容性。您必須使用 serialPersistentFields
、readObject()
和 writeObject()
方法和/或 transient
修飾符,通過各種方式進行定制。如果您確實對類的串行化格式做出了不兼容的更改,應相應更改 serialVersionUID
字段,以指出您這樣做了。
正常情況下,您不會過分關注串行化對象的詳細結構。而只是關注最初使用的那種格式隨著類的發展得到了維護。一旦類基本上具備了恰當的形式,即可寫入一些類的串行化實例,并存儲在隨后可將其作為參考使用的位置處。(您很可能確實希望多多少少地考慮如何串行化才能確保足夠的靈活性,以便應對未來的發展。)
編寫串行化實例的程序是臨時代碼,只需使用一次。實際上,您根本就不應該多次運行這段代碼,因為您不希望獲得串行化格式中的任何意外更改。例如,清單 4 展示了用于串行化 Jaxen 的 SimpleVariableContext
類的程序:
清單 4. 寫入串行化實例的程序
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 out = 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();
}
}
|
您只需將一個串行化對象寫入文件 —— 而且只需一次。這是您希望保存的文件,而不是用于寫入的代碼。清單 5 展示了 Jaxen 的 SimpleVariableContext
類的兼容性測試:
清單 5. 確保文件格式未被更改
public void testSerializationFormatHasNotChanged()
throws IOException, ClassNotFoundException, UnresolvableException {
//deserialize
InputStream in = new FileInputStream("xml/simplevariablecontext.ser");
ObjectInputStream ois = 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("", "");
}
|


|
回頁首 |
|
測試不可串行性
默認情況下,類通常是可串行化的。例如,java.lang.Throwable
或 java.awt.Component
的任何子類都會從其祖先繼承可串行性。在某些情況下,這也是您希望的結果,但并非總是如此。有的時候,串行化可能會成為安全漏洞,使惡意程序員能夠在不調用構造函數或 setter 方法的情況下創建對象,從而規避了您小心翼翼地在類中構建的所有約束性檢查。
若您希望類可串行化,就需要測試它,這與您需要測試一個直接實現了 Serializable
的類相同。如果您不希望類可串行化,則應重寫 writeObject()
和 readObject()
,使兩者均拋出 NotSerializableException
,隨后您也需要對其進行測試。
此類測試的實現方法與其他任何 JUnit 異常測試相似。只需在應拋出異常的語句兩端包圍一個 try
塊即可,隨后緊接欲拋出異常的語句之后添加一條 fail()
語句。如果愿意,您還可在 catch
中作出一些關于所拋出異常的斷言。例如,清單 6 驗證了 FunctionContext
是不可串行化的:
清單 6. 測試 FunctionContext 是不可串行化的
public void testSerializeFunctionContext()
throws JaxenException, IOException {
DOMXPath xpath = new DOMXPath("/root/child");
FunctionContext context = xpath.getFunctionContext();
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream oout = new ObjectOutputStream(out);
try {
oout.writeObject(context);
fail("serialized function context");
}
catch (NotSerializableException ex) {
assertNotNull(ex.getMessage());
}
}
|
Java 5 和 JUnit 4 使異常測試更為輕松。只需在 @Test
注釋中聲明所需異常即可,如清單 7 所示:
清單 7. 帶有注釋的異常測試
@Test(expected=NotSerializableException.class) public
void testSerializeFunctionContext()
throws JaxenException, IOException {
DOMXPath xpath = new DOMXPath("/root/child");
FunctionContext context = xpath.getFunctionContext();
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream oout = new ObjectOutputStream(out);
oout.writeObject(context);
}
|


|
回頁首 |
|
結束語
串行化格式可以說是代碼基礎中最脆弱、健壯性最差的部分。有的時候,似乎只要以奇異的眼神盯著它,它就會被破壞。單元測試和測試驅動的開發這些出色的工具使您可以信心十足地管理此類脆弱系統 —— 但只有在您確實使用了這些工具時,它們才能發揮作用。
若您關注對象串行化,特別是希望為長期持久性存儲使用串行化對象時,就必須對串行化進行測試。不要假設您的 Java 代碼所做的一切都是正確的 —— 它很可能會出錯!如果您將串行化測試作為測試套件的固定部分,則維護長期兼容性就會更輕松。您花費在對象串行化單元測試上的時間將為您帶來成倍的回報,此后調試時您能節省的時間將數倍于投入時間。
原文轉自:http://www.anti-gravitydesign.com
国产97人人超碰caoprom_尤物国产在线一区手机播放_精品国产一区二区三_色天使久久综合给合久久97
|