清單 2 中的測(cè)試有其他一些重大的缺陷 —— 而不僅僅是硬編碼 String 比較那么簡單。首先,測(cè)試并不真正可讀。第二,它驚人的脆弱;一旦 XML 文檔的格式改變(包括添加空格),與其嘗試修復(fù) String 本身,還不如粘貼進(jìn)一個(gè)新的文檔副本。后,測(cè)試的本性會(huì)迫使您必須應(yīng)付 Date 方面,雖然您并不想如此。
若想確保文檔中第二個(gè) Class 元素的 name 值是 com.acme.web.Account 又該如何呢?當(dāng)然,您可以使用常規(guī)表達(dá)式或 String 搜索,但所需的工作量太大。這樣看來,通過一個(gè)解析框架來操縱此 DOM 不是更有意義么?
XMLUnit 能否用于 TestNG?
XMLUnit 是一個(gè) JUnit 擴(kuò)展,但這并不意味著不能在 TestNG 使用它。只要它具有 API 而且此 API 支持委托同時(shí)不基于修飾器,那么您可以將幾乎任何框架整合進(jìn) TestNG。
用 XMLUnit 進(jìn)行測(cè)試
當(dāng)您感覺自己為完成一項(xiàng)任務(wù)而努力過了頭,您可以想想解決此問題是否還有更容易的捷徑可尋。如果所要解決的問題涉及的是編程式地驗(yàn)證 XML 文檔,那么所應(yīng)想到的解決方案是 XMLUnit。
XMLUnit 是一種 JUnit 擴(kuò)展框架,有助于開發(fā)人員測(cè)試 XML 文檔。實(shí)際上,XMLUnit 是一種真正的 XML 測(cè)試的“多面手”:可以使用它來驗(yàn)證 XML 文檔的結(jié)構(gòu)、內(nèi)容甚至該文檔的指定部分。
簡單的做法是使用 XMLUnit 在邏輯上對(duì)比運(yùn)行時(shí) XML 文檔和預(yù)定義的有效控制文件。本質(zhì)上講,這是一種差異測(cè)試:假定一個(gè) XML 文檔是正確的,那么此應(yīng)用程序在運(yùn)行時(shí)是否會(huì)生成同樣的東西?它是相對(duì)簡單的一種測(cè)試,但也可以使用它來驗(yàn)證 XML 文檔的結(jié)構(gòu)和內(nèi)容。也可以通過 XPath 的一點(diǎn)幫助來驗(yàn)證特定內(nèi)容。
委托而非繼承
首要原則是盡量避免測(cè)試用例繼承。許多 JUnit 擴(kuò)展框架,包括 XMLUnit,都提供可以通過繼承得到的專門的測(cè)試用例來協(xié)助測(cè)試某一特定的架構(gòu)。從框架繼承來的測(cè)試用例都缺乏靈活性,這是 Java 平臺(tái)的單一繼承的范型所致。更多的時(shí)候,這些相同的 JUnit 擴(kuò)展框架提供一個(gè)委托 API,此 API 可以更易于組合不同的框架,而無需采用嚴(yán)格的繼承結(jié)構(gòu)。
驗(yàn)證內(nèi)容
可以通過委托或繼承的方式使用 XMLUnit。作為佳策略,我建議避免測(cè)試用例繼承。另一方面,從 XMLUnit 的 XMLTestCase 繼承確實(shí)可以提供一些方便的聲明方法(這些方法不是靜態(tài) 的,因而也不能像 JUnit的 TestCase 聲明一樣被靜態(tài)引用)。
不管您如何選擇使用 XMLUnit,都必須實(shí)例化 XMLUnit 的解析器。您可以通過 System.setProperty 調(diào)用實(shí)例化它們,也可以通過 XMLUnit 核心類上的一些方便的 static 方法對(duì)它們進(jìn)行實(shí)例化。
一旦用所需要的不同的解析器實(shí)例化 XMLUnit 之后,可以使用 Diff 類,這是從邏輯上對(duì)比兩個(gè) XML 文檔所需的中心機(jī)制。在清單 3 中,我利用 XMLUnit 對(duì) >testToXML test 做了一些改進(jìn):
清單 3. 改進(jìn)后的 testToXML 測(cè)試
public class XMLReportTest extends TestCase {
protected void setUp() throws Exception {
XMLUnit.setControlParser(
"org.apache.xerces.jaxp.DocumentBuilderFactoryImpl");
XMLUnit.setTestParser(
"org.apache.xerces.jaxp.DocumentBuilderFactoryImpl");
XMLUnit.setSAXParserFactory(
"org.apache.xerces.jaxp.SAXParserFactoryImpl");
XMLUnit.setIgnoreWhitespace(true);
}
private Filter[] getFilters(){
Filter[] fltrs = new Filter[2];
fltrs[0] = new RegexPackageFilter("java|org");
fltrs[1] = new SimplePackageFilter("net.");
return fltrs;
}
private Dependency[] getDependencies(){
Dependency[] deps = new Dependency[2];
deps[0] = new Dependency("com.acme.resource.Configuration");
deps[1] = new Dependency("com.acme.xml.Document");
return deps;
}
public void testToXML() {
BatchDependencyXMLReport report =
new BatchDependencyXMLReport(new Date(1165203021718L),
this.getFilters());
report.addTargetAndDependencies(
"com.acme.web.Widget", this.getDependencies());
report.addTargetAndDependencies(
"com.acme.web.Account", this.getDependencies());
Diff diff = new Diff(new FileReader(
new File("./test/conf/report-control.xml")),
new StringReader(report.toXML()));
assertTrue("XML was not identical", diff.identical());
}
}