關於測試分類技術與Testing

昨天看了這篇關於軟件分類技術的文檔,讓我這個從未涉足測試的菜鳥有了不一樣的認識,帖子轉在這裏,也希望經過的你也看看,只希望技術的提高,學的到東西。。。

來源:ZDNet China軟件技術專區

摘要

  TestNG是一種基於註釋的測試框架,通過添加諸如靈活的裝置、測試分類、參數測試和依賴方法等特性來克服JUnit的一些不足之處。此外,TestNG運行於Java 5.0(通過註釋)和Java 1.4(通過JavaDoc樣式的註釋)之上。由於TestNG可以輕鬆地將開發人員測試分類成單元、組件和系統組,因此能夠使構建時間保持在可管理的範圍內。通過使用group註釋和多重Ant任務,測試組可以不同的頻率運行於一臺工作站之上或持續集成環境中。

  本文分析了測試分類的概念,演示瞭如何將TestNG的group註釋與靈活的測試裝置具相結合,通過特定的Ant目標促進以不同頻率運行的測試。本文假設您瞭解TestNG。

TestNG組的研究

  TestNG支持一種直觀的機制,用於分組測試類和相關測試方法。在最基本的層面上,TestNG的分組特性是通過test註釋的groups參數啓用的,它可附加到類或者單個方法。從其名稱即可看出,一個類或單個方法可屬於1至n組。

  例如,下面的類包含兩個公共方法,缺省標爲測試並進一步分類爲屬於one組:

/**
* @testng.test groups="one"
*/
public class SimpleGroupedTest {
private String fixture;
/**
* @testng.before-class = "true"
*/
private void init(){
this.fixture = "Hello World";
}

public void verifyEquality(){
Assert.assertEquals("Hello World", this.fixture);
}

public void verifySame(){
String value = this.fixture;
Assert.assertSame(this.fixture, value);
}
}

  相反,下一個類定義了兩個測試方法。然而,一個方法卻屬於兩個不同的組——one和two。相應地,任何相關裝置邏輯都必須與其所需的一個組關聯。本例中,在組one或組two執行之前,必須首先將init()方法配置爲運行。

public class SimpleGroupedTwoTest {
private String fixture;
/**
* @testng.before-class = "true"
* groups = "one, two"
*/
private void init(){
this.fixture = "Hello World";
}
/**
* @testng.test groups="one, two"
*/
public void verifyEqualityAgain(){
Assert.assertEquals(this.fixture, "Hello World");
}
/**
* @testng.test groups="two"
*/
public void verifySameAgain(){
String value = this.fixture;
Assert.assertSame(value, this.fixture);
}
}

  TestNG支持以多種方式運行所需組,從通過TestNG Eclipse插件指定這些組一直到在TestNG Ant任務中列舉它們。

測試分類簡介

  要驗證正在工作的軟件,最簡單的方法之一就是執行一次構建(即編譯源代碼並執行測試);因此,長時間運行的構建是降低開發人員生產力的一項因素,這一點也不令人吃驚。不得不等待那超長的構建過程完成,幾乎沒有什麼比這更惱人的了。可與之相提並論的就是編碼過程中遇到意外的藍屏和重啓,但我們至少能夠很容易地對時間較長的構建做點什麼。

  長時間構建的原因幾乎總是測試執行這個步驟(除非是有數百萬的.java 文件)。此外,如果存在大量的設置步驟,例如配置數據庫或是部署一個 EAR 文件,執行一個測試套件的總時間傾向於變長。所以,精心設計一個測試分類策略並按照規定的時間間隔運行分類有利於獲得可管理的構建持續時間。

  然而,分類測試要求我們定義具體的分類,即細化單元測試。單元測試就像一張三層餅的一片,另兩片則是組件測試和系統測試。下一節分析了開發人員通常會編寫的不同類型的測試,諸如單元測試、組件測試和系統測試。隨後,它們將在TestNG中執行,並集成到一個Ant 構建腳本里。

單元測試定義
  單元測試驗證獨立對象的行爲;然而,由於類的耦合,單元測試也能驗證相關對象的行爲。例如,下面的單元測試驗證了對象身份,它是在TestNG中實現的,只關注一個類型:PartOfSpeechEnum。

/**
* @testng.test
*/
public class PartOfSpeechEnumTest {

public void verifyNotEquals() throws Exception{
assert PartOfSpeechEnum.ADJECTIVE !=
PartOfSpeechEnum.NOUN: "NOUN == ADJECTIVE?";
}

public void verifyEquals() throws Exception{
assert PartOfSpeechEnum.ADJECTIVE ==
PartOfSpeechEnum.ADJECTIVE "ADJECTIVE != ADJECTIVE";
}
}

  有些時候,一個單元測試會驗證多個對象的行爲。然而,這些對象很少有其他的外部依賴項。例如,下面的測試驗證了兩個對象:Dependency和DependencyFinder。

//imports removed...

