最近利用些業余時間自己編寫了一個小型自動化測試框架,在設計過程中自己也漸漸對自動化框架的作用有了些新的認識,希望能和大家分享一下。
其實設計這個框架最初的動機是來源于工作中的一個任務——同事讓我維護一個很久以前編寫的“自動化腳本”,難度不大,只是一串處理和過程,看懂代碼以后只需要修改個別邏輯和參數即可。但后來我想了想,這樣純粹只有過程的腳本,在開發測試時用來當做小工具用不錯,但一旦需要建立穩定的自動化測試機制,有大量功能點和測試數據的時候就會顯得力不從心。一個功能點對應一個腳本,新增功能點甚至測試數據都需要對應增加腳本,開發維護的成本則會非常高。
后來我自己嘗試去做了一個小型的自動化測試框架,雖然花費了不少時間才實現了原來腳本的內容,但是磨刀不誤砍柴工,有了框架,接下來新增功能點的開發工作量大大減輕。自己在設計該框架時也基本上是摸著石頭過河,一邊思考自動化框架究竟需要做什么,一邊也參考一些開源自動化框架例如Ruby Watir的設計方式,以下便是我總結的一些經驗和心得。
一、測試腳本與測試框架脫離
我開頭提到的那個“測試腳本”,從程序啟動,測試動作執行,測試結果反饋都一手包干,例如對于A1和A2兩個相似的功能點,其測試代碼如下:
功能A-1的腳本:A1Test.java
public static void main(String[] args) throws Exception {
// A1測試邏輯實現
……
Class.forName("oracle.jdbc.driver.OracleDriver");
String url = "jdbc:oracle:thin:@localhost:1521:cui";
Connection conn = DriverManager.getConnection(url, "cui", "cui");
……
}
功能A-2的腳本:A2Test.java
public static void main(String[] args) throws Exception {
// A2測試邏輯實現
……
Connection conn = null;
try {
Class.forName("oracle.jdbc.driver.OracleDriver");
String connectionUrl = "jdbc:oracle:thin:@localhost:1521:cui";
conn = DriverManager.getConnection(connectionUrl, "cui", "cui");
……
}
這樣一來A1和A2的測試腳本都可以獨立運行,也不需要什么自動化框架,但是原本相似的A1和A2功能,卻因為這樣的架構要寫兩次代碼,如果TestA1和TestA2由兩個不同的程序員編寫,那么即便是如上面所示連接一個相同的數據庫,每個人需要自己寫出實現方式,且都可能有不同的代碼風格,這樣極大增加了測試代碼的編寫和維護成本。
對于測試腳本而言,僅僅只需要負責測試邏輯本身,不應該負責諸如腳本啟動,管理的功能,同時降低提升測試腳本編寫和維護成本,一些公用的方法例如數據庫連接等,都最好封裝成方法放在測試框架中,然后供測試腳本調用。
我在編寫自動化腳本時,將每個測試功能點腳本作為一個Scenario類供測試框架調用的,測試框架可以根據用戶輸入或者任務設定選擇執行哪些腳本。
TestFramework.excute(Scenario userInputScenarioName);
同時,對于如數據庫連接查詢這樣的操作,也都做了封裝,在每個Scenario腳本里,編寫者可以通過1行代碼就能查詢想要的數據:
在配置文件中填入數據庫信息:
#別名 db_dev #類型 Oracle #IP 127.0.0.1 #端口 1521 #數據庫名 db1 #用戶名 cui #密碼 cui
在腳本文件中就可以這樣來訪問數據庫:
String id = DB("db_dev").getSingleResult("select id from ……");
數據庫的鏈接,關閉工作都由框架進行統一封裝。
二、測試數據與測試腳本脫離
測試腳本與測試框架脫離后,測試腳本更加專注于業務邏輯,但僅僅這樣是不是就夠了呢?對于同一個功能點,我們往往也需要測很多數據,如果每個測試數據都“硬編碼”到測試腳本里,那么數據增加的時候又要將硬編碼的部分代碼復制粘貼,犯了和先前一樣的毛。
// 測試腳本脫離測試框架后的A1Test
class A1Test() {
……
// 測試A的實現過程
id = DB("db_dev").getSingleResult("select id from …… where coutry = 'CN'");
Assert(id == 100000);
id = DB("db_dev").getSingleResult("select id from …… where coutry = 'US'");
Assert(id == 200000);
id = DB("db_dev").getSingleResult("select id from …… where coutry = 'CA'");
Assert(id == 300000);
……
}
我們可以看到,雖然測試腳本TestA1的確不再負責程序啟動這樣的雜事,同時數據鏈接也更加方便和規范,但是對于多個用例(country值不同,需要校驗的id大小不同),仍然需要復制粘貼代碼來實現。
因此我們也需要將測試數據從測試腳本中獨立出來,實現“一個框架對應多個測試腳本,一個測試腳本對應多個測試數據”。
對于這樣的測試數據,往往是相對整齊規范的,我們可以用Excel,txt等文件,用表格的方式存儲測試數據,然后寫出程序逐行讀取(jxl可以支持Excel2003以前Excel文件的讀取),每一行就是單次測試所需的數據。
用例ID 用例描述 是否執行 國家縮寫 期望結果ID
用例ID | 用例描述 | 是否執行 | 國家縮寫 | 期望結果ID |
1 | 測試CN對應ID | y | CN | 100000 |
2 | 測試US對應ID | y | US | 200000 |
3 | 測試CA對應ID | y | CA | 300000 |
我采用jxl去讀取Excel數據,同時再對參數取用的方法進行簡化和封裝,最終可以用例如param("期望結果")這樣的方式返回當前執行的數據行對應列的數據。
// 測試數據與測試腳本脫離后的A1Test
class A1Test() {
……
// 測試A的實現過程
id = DB("db_dev").getSingleResult("select id from …… where coutry = " + param("國家縮寫"));
Assert(id == param("期望結果ID"));
……
}
三、總結
測試腳本從測試框架脫離,即是將“一個測試腳本負責整個測試執行過程”的設計思路變為“測試腳本只負責業務邏輯,一個測試框架驅動多個測試腳本完成測試”。當業務邏輯變化時,我們可以只修改測試腳本和數據而無須修改測試框架,當測試數據需要增加和修改時,我們可以只修改測試數據而無須修改測試腳本,這樣的思路歸根結底還是來源于面向對象,但不管怎樣,提高效率,降低成本才是最終的目的。
文章來源于領測軟件測試網 http://www.kjueaiud.com/