網站測試自動化系統—基於Selenium和VSTT

 

剛剛以SCRUM的方式結束了一個的ASP.NET網站的測試的第一個Spring,因爲團隊從無到有實現自動化測試系統,有必要把這次的經驗和教訓總結一下,以便後續的Spring可以獲取一些有意義的借鑑。

因爲是一個技術博客,所以使用SCRUM管理這個測試項目的經驗放在別的地方分享,這系列的文章分享一下使用VSTTSelenium結合實現自動化測試系統的經驗。

 

Selenium簡介

Selenium主要是一個錄製並回放的自動化測試用例編制工具,由一個錄製工具Selenium IDE(一個Firefox插件,當然這個工具也可以回放啦),一個回放工具Selenium Remote Control在其他機器和其他操作系統上進行回放。Selenium的一個好處就是你可以使用它測試所有操作系統下的所有主流瀏覽器,至於Linux下面的konquerorgnome下面自帶的瀏覽器,沒有試過Selenium是否支持,當然那個控制檯界面下的瀏覽器就更沒有試過啦。Selenium還有一個Selenium Grid,據說很強大,因爲項目比較緊,就沒有花時間去看它。

至於Selenium各個工具的用法,它的官網上有詳細的文檔,如果文檔也沒說清楚的話,那就直接讀源代碼吧。

 

SeleniumVSTT的整合

Selenium可以根據錄製的步驟生成直接在NUnit中使用的C#代碼,這些代碼基本上都可以在VSTT中直接使用,就是一些屬性需要更改。例如[TestFixture]改成[TestClass][Test]改成[TestMethod]之類的,改好以後,啓動Selenium-RC,就可以直接在VSTT裏面當作普通的單元測試用例執行了。

 

Selenium代碼優化

既然要做自動化測試,那麼有一點是必須要時刻考慮的,就是在產品開發過程中,程序界面甚至是內部的類庫接口也是時刻改變的。而Selenium只能記錄當時錄製測試用例的界面情況,因此需需要將它生成的代碼分解一下,以面向對象的方式來重寫。例如下面這段代碼的目的是測試用戶可以查看自己的博客:

[TestMethod]

public void TheTestTest()

{

    selenium.Open("/");

    selenium.Click("link=登錄");

    selenium.WaitForPageToLoad("30000");

    selenium.Type("tbUserName", "donjuan");

    selenium.Type("tbPassword", "");

    selenium.Click("btnLogin");

    selenium.WaitForPageToLoad("30000");

    selenium.Click("link=donjuan");

    selenium.WaitForPageToLoad("30000");

    selenium.Click("link=博客");

    selenium.WaitForPageToLoad("30000");

}

但是網頁頁面佈局,或者Html控件的Id、文本等內容隨時都會被程序員修改,修改的原因有多種,例如修復新的錯誤(Bug),或者僅僅就是代碼重構。因此作爲測試團隊,不能總是認爲網頁的內容一成不變的。而象登錄這種操作,大部分測試用例都會用到,所以最好只要爲登錄動作創建唯一的代碼 。有多個方案:

1.       爲登錄創建一個獨立的測試用例,本來登錄這個功能就是要測試的嘛,在編輯自動化測試用例列表的時候,把登錄用例放在最前面。

2.       爲登錄動作創建一個單獨的函數,例如LogOn(),然後在其他測試用例當中(包括登錄的測試用例)調用這個函數,另外,因爲可能會需要用到不同的 用戶,所以最好把用戶名和密碼等變量提取出來,變成LogOn(string username, string password)之類的函數。

兩個方案,顯然是第二個方案的彈性大,但是對於第一個方案,如果測試人員都是新手,且對代碼不熟悉的話,建議可以考慮。

於是我們的代碼就變成類似下面的代碼:

using System;

 

//

// 這個異常是故意創建出來,用來封裝所有在測試代碼中發生的錯誤

//

public class CaseErrorException : Exception

{

    public CaseErrorException(string message)

        : base()

    {

    }

 

    public CaseErrorException(Exception inner)

        : this(null, inner)

    {

    }

 

    public CaseErrorException(string message, Exception inner)

        : base(message == null ? "測試代碼錯誤,請修復測試代碼,查看InnerException屬性!" :

                                 string.Format("測試代碼錯誤,請修復測試代碼,詳細錯誤信息:{0};或者查看InnerException屬性!", message),

               inner)

