在測試用例中通過setUp()、tearDown()創(chuàng)建測試固件,只能使這個測試固件在單個測試用例的不同測試方法中共用,如果有多個測試用例都需要使用相同的測試固件,就需要將測試固件抽取到一個獨立的類中。JBuilder提供了3個預定義的測試固件類,它們分別是:
·JDBC測試固件(JDBC Fixture):用于獲取數(shù)據(jù)庫連接的測試固件,用戶僅需要通過設置一些數(shù)據(jù)庫信息,就可以用方便的方法獲取數(shù)據(jù)連接。
·JNDI 測試固件(JNDI Fixture):用于模擬從JDNI環(huán)境中獲取對象的測試固件。
·比較測試固件(Comparision Fixture):將測試輸出到外部文件中,以便第二次測試時進行比較。
·自定義測試固件(Custom Fixture):用戶自定義的測試固件。
如果對JUnit的框架結(jié)構(gòu)非常了解,也許這些JBuilder擴展的測試固件對你來說意義并不大,它們無非是構(gòu)建一些常見的測試環(huán)境罷了,你完全可以自己編寫。在本節(jié)里,我們介紹兩個測試固件。
JDBC測試固件
如果你的工程中已經(jīng)有一個獲取數(shù)據(jù)連接的公共類,你也有必要構(gòu)建一個JDBC測試固件,因為JDBC測試固件不但可以直接通過創(chuàng)建測試用例的向?qū)е苯又付?,此外,JDBC測試固件還提供了許多面向測試的方法。
為了創(chuàng)建JDBC測試固件,我們先創(chuàng)建一個JDataStore的數(shù)據(jù)庫,其數(shù)據(jù)文件位于/db/hr.jds,這個數(shù)據(jù)庫的用戶名和密碼是:sysdba/123456。hr.jds數(shù)據(jù)庫中有一張EMPLOYEE的表,其結(jié)構(gòu)如下所示:
圖 錯誤!文檔中沒有指定樣式的文字。EMPLOYEE表的數(shù)據(jù)
EMPLOYEE有3個字段,分別是ID、NAME和AGE,分別是String、String和int類型,并按上圖所示填入3條記錄。
為了演示JDBC測試固件的具體使用,我們設計兩個業(yè)務類:Employee和EmployeeDAO,爾后用JDBC測試固件為測試EmployeeDAO提供數(shù)據(jù)連接。這兩個類的代碼如下:
代碼清單 錯誤!文檔中沒有指定樣式的文字。Employee.java類
1. package chapter25.db;
2. public class Employee
3. {
4. private String id;
5. private String name;
6. private int age;
7. public Employee(String id, String name, int age) {
8. this.id = id;
9. this.name = name;
10. this.age = age;
11. }
12. public String getId() {
13. return id;
14. }
15. public String getName() {
16. return name;
17. }
18. public int getAge() {
19. return age;
20. }
21. public boolean equals(Object o) {
22. if (o instanceof Employee) {
23. Employee e1 = (Employee) o;
24. return id.equals(e1.getId()) name.equals(e1.getName()) age == e1.getAge();
25. } else {
26. return false;
27. }
28. }
29. }
Employee類用于描述EMPLOYEE表的一條記錄,該類訪問數(shù)據(jù)庫的EmployeeDAO代碼如下所示:
代碼清單 錯誤!文檔中沒有指定樣式的文字。EmployeeDAO.java類
1. package chapter25.db;
2. import java.sql.*;
3. public class EmployeeDAO
4. {
5. private Connection conn;
6. public EmployeeDAO(Connection conn) {
7. this.conn = conn;
8. }
9. public Employee findById(String id) throws SQLException
10. {
11. String sqlStr = "select * from employee where id ='"+id+"'";
12. Statement stat = conn.createStatement();
13. ResultSet rs = stat.executeQuery(sqlStr);
14. if (rs.next()) {
15. return new Employee(id,rs.getString("name"),rs.getInt("age"));
16. }else{
17. return null;
18. }
19. }
20. }
為了節(jié)省篇幅,我們僅提供一個訪問數(shù)據(jù)庫的訪問方法:findById(),即通過id查找Employee對象。
下面,我們利用JBuilder向?qū)?chuàng)建一個JDBC測試固件:
1. File->New...->Test->在Test頁中,雙擊JDBC Fixture圖標,啟動創(chuàng)建向?qū)?,其對話框如下所示?
圖 錯誤!文檔中沒有指定樣式的文字。指定JDBC測試固件類名
在Class name中為JDBC測試固件指定類名:HrJdbcFixture,接受其他的默認設置,按Next到下一步。
2.設置連接數(shù)據(jù)庫的信息。
在這步里,JBuilder提供了大部分數(shù)據(jù)庫驅(qū)動程序的選擇和連接信息設置,其對話框如下所示:
圖 錯誤!文檔中沒有指定樣式的文字。指定數(shù)據(jù)庫連接信息 ·Driver:選擇borland.databstore.jdbc.DataStoreDriver類。JDBC測試固件提供了對大多數(shù)數(shù)據(jù)庫的支持。其中下拉框中暫不可用的數(shù)據(jù)庫驅(qū)動器類顯示為紅色,你可以通過配置工程擴展類庫使它們可用。
·URL:點擊其后的…按鈕,彈出Create URL for DataStore對話框,如下圖所示:
圖 錯誤!文檔中沒有指定樣式的文字。構(gòu)造DataStore數(shù)據(jù)連接URL的對話框
該對話框的設置內(nèi)容會隨著數(shù)據(jù)庫類型的不同而變化。對于JDataStore數(shù)據(jù)庫,該對話框提供了兩個選項,如果數(shù)據(jù)庫文件放在本機上時用第一個設置項,否則用第二個設置項。我們選擇第一種選項,點擊其后的…按鈕,導航到/db/hr.jds并選擇之,按OK返回向?qū)е鲗υ捒虼翱凇?
·User name:sysdba。
·Password:123456。
按對話框下的Test Connection測試連接,應該會返回一個Success信息報告連接測試成功。按Finish創(chuàng)建JDBC 測試固件,其代碼如下所示:
代碼清單 錯誤!文檔中沒有指定樣式的文字。HrJdbcFixture.java
1. package fixture;
2. import java.sql.*;
3. import java.io.*;
4. import com.borland.jbuilder.unittest.JdbcFixture;
5. public class HrJdbcFixture
6. extends JdbcFixture
7. {
8. public HrJdbcFixture(Object obj) {
9. super();
10. super.setUrl("jdbc:borland:dslocal:D:/JTJB2005/chapter25/db/hr.jds");
11. super.setDriver("com.borland.datastore.jdbc.DataStoreDriver");
12. super.setUsername("sysdba");
13. super.setPassword("123456");
14. }
15.
16. public void setUp() {
17. super.setUp();
18. }
19.
20. public void tearDown() {
21. super.tearDown();
22. }
23. }
JDBC測試固件承繼了com.borland.jbuilder.unittest.JdbcFixture,這個類的重要方法包括:
·dumpResultSet():將一個ResultSet導到一個Writer中,該方法接受兩個參數(shù),一個是ResultSet另一個是Writer。
·getConnection():獲取一個數(shù)據(jù)連接。
·runSqlBuffer():執(zhí)行緩存于StringBuffer對象中的SQL語句。
·runSqlFile():執(zhí)行保存在文件中的SQL語句,通過入?yún)⒅付⊿QL文件的地址。
·setDriver():設置JDBC驅(qū)動器。
·setUrl():設置數(shù)據(jù)連接的URL。
·setUsername():設置用戶名。
·setPassword():設置密碼。
提示:
通過向?qū)?chuàng)建JDataStore的JDBC測試固件,雖然可以直接在對話框中選擇com.borland.datastore.jdbc.DataStoreDriver驅(qū)動器,但運行這個JDBC測試固件時,JBuilder卻報這樣的錯誤信息:java.lang.ClassNotFoundException: com.borland.datastore.jdbc.DataStoreDriver。原來是JBuilder通過向?qū)?chuàng)建JDBC測試固件時,并沒有直接將驅(qū)動器類加載到工程類庫中,所以你需要手工通過Project->Project Properties...->Paths中,將JBuilder類庫中名為JDataStore類庫項加到工程類庫中。
由于EMPLOYEE表的數(shù)據(jù)可能會隨著測試的進行不斷更改,這樣在測試時測試規(guī)則就很難制定,因為規(guī)則的制定必須基于一個假設的環(huán)境。舉個例子,我們現(xiàn)在要測試findById()方法,就必須知道EMPLOYEE表中有哪些數(shù)據(jù),所以在測試開始時就必須創(chuàng)建好一些特定的數(shù)據(jù)。由于JDBC固件可以執(zhí)行保存在外部文件中的SQL,所以我們創(chuàng)建一個insert.sql文件,將其放置在/db/insert.sql下,文件的內(nèi)容如下:
delete from employee;insert into employee values('0004','大山',23);insert into employee values('0005','英華',30);insert into employee values('0006','柯明',31);
運行這個SQL語句時,先清空EMPLOYEE表中的數(shù)據(jù),然后再插入3條特定的記錄。下面,我們來創(chuàng)建應用JDBC測試固件的TestEmployeeDAO測試用例類。
1.在編輯器中激活EmployeeDAO。
2.File->New...->Test->雙擊Test Case圖標啟動創(chuàng)建測試用例的向?qū)В谙驅(qū)У?、2步為EmployeeDAO的創(chuàng)建一個名為TestEmployeeDAO的測試用例,這個測試用例對EmployeeDAO的findById()方法進行功能測試。
3.在向?qū)У牡?步選擇測試固件,在向?qū)υ捒蛑形覀兦懊鎰?chuàng)建的HrJdbcFixture已經(jīng)出現(xiàn)在列表中。你也可以通過對話框的Add...和Remove選擇不同的測試固件。
圖 錯誤!文檔中沒有指定樣式的文字。選擇測試固件 按Finish直接創(chuàng)建TestEmployeeDAO的測試用例,其代碼如下所示:
代碼清單 錯誤!文檔中沒有指定樣式的文字。TestEmployeeDAO.java,向?qū)?chuàng)建的測試用例類
1. package chapter25.db;
2. import junit.framework.*;
3. import fixture.*;
4. import java.sql.*;
5. public class TestEmployeeDAO extends TestCase {
6. private EmployeeDAO employeeDAO = null;
7. HrJdbcFixture hrJdbcFixture;
8. protected void setUp() throws Exception {
9. super.setUp();
10. /**@todo verify the constructors*/
11. employeeDAO = new EmployeeDAO(null);
12. hrJdbcFixture = new HrJdbcFixture(this);
13. hrJdbcFixture.setUp();
14. }
15.
16. protected void tearDown() throws Exception {
17. employeeDAO = null;
18. hrJdbcFixture.tearDown();
19. hrJdbcFixture = null;
20. super.tearDown();
21. }
22.
23. public void testFindById() throws SQLException {
24. String id = ";
25. Employee expectedReturn = null;
26. Employee actualReturn = employeeDAO.findById(id);
27. assertEquals("return value", expectedReturn, actualReturn);
28. /**@todo fill in the test code*/
29. }
30. }
測試用例在setUp()方法中實例化HrJdbcFixture對象,并調(diào)用其setUp()方法初始化環(huán)境。只有JDBC測試固件的setUp()方法執(zhí)行后(第13),才可調(diào)用JDBC測試固件的其他方法,如getConnection()等,所以JDBC測試固件的setUp()是其初始化方法。下面我們對這個TestEmployeeDAO進行改造,改造的代碼如下粗體代碼所示:
代碼清單 錯誤!文檔中沒有指定樣式的文字。改造后的TestEmployeeDAO類
1. …
2. public class TestEmployeeDAO extends TestCase {
3. …
4. protected void setUp() throws Exception {
5. super.setUp();
6. hrJdbcFixture = new HrJdbcFixture(this);
7. hrJdbcFixture.setUp();
8. employeeDAO = new EmployeeDAO(hrJdbcFixture.getConnection());
9. hrJdbcFixture.runSqlFile("D:/JTJB2005/chapter25/db/insert.sql",true);
10. }
11. …
12. public void testFindById() throws SQLException {
13. Employee expectEmp = new Employee("0004","大山",23);
14. Employee realEmp = employeeDAO.findById("0004");
15. assertNotNull(realEmp);
16. assertEquals(expectEmp,realEmp);
17. }
18. }
因為JDBC測試固件需要在setUp()方法調(diào)用后,其他方法才可用,所以在TestEmployeeDAO的setUp()方法中,我們將EmployeeDAO的實例化方法移到后面,以便實例化EmployeeDAO時可以通過hrJdbcFixture.getConnection()獲取數(shù)據(jù)連接(第8行)。在第9行,執(zhí)行insert.sql文件,清除表中原來的數(shù)據(jù)并插入3行測試數(shù)據(jù)。
在第13~14行,通過EmployeeDAO的findById()方法查找返回ID為0004的Employee對象,在第15~16行設定兩個測試規(guī)則。
運行這個帶JDBC測試固件的測試用例,HrJbdcFixture測試固件先準備好測試環(huán)境,然后再執(zhí)行TestEmployeeDAO的testFindById()測試方法。
比較測試固件
比較固件用于記錄下當前的測試記錄,以便和下一次的輸出比較。比較固件類繼承于com.borland.jbuilder.unittest.TestRecorder,而TestRecorder類繼承java.io.Writer。所以如果在測試時,需要用Writer輸出信息就可以考慮使用比較固件了,它提供了許多易用的輸出信息的方法。你可以通過向?qū)韯?chuàng)建比較固件。
TestRecorder共有 4個記錄模式的常量,它們分別是:
·UPDATE:比較固件將當前輸出信息和已存在的信息文件相比較,如果文件沒有存在則新創(chuàng)建一個文件,記錄輸出信息。
·COMPARE:比較固件將當前輸出的信息和已經(jīng)存在的信息比較。
·RECORD:比較固件記錄當前輸出的信息,如果原來已經(jīng)有輸出文件存在,覆蓋之。
·OFF:關閉比較固件的功能。
注意:
在創(chuàng)建記錄文件后,假設你更改了測試用例或測試套件,需要重新初始化這個輸出文件:將TestRecorder的輸出模式設置為RECORD,創(chuàng)建文件后再將其調(diào)整為UPDATE。輸出的數(shù)據(jù)文件是二進制文件,放在和源程序文件相同的目錄下且和測試用例類同名。
下面是測試固件常用的方法,介紹如下:
·boolean print(String s)
用TestRecorder打印一個字符串,如果模式為RECORD,且這個字符串和原來記錄的不一致,則返回false。你可以設定這樣的測試規(guī)則:
assertTrue(recorder.print(result.toString())
·boolean println(String s)
和print()相似,只不過添加一個換行。
·boolean compareObject(Object o)
調(diào)用傳入對象的equals()方法和用前面用recordObject()記錄的對象進行比較。
·boolean recordObject()
記錄一個對象,以便后面調(diào)用compareObject()方法進行比較。
下面,我們創(chuàng)建一個比較固件,并應用這個比較固件為Employee類創(chuàng)建一個測試用例。
1.File->New...->Test->在Test頁中,雙擊Comparision Fixture圖標啟動創(chuàng)建比較固件類的向?qū)В鋵υ捒蛉缦滤荆?/P>
圖 錯誤!文檔中沒有指定樣式的文字。指定比較固件名及屬性
·Class name:測試固件類名,接受默認的ComparisionFixture1。
·Echo output to console:測試固件將信息同時輸出到測試運行器的控制臺上。
·Verbose output:測試固件將輸出詳細的信息。
此外,Save comparision data in this directory指定比較輸出信息文件的存放位置,可以通過其后的…按鈕更改,這里我們接受默認的設置。按OK直接創(chuàng)建比較固件類,你代碼如下所示:
代碼清單 錯誤!文檔中沒有指定樣式的文字。?15 ComparisonFixture1.java,向?qū)?chuàng)建的測試固件類
1. package fixture;
2. import com.borland.jbuilder.unittest.TestRecorder;
3. public class ComparisonFixture1 extends TestRecorder
4. {
5.
6. public ComparisonFixture1(Object obj) {
7. super();
8. super.setMode(UPDATE);
9. super.setVerbose(true);
10. super.setEcho(true);
11. String fileName = super.constructFilename("D:/JTJB2005/chapter25/test",obj);
12. super.setOutputFile(fileName);
13. }
14.
15. public void setUp() {
16. }
17.
18. public void tearDown() {
19. }
20. }
第8行將模式設置為UPDATE,而第9,10行對輸出屬性作設置。第11~12行指定輸出文件的目錄。
2.創(chuàng)建TestEmployee測試用例類。
File->New...->Test->在Test頁,雙擊Test Case圖標啟動創(chuàng)建測試用例向?qū)?,為Employee類和構(gòu)造函數(shù)創(chuàng)建TestEmployee測試用例類。在向?qū)У牡?步,你將看到如下的對話框:
圖 錯誤!文檔中沒有指定樣式的文字。選擇測試Employee類的構(gòu)造函數(shù)
點擊Next一直到向?qū)У牡?步:
列表中列出了工程的所有的測試固件,選擇HrJdbcFixture,點擊Remove刪除這個固件,只留下ComparisionFixture1的固件,點擊Finish直接創(chuàng)建TestEmployee測試用例類的代碼框架,在代碼框架基礎上利用比較固件對Employee進行測試,其最終代碼如下所示:
代碼清單 錯誤!文檔中沒有指定樣式的文字。應用比較固件的測試用例
1. package chapter25.db;
2.
3. import junit.framework.*;
4. import fixture.*;
5.
6. public class TestEmployee extends TestCase {
7. private Employee employee = null;
8. ComparisonFixture1 comparisonFixture1;
9.
10. protected void setUp() throws Exception {
11. super.setUp();
12. employee = new Employee("0004", "王五", 23);
13. comparisonFixture1 = new ComparisonFixture1(this);
14. comparisonFixture1.setUp();
15. }
16.
17. protected void tearDown() throws Exception {
18. employee = null;
19. comparisonFixture1.tearDown();
20. comparisonFixture1 = null;
21. super.tearDown();
22. }
23.
24. public void testEmployee() {
25. String id = "0004";
26. String name = "王五";
27. int age = 23;
28. comparisonFixture1.print(employee.getId());
29. comparisonFixture1.recordObject(employee);
30. employee = new Employee(id, name, age);
31. assertTrue(comparisonFixture1.print(employee.getId()));
32. assertTrue(comparisonFixture1.compareObject(employee));
33. }
34. }
在第12行實例化一個Employee對象,在第28行保持并打印出原employee對象的id值,在第31行進行比較;第29行記錄原employee對象,在第31行進行兩對象的比較。
運行TestEmployee類,在測試運行器的測試輸出標簽頁中,你將可以看到輸出的信息,在測試用例所在的文件夾下將創(chuàng)建一個無后綴名的輸出文件Employee。