public class DependencyFinderTest {
private DependencyFinder finder;
/**
* @testng.test
*/
public void verifyFindDependencies() throws Exception{
String targetClss = "test.com.sedona.frmwrk.dep.Finder";
Filter[] filtr = new Filter[] {
new RegexPackageFilter("java|org")};
Dependency[] deps =
finder.findDependencies(targetClss, filtr);
Assert.assertNotNull(deps, "deps was null");
Assert.assertEquals(deps.length, 5, "should be 5 large");
}
/**
* @testng.before-class = "true"
*/
protected void init() throws Exception {
this.finder = new DependencyFinder();
}
}
  要牢記的一個要點就是:單元測試不依靠外部依賴項,如數據庫。數據庫會增加設置和運行測試的時間。單元測試沒有配置成本(就時間來度量),運行它的資源消耗可忽略不計。

  單元測試運行很快,所以只要運行了一次構建,就應該運行單元測試,包括在持續集成環境中也是如此。在持續集成環境中,如果源儲存庫(如CVS)發生變更,通常就要運行構建。

組件測試

  組件測試有幾個別名,如子系統測試或集成測試。不管用哪個術語,這樣的測試驗證了應用程序的若干部分,甚至還可能需要一個完全安裝的系統或一組更有限的外部依賴項,如數據庫、文件系統、或網絡端點。實質上,這些測試驗證了不同組件交互以產生預期組合行爲的過程。

  典型的組件測試需要一個種子數據庫(seeded database);此外,測試本身可能要跨架構邊界來驗證行爲。由於組件測試要處理大量的代碼,所以實現了更廣泛的代碼覆蓋範圍;但運行此類測試要比運行單元測試佔用更長時間。

  因爲組件測試有相關成本——依賴項必須就位並被配置好,所以不該在每次執行構建時運行,而應以規定的時間間隔運行。記住,這些測試本身可能只需幾秒鐘,但更多的組件測試被添加到套件中時,整個測試時間就增加了,而且往往增加的非常快。

  例如,下面的組件測試用DbUnit 播種一個底層數據庫。這一測試用例中,設置本身所用的時間比大多數單元測試的運行時間都要長。

//imports removed...

public class WordDAOImplTest {
private WordDAO dao = null;
/**
* @testng.before-method = "true"
*/
private void setUp() throws Exception {
final ApplicationContext context =
new ClassPathXmlApplicationContext(
"spring-config.xml");
this.dao = (WordDAO) context.getBean("wordDAO");
}
/**
* @testng.before-class = "true"
*/
public void oneTimeSetUp() throws Exception{
final IDatabaseConnection conn =
this.getConnection();
final IDataSet data = this.getDataSet();
try{
DatabaseOperation.CLEAN_INSERT.execute(conn, data);
}finally{
conn.close();
}
}
/**
* @testng.test
*/
public void createWord() {
final IWord word = new Word();
word.setSpelling("pabulum");
word.setPartOfSpeech(
PartOfSpeechEnum.NOUN.getPartOfSpeech());
final IDefinition defOne = new Definition();
defOne.setDefinition("food");
defOne.setWord(word);
final Set defs = new HashSet();
defs.add(defOne);
word.setDefinitions(defs);
try{
this.dao.createWord(word);
}catch(CreateException e){
Assert.fail("CreateException was thrown");
}
}

private IDataSet getDataSet()
throws IOException, DataSetException {
return new FlatXmlDataSet(
new File("test/conf/words-seed.xml"));
}

private IDatabaseConnection getConnection()
throws ClassNotFoundException, SQLException {
final Class driverClass =
Class.forName("org.gjt.mm.mysql.Driver");
final Connection jdbcConnection = DriverManager.
getConnection("jdbc:mysql://localhost/words",
"words", "words");
return new DatabaseConnection(jdbcConnection);
}
}

  但是,組件測試不總是依靠數據庫。例如依靠文件系統創建一個耦合,這會增加配置的複雜性,在某些情況下,也會增加所需時間。舉個例子,下面的組件測試使用XMLUnit 驗證所生成的XML。注意這個測試依靠文件系統路徑來比較兩個XML文檔。

//imports removed...

public class BatchDepXMLReportValidationTest {
/**
* @testng.before-class = "true"
*/
protected void configure() 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(){
return new Filter[] {
new RegexPackageFilter("java|org"),
new SimplePackageFilter("junit.")
};
}

private Dependency[] getDependencies(){
return new Dependency[] {
new Dependency("com.vanward.resource.XMLizable"),
new Dependency("com.vanward.xml.Element")
};
}
/**
* @testng.test
*/
public void assertToXML() throws Exception{
BatchDependencyXMLReport report =
new BatchDependencyXMLReport(
new Date(9000000), this.getFilters());

report.addTargetAndDependencies(
"com.vanward.test.MyTest", this.getDependencies());
report.addTargetAndDependencies(
"com.xom.xml.Test", this.getDependencies());

Diff diff = new Diff(new FileReader(
new File("./test/conf/report-control.xml")),
new StringReader(report.toXML()));

Assert.assertTrue(
diff.identical(),"XML was not identical");
}
}

  雖然不應在每次運行構建時執行組件測試,但在將代碼提交到儲存庫之前運行組件測試卻是一個好主意。在持續集成環境中,時常運行它很可能是個好主意,比如每小時一次。