    {

    }

}

 

public class UserOperationsHelper

{

    public void LogOn(string username, string password)

    {

        // string.Empty留出來爲測試目的服務

        if (username == null)

            throw new CaseErrorException(new ArgumentNullException("username"));

        if (password == null)

            throw new CaseErrorException(new ArgumentNullException("password"));

 

        selenium.Open("/");

        selenium.Click("link=登錄");

        selenium.WaitForPageToLoad(Consts.TimeToWaitForPageLoad);

        selenium.Type("tbUserName", username);

        selenium.Type("tbPassword", password);

        selenium.Click("btnLogin");

        selenium.WaitForPageToLoad(Consts.TimeToWaitForPageLoad);

    }

}

 

public static class Consts

{

    // 將等待的時間提取成一個公開的函數,因爲在今後大規模的測試

    // 過程中,很多自動化測試用例不簡單地執行,會導致網站響應速度

    // 變慢,所以

    public const string TimeToWaitForPageLoad = "30000";

}

 

public class TestLibrary

{

    public UserOperationsHelper UserHelper { get; private set; }

}

 

public class TestClass

{

    [TestMethod]

    public void LogOnTest()

    {

        var username = "donjuan";

        var password = "它是個祕密";

        TestLibrary.UserHelper.LogOn(username, password);

 

        // 在測試過程中,我們發現這個鏈接是

        // 根據用戶名而變的,爲了擴展性,動態生成其標識文本

        selenium.Click(string.Format("link={0}", username));

        selenium.WaitForPageToLoad(Consts.TimeToWaitForPageLoad);

        selenium.Click("link=博客");

        selenium.WaitForPageToLoad(Consts.TimeToWaitForPageLoad);

 

        // 執行一些必要的測試驗證過程

        Assert.IsTrue(selenium.IsTextPresented(...));

    }

}

 

這裏稍微解釋一下,創建自動化測試代碼,就是爲了節省手工重複測試的工作量以及測試失誤的風險。但只要是代碼,都會有可能出錯,因此自動化測試框架裏面創建了一個CaseErrorException,這樣在每次分析測試用例失敗的時候,可以一眼區分開測試代碼的錯誤和產品代碼中的錯誤。例如在UserOperationHelper.LogOn函數中的參數檢查,當然啦,在測試過程當中,有可能需要測試不輸入用戶名或者密碼的情況下,驗證登錄界面是否正常工作的情況。因此在驗證參數的時候,特意爲這種情況留下了String.Empty的入口,而對於null值,則基本上可以判斷是因爲測試人員在編寫代碼上的失誤(具體原因會在數據驅動測試裏面講到)。

至於TestLibrary的初始化,完全可以放到每一個測試類型的TestInitializer裏面,如下表所示:

[TestClass]

public class AddBlogTest

{

    private TestContext testContextInstance;

    public TestContext TestContext

    {

        get

        {

                return testContextInstance;

        }

        set

        {

            testContextInstance = value;

        }

    }

 

    private TestLibrary TestLibrary;

    private ISelenium selenium;

 

    [TestInitialize]

    public void SetupTest()

    {

        TestLibrary = TestLibrary.SetupTest(TestContext);

        selenium = TestLibrary.Selenium;

    }

 

    [TestCleanup]

    public void TeardownTest()

    {

        TestLibrary.Shutdown();

    }

}

 

咋看起來,把LogOn測試用例分解成那麼多的類型,有點畫蛇添足,實際上這些函數庫正是爲了更方便地創建後續的測試用例耗費的磨刀的功夫。例如下面的代碼是基於一些創建好了的函數編寫的測試用例:

[TestMethod]

public void CreateBlog()

{

    TestLibrary.UserHelper.LogOnAsAdmin();

    var blog = TestLibrary.BlogHelper.CreateBlog("博客的標題", "博客的鏈接");

 

    selenium.Click("link=管理博客");

    selenium.WaitForPageToLoad(Consts.TimeToWaitForPageLoad);

 

    Assert.IsTrue(selenium.IsElementPresent(string.Format("link={0}", blog.Title)));

}

 

下一篇文章網站測試自動化系統—數據驅動測試講解這個框架和數據驅動測試的結合。

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