selenium從入門到應用 - 4,頁面對象設計模式的實現


本系列所有代碼 https://github.com/zhangting85/simpleWebtest

 

本文將介紹一個Java+TestNG+Maven+Selenium的web自動化測試腳本環境下selenium頁面對象腳本的編寫,並提供全部代碼。

文中將看到,使用selenium編寫的一個高復用性、高可維護性的測試框架的核心部分。

 

背景就不多介紹了,不用頁面對象設計的selenium測試框架多半會存在巨大缺陷,長時間使用后問題多多,並且導致了N多項目失敗。或者即使不失敗,也讓自動化測試人員感覺非常厭煩。比如我見過的一些失敗框架:有用一個類封裝了一百二十幾個selenium操作,用了一個三千多行的上帝類的;有完全不能重用全部用線性代碼實現,開發人員小小改動一行代碼,自動化測試人員改30多個文件的;還有試圖用Spring去實現testNG的測試配置、執行功能的;有不管什么東西都先放個接口再implements的;總之奇奇怪怪的設計千萬種(上帝類特別多,上帝類其實真的很累,給上帝減負吧親。)。

 

以下是我的精簡設計,僅供參考

3個核心類:Page、TestCase、DriverManager; 由於太簡單,類圖就略了。

Page是所有頁面對象的父親,並在構造方法里調用selenium頁面工廠初始化頁面。

TestCase是所有測試用例的父親,一方面,他定義了每個測試用例的前置操作和后置操作,另一方面,他內部有一個嵌套類DriverManager負責處理driver。

DriverManager是TestCase的靜態內部類,負責處理driver相關的操作 

 

由於我的測試用例是由TestNG來執行的,通常從一個xml文件開始。指定執行某個包下所有帶@Test標簽的方法(即測試方法)。這個機制使得我們不用親自去創建TestCase類的實例。(有的開發人員用Spring來創建TestCase的實例,但我用TestNG更簡便。兩者實現的功能都是通過配置文件,由框架創建我們需要執行的TestCase類的實例並執行他。)

 

先貼上代碼:

 

Page

 1 public class Page {
 2     /**
 3      * 構造方法,被所有Page的子類繼承,所以每個頁面都可以通過自動調用這個方法來初始化頁面對象 
 4      * it auto calls by all sub-page
 5      */
 6     public Page() {
 7         PageFactory.initElements(DriverManager.driver, this);
 8     }
 9 
10 }

 

TestCase和DriverManager

  1 package simplewebtest.core;
  2 
  3 import org.apache.commons.logging.Log;
  4 import org.apache.commons.logging.LogFactory;
  5 import org.openqa.selenium.WebDriver;
  6 import org.openqa.selenium.firefox.FirefoxDriver;
  7 import org.openqa.selenium.support.events.EventFiringWebDriver;
  8 import org.testng.annotations.AfterClass;
  9 import org.testng.annotations.AfterMethod;
 10 import org.testng.annotations.BeforeClass;
 11 import org.testng.annotations.BeforeMethod;
 12 import org.testng.annotations.Optional;
 13 import org.testng.annotations.Parameters;
 14 /**
 15  * 所有TestNG TestCase都繼承這個類。
 16  * 這個類的功能是讓testNG可以設置他的driver類型
 17  * 以及由他的成員driver manager來操作這個case要用到的driver
 18  */
 19 public class TestCase {
 20 
 21     /**
 22      * 打log用的對象,this表示具體的子類。
 23      * print log
 24      */
 25     protected Log log = LogFactory.getLog(this.getClass());
 26     
 27     /**
 28      * 決定這個TestCase是用什么瀏覽器的driver來執行。
 29      * 由於設置了BeforeMethod標簽,這個方法將由TestNG在每個TestMethod被執行前調用。
 30      * 他將接收一個從TestNG的xml文件傳入的參數表示瀏覽器種類。
 31      * 告訴manager我要新建的driver的類型。
 32      * runs by testNG, it will be run before every test method to decide the driver type 
 33      * @param browser:從testng的xml文件傳入的瀏覽器名稱。 默認值為firefox
 34      */
 35     @BeforeMethod(alwaysRun=true)
 36     @Parameters("brwoser")
 37     protected void testMethodStart(@Optional("firefox") String browser){
 38     DriverManager.setDriver(browser);
 39     }
 40     
 41     /**
 42      * 在一個測試方法結束時再打一遍名字關閉driver,這部分可以根據需要調整
 43      * runs by testNG, it will be run after every test method to close the driver
 44      */
 45     @AfterMethod(alwaysRun=true)
 46     protected void testMethodEnd(){
 47     DriverManager.quitDriver();;
 48     }
 49     
 50     /**
 51      * 打印類名。建議一個CASE只放一個方法。
 52      * Print Class Name
 53      */
 54     @BeforeClass(alwaysRun=true)
 55     protected void testCaseStart(){
 56     //打印類名
 57     log.info("\\/\\/\\/\\/\\/\\/---TestCase = "+ this.getClass().getSimpleName()+"---\\/\\/\\/\\/\\/\\/");
 58     }
 59     
 60     /**
 61      * 再次打印類名
 62      * Print Class Name again and separator
 63      */
 64     @AfterClass(alwaysRun=true)
 65     protected void testCaseEnd(){
 66     //打印類名
 67     log.info("/\\/\\/\\/\\/\\/\\---TestCase = "+ this.getClass().getSimpleName()+"---/\\/\\/\\/\\/\\/\\");
 68     //打印分隔符
 69     log.info("#####################################################");
 70     }
 71     
 72     /**
 73      * 靜態內部類。因為把這些driver相關的東西直接放在TestCase類里,我感覺從邏輯上說不通。引入一個靜態內部類來解決。
 74      */
 75     public static class DriverManager {
 76         /**
 77          * 每個DriverManager只管理一個driver,所以他是static的 
 78          * shares the same web driver
 79          */
 80         public static WebDriver driver;
 81         /**
 82          * 根據TestCase的要求來新建一個driver並保存起來
 83          * crate and saves the driver according to the browser type
 84          */
 85         public static void setDriver(String browser){
 86             if (browser.equals("firefox")){
 87                  driver = new EventFiringWebDriver(new FirefoxDriver()).register(new LogEventListener());
 88             }
 89             //有需求的同學自己在這里添加IE等瀏覽器的支持
 90             //you can add ie/chrome or other driver here
 91         }        
 92         
 93         /**
 94          * 關瀏覽器,Windows上需要在這里殺進程的步驟
 95          * quit the driver
 96          */
 97         public static void quitDriver(){
 98             driver.quit();
 99         }
100 
101     }
102 }

 