系統測試

  系統測試處理一個完整的軟件應用程序,驗證外部接口,如Web頁面、Web 服務端點、GUI、按照設計端到端地完成工作。因爲系統測試處理的是整個系統,所以往往在開發的後期創建。除增加了設置和配置時間之外,系統測試還傾向於具有更長的執行時間。

  例如,以下測試使用 jWebUnit 測試一個Web站點的登陸功能。請注意,這個測試中做了許多假設,如可用URL、實際上擁有有效賬戶的“tst”用戶、未提及交易歷史記錄等。這些隱含的依賴項通常需要在測試運行前完成一個配置步驟。

public class LoginTest {
private WebTester tester;
/**
* @testng.before-class = "true"
*/
protected void init() throws Exception {
this.tester = new WebTester();
this.tester.getTestContext().
setBaseUrl("http://stocktrader.com");
}
/**
* @testng.test
*/
public void verifyLogIn() {
this.tester.beginAt("/");
this.tester.setFormElement("user", "tst");
this.tester.setFormElement("psswd", "t@t");
this.tester.submit();
this.tester.assertTextPresent("Logged in as tst");
}
/**
* @testng.test dependsOnMethods = "verifyLogIn"
*/
public void verifyAccountInfo() {
this.tester.clickLinkWithText("History", 0);
this.tester.assertTextPresent(".00, sold");
}
}

  開發人員應當以需求爲基礎,在一個持續集成環境中本地運行這些測試。每晚執行測試是個不錯的策略(如果測試能夠實現自動運行)。更頻繁地運行測試可能消耗大量系統資源,特別是在較大的環境中。但有了合理的硬件規劃和更加完善的自動化,團隊就能更頻繁地運行這些測試。

實現TestNG分類

  將TestNG測試分成三類就像使用上面所講的group註釋一樣簡單。通常不會有一個具有跨不同測試粒度的方法的測試類,所以在類的級別上,可以有效地應用標記。

  例如,下面的類被標記爲屬於unit測試組。請注意,HierarchyBuilderTest是怎樣依靠HierarchyBuilder類型來驗證Hierarchy類的行爲的。由於這一關係結束於不依賴文件系統或數據庫的HierarchyBuilder,因此實際上可以把它看作單元測試:

import org.testng.Assert;

/**
* @testng.test groups="unit"
*/
public class HierarchyBuilderTest {
private Hierarchy hier;
/**
* @testng.before-class = "true" groups="unit"
*/
private void init() throws Exception{
this.hier =
HierarchyBuilder.buildHierarchy(Vector.class);
}

public void validateIsntNull() throws Exception{
Assert.assertNotNull(this.hier,
"should be something!");
}
/**
* @testng.test dependsOnMethods="validateIsntNull"
*/
public void validateValues() throws Exception{
Assert.assertEquals(
this.hier.getHierarchyClassNames().length,
2, "should be 2");
}
}

  類似地,系統測試中單獨的方法可以用system組標識來標記,示範如下:

public class LoginTest {
private WebTester tester;
/**
* @testng.before-class = "true" groups="system"
*/
protected void init() throws Exception {
this.tester = new WebTester();
this.tester.getTestContext().
setBaseUrl("http://acme.com:8080/ppo/");
}
/**
* @testng.test groups="system"
*/
public void verifyView() {
this.tester.beginAt("/");
this.tester.setFormElement("isbn", "900930390");
this.tester.submit();
this.tester.assertTextPresent("Book in stock");
}
}

運行分類測試

  將代碼簽入內容管理系統之前,通過構建或像IDE那樣的環境進行本地測試是極爲重要的。通過TestNG Eclipse插件運行分類測試非常簡單。如圖1所示,通過在TestNG Create, manage, and run configurations 對話框中選擇組選項,可用組的列表將出現,它帶有複選框,這使選擇變得更輕鬆。選好所需的一個或多個組以後,單擊Run按鈕,然後就看着綠色進度條一直向前!

  通過構建,運行分類TestNG 測試將轉變成爲各組定義恰當的 Ant 目標。例如,爲了運行屬於組件組的所有測試,用指定的component組定義TestNG Ant 任務。

<target name="testng-component"
depends="testng-init">
<mkdir dir="$"/>

<testng groups="component"
outputDir="$"
sourceDir="$"
classpath="$; $">
<classfileset dir="$">
<include name="**/*Test.java"/>
</classfileset>
<classpath>
<path refid="build.classpath"/>
</classpath>
</testng>

  因此,採用這一策略,至少可以創建4個目標。其中3個分別對應單元、組件和系統測試,最後一個目標則能運行所有這3種測試。

結束語

  TestNG使測試分類變得相當容易,這很可能是TestNG的最激動人心的優點之一。此外,TestNG的group註釋還有助於把測試放到其他分類中,如成批測試、驗收測試,甚至是性能測試。事實上,這一特性似乎已對最新版本的JUnit產生了影響,JUnit也在規劃支持測試組! 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章