IBM Rational Functional Tester是由IBM推出的針對Java,.Net和Web應用程序的自動化測試工具,借助這一工具,測試人員可以輕松地錄制或編寫腳本來進行自動化測試,測試效率得到顯著提高,因而受到廣大功能測試人員的青睞。由于自動測試的運行無需人工干預,日志系統作為記錄運行過程的載體,對于測試成功與否的判定、錯誤的跟蹤與分析都有著非常重要的作用,是自動測試框架中不可或缺的組成部分。
GUI 即 Graphical User Interface, 圖形用戶界面;GUI測試,顧名思義,是對圖形用戶界面進行測試。由于人機交互界面對于大部分應用軟件產品都是必不可少的,而且軟件的顯示和功能也基本上是通過人機交互操作來體現和完成的,所以軟件產品的GUI測試在整個產品測試中占有非常重要的地位。
GUI測試我們可以采用手工和自動化的測試方法。對于一個帶有較多功能和較復雜界面的軟件產品,單調繁瑣的回歸測試會讓手工測試人員而感到十分枯燥和疲憊,進而影響了測試的準確度和效率;相反,使用自動化方式進行功能測試則會使情況大為改觀:機器在進行步驟簡單、較少互動的測試操作時,無論是效率還是準確度,都比手工測試更加優越。隨著自動化測試功能的延伸和適應性的增強,它在GUI測試中所覆蓋的工作范圍,也在日趨擴大。
基于Rational Functional Tester構建自動測試框架
IBM Rational Functional Tester是由IBM推出的針對Java,.Net和Web應用程序的自動化測試工具,擁有功能強大的編輯器并支持多種腳本語言,還集成了ScriptAssure 技術、模式匹配、及數據驅動等高級特性,以增強測試腳本的靈活性。借助這一工具,測試人員可以輕松地錄制或編寫腳本來進行自動化測試,測試效率得到顯著提高,因而受到廣大功能測試人員的青睞。
Rational Functional Tester作為一款通用的功能測試工具,除了提供基本功能外,同時向廣大使用者開放了一組API工具包供擴展使用(可參閱IBM Rational Functional Tester的聯機幫助文檔)。針對具體的被測應用軟件,測試人員可以拓展、包裝RFT的API,或引入其它工具,以實現必要的輔助功能。通常情況下,為了更好地執行GUI自動測試,一個基本的測試框架還需要加入以下輔助功能:測試數據的導入、測試環境的定制、測試執行日志、測試結果的生成和分析等等。
由于自動測試的運行無需人工干預,日志系統作為記錄運行過程的載體,對于測試成功與否的判定、錯誤的跟蹤與分析都有著非常重要的作用,是自動測試框架中不可或缺的組成部分。
![]() ![]() |
![]()
|
我們常見的日志方案簡單說來就是:“出錯后截圖+文本日志”。隨著自動測試的運行,模擬生成的各種鼠標、鍵盤操作也會被記錄在文本日志文件中,標明鼠標點擊了某某按鈕、在某文本框內輸入了某些文字等等。當用戶界面的表現與預期情況不一致時(通常預示著該測試用例失敗,但也有可能是由于測試腳本的不完善所致),測試框架會抓下當前屏幕的內容,以圖片形式保存,作為檢查和回溯時的依據。
文本日志和出錯后的截圖可以基本反映自動測試的過程;通過截圖,測試人員還可以了解到出錯時的軟件界面場景,對錯誤進行分析和糾正。如果是軟件缺陷導致的,表示測試用例失敗,需要及時撰寫測試報告,向開發人員反饋測試結果;如果是由于測試腳本的不完善所致,測試人員還需要對自動測試腳本進行修正,避免在日后的自動測試過程中,產生不必要的錯誤。
但傳統日志方案也有其明顯的不足之處,主要體現在:
針對傳統日志方案的不足之處,我們可以有的放矢,根據它的不足提出改進方案。主要思路有以下兩點:
![]() ![]() |
![]()
|
在改進前的日志方案里,自動測試軟件是按照既定邏輯運行測試用例,無論是通過錄制還是編寫腳本的方式;遇到錯誤后,它會截取當前屏幕的狀態,同時將錯誤信息以文本方式記錄在日志文件中。(如圖1所示)
該方案向測試人員提供了一副描述錯誤現場的截圖,以及文本形式的執行日志。為了找出導致錯誤發生的確切原因,測試人員需要追蹤并分析執行日志,截圖反映了發生錯誤當時的情形,但對于之前過程的反映,卻相當有限。
借助錯誤現場的截圖和文本日志,測試人員判斷測試失敗的癥結所在會非常吃力,尤其是一些不太明顯的錯誤。即便是富有經驗的測試人員,自動測試的錯誤分析也是比較棘手的工作,新人則更是無從下手。
在改進后的日志方案里,我們設計了一種更為簡單和直觀的方式,來反映錯誤發生前后的那段時間內,自動測試的真實運行過程。(如圖二 所示)
改進措施如下:
新方案在已有功能的基礎上,進行重構和轉化,模擬生成視頻片斷來再現測試過程。(對測試運行過程的屏幕截圖進行緩沖。必要時,將所有緩沖的圖片轉化成動態圖片,以再現實際過程。)
日志系統在采納新方案后,可以:
截屏以及生成偽視頻的操作會對腳本回放速度有一定影響,但并不突出,因為自動測試有著充足的硬件資源和時間資源(夜間運行)。這一弱點和新特性帶來的好處相比,幾乎可以忽略不計。
![]() ![]() |
![]()
|
假設該自動測試框架的原日志方案已經具有生成文本日志和截圖的功能,我們將在它的基礎上進行改進和優化,使之具有生成偽視頻日志的新特性。
圖三闡述了改進方案的基本工作步驟:
本例中給出的代碼主體ScreenshotsPool將以線程的形式運行,獨立于自動測試腳本的回放,因此實現了Runnable接口中的run()方法。(見圖四)
此外,還包括了其它必要的屬性,方法來完成截圖、儲存、生成偽視頻等操作,以及相關設置。后面會做詳細介紹。
在每個RFT腳本的初始化方法中,可以同時創建一個ScreenshotsPool對象,用以對腳本自動回放過程進行監控。
清單一: 實現代碼中的主要屬性
//單件模式,用來儲存截圖的緩沖區。 private static Vector bufferVec = null; //標識緩沖區是否儲存固定數目的截圖,默認為固定數目。 private boolean isLimitedBuffer = true; //偽視頻所覆蓋的自動測試過程,單位為秒;默認情況下存儲最近30秒內的截圖。 private int videoLength = 30; //截圖的時間間隔,單位為秒;默認情況下每隔1秒執行一次抓屏操作。 private double snapInterval = 1; //緩沖區容量,該屬性值由videoLength和snapInterval共同決定。 private int bufferCapability; //偽視頻輸出位置 private String outputLocation = "c:/"; //偽視頻格式,即生成的動態圖片格式,在本例給出的實現使用了GIF格式。 private String pseudoVideoFormat = ".gif"; //標識生成的偽視頻是否循環播放,默認為只播放一次。 private boolean replayIndefinitely = false; //偽視頻的重放速度,可選值為1,2,3。分別表示快速、中速和慢速。 private int replaySpeed = 2; //標識是否有錯誤發生。當其值為true時,表明需要立即生成偽視頻。 public static boolean errorFlag = false; |
清單二: 實現代碼中的主要方法
/** * Create a screenshot pool with limited capability, it only * stores enough screenshots to create a pseudo-video of appointed length * * @param videoLength - length of pseudo-video * @param snapInterval - interval to scratch screenshots, the unit is second * @param replayIndefinitely - the video plays once or repeats indefinitely * @param replaySpeed - play speed of the video,1 means fast,2 is normal,and 3 means slow */ public ScreenshotsPool(int videoLength, double snapInterval, boolean replayIndefinitely, int replaySpeed) { super(); this.isLimitedBuffer = true; this.videoLength = videoLength; this.snapInterval = snapInterval; this.replayIndefinitely = replayIndefinitely; //設置有效的回放速度; if((replaySpeed==1)||(replaySpeed==2)||(replaySpeed==3)) this.replaySpeed = replaySpeed; //通過偽視頻長度videoLength和抓屏頻率snapInterval計算出緩沖區的容量 this.bufferCapability = new Double(videoLength/snapInterval).intValue(); //初始化緩沖區 getBufferVec(); } |
ScreenshotsPool方法為構造器,按照給定的參數對必要的屬性進行初始化,并初始化唯一的緩沖區。
/** * Get buffer pool that store screenshots * * @return Vector - the only buffer pool to store screenshots */ public Vector getBufferVec() { if(null==bufferVec) bufferVec = new Vector(bufferCapability); return bufferVec; } |
getBufferVec()方法是單件模式,用來獲得唯一的截圖緩沖區;如果緩沖區暫時還不存在,則進行初始化。
/** * Push a new screenshot into buffer * if the buffer is limited and has reached its capability, the oldest * snapshot would be removed */ public void pushScreenshot() { //獲得當前屏幕的截圖對象 BufferedImage image = getCurrentScreen(); if(null==image) return; //如果截圖緩沖區有固定容量, if(isLimitedBuffer){ //如果已經到達緩沖區的最大容量,則刪去第一副截圖 int size = getBufferVec().size(); if(size==bufferCapability) getBufferVec().remove(0); } //將當前截圖存入緩沖區 getBufferVec().add(image); } |
pushScreenshot方法用來將當前屏幕的截圖存入緩沖區內。如果截圖數目到達緩沖區上限,最早的截圖會被刪除。
/** * Get current screenshot * * @return BufferedImage - screenshot of current screen */ public BufferedImage getCurrentScreen() { try{ //獲得屏幕大小 Dimension screenSize = toolkit.getDefaultToolkit().getScreenSize(); int width = screenSize.width; int height = screenSize.height; BufferedImage capture = null; //獲得當前屏幕范圍 Rectangle area = new Rectangle(0, 0, width, height); Robot robot = new Robot(); //截取當前屏幕范圍內的內容,返回BufferedImage對象 capture = robot.createScreenCapture(area); return capture; } catch (Exception e) { e.printStackTrace(); return null; } } |
getCurrentScreen方法用來獲得當前屏幕的截圖,返回值是一個包含當前屏幕圖像的BufferedImage對象。
/** * Generate pseudo-video with those buffered screens in pool * * Here we will generate animated picture of '.gif' format, and we can * generate animated picture of other formats likewise.(SVG, PNG, etc.) */ public void generatePseudoVideo() { int size = this.getBufferVec().size(); if(size<1) return; try{ BufferedImage bi = null; //新建一個AnimatedGifEncoder對象,用來生成動態的GIF文件 AnimatedGifEncoder pseudoVideoGenerator = new AnimatedGifEncoder(); //設置GIF文件是否循環顯示各幀 int repeat = replayIndefinitely?0:1; pseudoVideoGenerator.setRepeat(repeat); //設置輸出位置,以當前時間為文件名 pseudoVideoGenerator.start(outputLocation+getCurrentTime()+pseudoVideoFormat); //依次將緩沖區內的所有截圖按指定間隔加入AnimatedGifEncoder對象 for(int i=0;i<size;i++){ bi = (BufferedImage)(getBufferVec().get(i)); pseudoVideoGenerator.setDelay(200*replaySpeed); pseudoVideoGenerator.addFrame(bi); } //生成動態的GIF文件 pseudoVideoGenerator.finish(); }catch(Exception e){ e.printStackTrace(); } } |
generatePseudoVideo方法將緩沖區內的截圖集中起來,每副截圖作為一幀加入到一個生成器,再對相關參數做必要的設置,最后導出到一個GIF文件中。
其中,AnimatedGifEncoder類是一段開源程序,用來將一幀或多幀圖像轉換為一個GIF文件。作者是Kevin Weiner。
/** * main method of this thread */ public void run() { while(true) { try { //將當前屏幕的截圖存入緩沖區 pushScreenshot(); //睡眠一段時間 Thread.sleep(new Double(1000*this.snapInterval).longValue()); //查看錯誤標識,一旦有錯 if(errorFlag) { //立即生成偽視頻 generatePseudoVideo(); //復位錯誤標識 errorFlag = false; } } catch(Exception e) { e.printStackTrace(); } } } |
run()方法是該線程的主要方法,通過輪詢方式監測錯誤標識。如果在自動測試腳本在運行過程中有錯誤發生,它會將錯誤標識設為true;run()方法偵測發現該標識后,在第一時間內生成偽視頻,再對錯誤標識進行復位。
![]() ![]() |
![]()
|
新的日志方案在原有方案的基礎上進行了重構,對錯誤后截圖的功能加以萃取和改進,使原有日志系統在不追加任何軟硬件投資的前提下,能生成具有類似功能的偽視頻。測試人員能夠直觀地了解自動測試過程,快速定位測試腳本或軟件缺陷導致的錯誤成因,從而高效地優化測試腳本或填寫測試報告。
本例給出的實現使用了GIF格式的動態圖片作為生成的偽視頻,讀者可以按照類似方法,生成諸如SVG,PNG等其它格式的動態圖片來再現測試過程。
對于新日志系統中的各種各樣的表現形式:文本日志、截圖、以及偽視頻,可以使用HTML格式統一展現,有概況有明細,通過超鏈接將眾多日志內容組織在一起,讓日志系統能更加友好方便地供測試人員查閱。
原文轉自:http://www.anti-gravitydesign.com