當測試執行時,原本testNG應該去創建某個具體測試用例(TestCase的某個子類),然后調用里面的@Test方法來執行測試。但是現在這些具體的測試用例有了共有的父類,Java里創建子類實例也就是調用子類構造方法時,會先調用其父類的構造方法,也就是創建他的父類實例。然后TestNG就知道了有這個實例。

接着,因為我們在他的父類也就是TestCase類里定義了使用@BeforeClass @BeforeMethod標簽的兩個方法,所以在執行子類的@Test前TestNG實際上會先執行這兩個方法。於是,我可以在這兩個方法里面去打log和啟動瀏覽器。這里,啟動瀏覽器的參數是由TestNG.xml中傳入的。我們可以配置好告訴testNG我要用什么瀏覽器,也可以傳入更多配置參數實現更豐富的測試組合。

 

之后,進入正式的測試用例執行階段,這里給一個測試用例的例子:

TestJDSearch

package simplewebtest.test.testcases.sample.jd;

import org.testng.annotations.Test;

import simplewebtest.core.TestCase;
import simplewebtest.core.page.sample.jd.JDHomepage;
import simplewebtest.core.page.sample.jd.JDItemlistPage;

public class TestJDSearch extends TestCase {

    /**
     * JD首頁上搜索一個商品 主要介紹定位某個商品名稱的N種寫法
     */
    @Test
    public void searchProduct() throws InterruptedException {
        log.info("這是測試方法里的第一句打印的log");
        JDHomepage home = new JDHomepage();
        
        //結果頁面the expected result page 
        JDItemlistPage resultPage=home.init().searchHeader.search("巧克力");
        //actual result: 用三種方法找出第一個商品名字,作為實際結果.(回字有五種寫法:P)
    
        String product_1= resultPage.firstproduct.getText();//不推薦,但偶爾有適用場景
        String product_2= resultPage.getFirstProductName();//不推薦,但偶爾有適用場景
        String product_3= resultPage.getProductNameByIndexMethodOne(1);//推薦寫法,但你方法名字不要這么長
        String product_4= resultPage.getProductNameByIndexMethodTwo(1);//推薦寫法,但你方法名字不要這么長
        
        log.info("第一個商品名字(用第一種取法)= "+product_1);
        log.info("第一個商品名字(用第二種取法)= "+product_2);
        log.info("第一個商品名字(用第三種取法)= "+product_3);
        log.info("第一個商品名字(用第四種取法)= "+product_4);
        
        //不加asseriton你的case永遠是pass
        assert(product_1.contains("巧克力"));
        assert(product_1.equals(product_2));
        assert(product_1.equals(product_3));
        assert(product_1.equals(product_4));
        
        log.info("這是測試方法里的最后一句打印的log");
    }

}

(這里用到的測試頁面JDHomepage我會在下一期詳解,但代碼在我的github工程上已經有了)

 

這里,首先新建了一個JDHomepage,他是Page類的子類,並且我沒有為他定義構造函數。所以他這里new一個對象時調用的其實是父類Page的構造函數。在這個構造函數里,page類調用了selenium的PageFactory來初始化頁面。(所以請開發轉的自動化測試人員們不要再通過集成Spring來創建頁面了。。)

不管是在具體case里還是在具體page里,我都可以直接使用DriverManager.driver來訪問當前driver,因為這個類和對象是靜態的並且在test case里BeforeMethod里已經進行初始化。

另外,這個靜態的driver會隨着test case的創造而創造,隨着test case的毀滅而毀滅。也就是說對每一個具體的test case,他會重新被創建和銷毀。

 

 

如果覺得不理解整個過程,建議下載源代碼,單步走一遍。就知道testNG下執行這個測試框架時他的執行流程了。

 

下一篇繼續深入頁面對象,講官網上沒有的東西:頁面模塊。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM