[30天快速上手TDD][Day 8]Integration Testing & Web UI Testing

[30天快速上手TDD][Day 8]Integration Testing & Web UI Testing

前言

原本只打算講 Integration Testing ,但又覺得這樣講的不過癮,只好把 Web UI 的 Testing 納進來。

這篇文章主要會介紹到,如何界定 Integration Testing ,以及在撰寫時要注意的事項。

而大部分的篇幅,會介紹一下,在撰寫 Web 網頁時,可以透過 Selenium 這個工具,來幫助我們進行網頁的測試。

即使不是自動化測試,也希望讀者朋友們可以了解一下 Selenium 怎麼使用,絕對可以幫助各位在開發、偵錯、維護時,節省不少時間。

 

Integration Testing 定義

整合測試,針對的其實就是各物件之間的互動,或是模組運作是否正常。

整合測試所在的環境,應該是模擬的測試環境,基本上正式環境中該有服務、資源、資料庫等等,在測試環境也應有相對應的一份。(通常可能稱之為 alpha, beta 等等環境)。

如果單元測試的定義,是要獨立的測試目標物件上的行為,那麼整合測試就是不獨立的測試目標物件。在測試環境中,仿真地黑箱測試每一個物件的行為,並驗證是否如同預期。

若單元測試為白箱測試,則整合測試偏黑箱測試,針對流程、模組、物件每一個重要的入口點,進行 input/output 的驗證,以了解在實際環境中, production code 是否能如預期般正常運作。

 

Integration Testing 進入點

筆者通常測試的進入點有三個:

  1. Business Logic Layer 的 public 入口。
  2. Data Access Layer 上, Dao 的開發。(可能為存取檔案或資料庫)
  3. 重要的 domain object 行為驗證。

 

Integration Testing 注意事項

讀者需要注意一點,即使在測試環境進行整合測試,仍有可能有一些外部資源或服務,是無法模擬的,例如:每次查詢要花錢的服務,或是跟銀行交換資料之類的服務。

這時候,還是得針對這一類的相依服務,進行 stub/fake object 的設計來模擬。但絕大部分應該都不需要再透過 stub 等機制來模擬,因為要測試的就是各物件之間合作是否正常。

還有,整合測試的另一個重點:該如何重複執行而不會發生錯誤。

舉例來說,測試一個 Dao ,新增資料至DB中,如果測試案例不變,而資料表的PK也不是自動增加的,那麼執行第二次整合測試程式時,勢必就會出現主索引重複的錯誤。然而,測試程式基本上是不帶有邏輯的,所以也不建議寫一堆程式碼來避開這類的錯誤。

這類的問題,該如何解決呢?

簡單的建議是: snapshot !

每一次開始進行整合測試時,都將環境還原為 snapshot 的情況後,才進行測試。測試案例執行完畢後,再還原一次。

這樣的動作,相當花費時間,所以當 developer 在抱怨自己的單元測試跑很久時,他的測試程式大概八九不離十是整合測試,而非單元測試。

單元測試強調獨立且在任何環境應該都能跑出一樣的結果,整合測試則強調在模擬環境下,各物件模組功能應如同預期。也因為相當花費時間,所以如果有 CI server 的話,建議這個執行整合測試的動作,可以交由 CI daily build 的時候,在 server 離峰時間,或不影響到大家作業時,在測試機上進行整合測試。

有關 CI 的資訊,請參考小弟去年的文章:[軟體工程]持續整合 (Continuous integration, CI) 簡介

 

Web UI Testing – Selenium

基本上測試的粒度越大,測試花費的成本越小,但效益也越小,異動也越多。

廢話不多說,網頁的測試,首推免費的 Firefox Add-on :  Selenium

推薦原因:免費、簡單、擴充性高。

  1. 免費:不管公司大小,誰都可以下載來玩。
  2. 簡單:連 PM、工讀生都可以輕鬆上手。
  3. 擴充性高:支援將錄製的腳本轉成多種語言,轉成語言後,即可透過 CI 或執行測試,啟動 Web Auto Testing 的腳本。

 

Selenium 安裝

請先到 Selenium 官網下載 Selenium IDE 。如下圖所示:

Selenium IDE download

安裝的時候,可以看到 Selenium 支援許多語言格式,當然這邊我們關心的是 C# format,如下圖所示:

selenium ide install

安裝完成後,打開 Firefox 後,在 [Tool] 選項中,就可以看到 Selenium IDE,其實就是 Web 錄製器。紅色按鈕,即為「開始錄製」。如下圖所示:

IDE init

 

範例

這邊使用 ASP.NET 建立一個網站,第一頁為 Login 頁面,當 account 輸入 Joey , password 輸入 91 時,即導到 Welcome 頁面。當 account 輸入 Joey , password 輸入 123 時,則出現「登入錯誤,請重新輸入帳號密碼」。

當我們打開 Selenium 開始錄製的按鈕後,連到 Login 頁面,接著在 account 輸入 Joey ,在 password 輸入 91 後,切換到 Selenium IDE 的畫面,可以看到 Selenium 已經幫我們自動錄製了剛剛的動作,如下圖所示:

Selenium Auto Record

繼續往下錄下去,當點了「Login」按鈕後,導到了 Welcome 頁面。接下來我們希望驗證,畫面是否出現「Welcome, joey」,這時讀者可以自行在 Selenium IDE 上輸入 command ,但也有更簡單的方式,將想要 assert 的部分反白後,按滑鼠右鍵出現選單,上面就有常用的 Selenium command ,甚至可以 show all avaiable commands ,讓讀者可以自行選擇適合的 command 。

這個例子,我們要驗證的是 id 為 lblMessage ,其 text 是否為「Welcome, joey」,如下圖所示:

assert text

將這個 Test case 存檔為 Login Success ,這時讀者應該會發現一個很特別的地方:「Selenium IDE 自動錄製的腳本,是存成 .html 檔。」

到這邊,我們就錄好了 Login Success 的腳本,有這麼簡單嗎?!就這麼簡單,讀者朋友可以按一下 play ,就可以看到測試腳本瞬間就執行完畢了(如需調整速度,以因應網路 latency ,請自行調整 Fast-Slow 軸)如下圖所示:

play selenium test case

相信大家一定很好奇, test case 存成的 html 長什麼樣子,基本上可以直接透過瀏覽器瀏覽 test cases 的樣子,如下圖所示:

test case html

這樣子存成 html 的好處是,閱讀方便,擴充方便。而且存好的 test cases ,可以透過中斷點,隨時再加入新的 command ,也可以將多個 test case 存成一份 Test Suite 。

Selenium IDE ,就這麼簡單,不管是給 PM, SA, QA, developer, 甚至工讀生、老闆或客戶,只要 5 ~ 10 分鐘,就可以教會他們怎麼使用錄製跟播放的功能。

 

Selenium WebDriver

Selenium 如果只有這樣子,稱不上 amazing ,接下來要介紹,怎麼透過測試專案來啟動我們錄好的腳本。

在 Selenium IDE 上,點選 [File] , [Export Test Case As...] ,接著選 [C# /NUnit/ WebDriver] ,存好檔之後,打開會發現程式碼如下:

using System;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using NUnit.Framework;
using OpenQA.Selenium;
using OpenQA.Selenium.Firefox;
using OpenQA.Selenium.Support.UI;

namespace SeleniumTests
{
    [TestFixture]
    public class LoginWithNUnit
    {
        private IWebDriver driver;
        private StringBuilder verificationErrors;
        private string baseURL;
        
        [SetUp]
        public void SetupTest()
        {
            driver = new FirefoxDriver();
            baseURL = "http://localhost:56099/";
            verificationErrors = new StringBuilder();
        }
        
        [TearDown]
        public void TeardownTest()
        {
            try
            {
                driver.Quit();
            }
            catch (Exception)
            {
                // Ignore errors if unable to close the browser
            }
            Assert.AreEqual("", verificationErrors.ToString());
        }
        
        [Test]
        public void TheLoginWithNUnitTest()
        {
            driver.Navigate().GoToUrl(baseURL + "/MyWebSite/Login.aspx");
            driver.FindElement(By.Id("txtAccount")).Clear();
            driver.FindElement(By.Id("txtAccount")).SendKeys("joey");
            driver.FindElement(By.Id("txtPassword")).Clear();
            driver.FindElement(By.Id("txtPassword")).SendKeys("91");
            driver.FindElement(By.Id("btnLogin")).Click();
            Assert.AreEqual("Welcome, joey", driver.FindElement(By.Id("lblMessage")).Text);
        }
        private bool IsElementPresent(By by)
        {
            try
            {
                driver.FindElement(by);
                return true;
            }
            catch (NoSuchElementException)
            {
                return false;
            }
        }
    }
}

是的, Selenium 可以將 IDE 上自動錄製的腳本,直接轉換成 C# NUnit 的測試程式。

Magic !!! 就這麼簡單,接下來只需要把這段程式碼,貼到測試專案中,透過執行測試,就可以看到 Selenium WebDriver 啟動對應的 browser ,並執行錄製的腳本。

 

WebDriver to MSTest 範例

說明:這邊除了要執行剛剛錄製好的腳本,順手把 NUnit 改成 MSTest 。

新增一個測試專案,並透過 NuGet 加入 Selenium WebDriver 的參考,如下圖所示:

webdriver install from NuGet

接著將 NUnit 的關鍵字,都改成 MS Test Framework 的關鍵字即可,程式碼如下:

using System;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using OpenQA.Selenium;
using OpenQA.Selenium.Firefox;
//using OpenQA.Selenium.Support.UI;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace SeleniumTests
{
    [TestClass]
    public class LoginWithNUnit
    {
        private IWebDriver driver;
        private StringBuilder verificationErrors;
        private string baseURL;

        [TestInitialize()]
        public void SetupTest()
        {
            driver = new FirefoxDriver();
            baseURL = "http://localhost:56099";
            verificationErrors = new StringBuilder();
        }

        [TestCleanup()]
        public void TeardownTest()
        {
            try
            {
                driver.Quit();
            }
            catch (Exception)
            {
                // Ignore errors if unable to close the browser
            }
            Assert.AreEqual("", verificationErrors.ToString());
        }

        [TestMethod]
        public void TheLoginWithNUnitTest()
        {
            driver.Navigate().GoToUrl(baseURL + "/MyWebSite/Login.aspx");
            driver.FindElement(By.Id("txtAccount")).Clear();
            driver.FindElement(By.Id("txtAccount")).SendKeys("joey");
            driver.FindElement(By.Id("txtPassword")).Clear();
            driver.FindElement(By.Id("txtPassword")).SendKeys("91");
            driver.FindElement(By.Id("btnLogin")).Click();
            Assert.AreEqual("Welcome, joey", driver.FindElement(By.Id("lblMessage")).Text);
        }

        private bool IsElementPresent(By by)
        {
            try
            {
                driver.FindElement(by);
                return true;
            }
            catch (NoSuchElementException)
            {
                return false;
            }
        }
    }
}

接著執行測試,即可看到 Visual Studio 執行這一段測試程式,會透過 Selenium 的 WebDriver 開啟本機端的 Firefox 瀏覽器,並連至 http://localhost:56099/MyWebSite/Login.aspx

既然是測試程式,當然也可以設定中斷點,如下圖所示:

debug in selenium webdriver

很有趣吧。透過這樣的方式,可以針對系統重要的流程,錄製好測試腳本,匯出成 C# 或其他語言的測試程式,接著進行微調後,執行測試程式即可進行 Web UI 的自動測試。

一樣,這樣的測試程式執行起來會花點時間,可以放到 CI 的 daily build 上執行,即可確定最新版本的程式,是否會影響原本網頁的正常運作。

 

結論

如文章中提到的一個重點,測試粒度越大,基本上測試花費的難易度可能較小,但通常也是最容易因為異動而需要改變。因此建議,例如針對產品,越穩定的流程,或重要性越高的流程,進行這些測試腳本的錄製與撰寫,投資報酬比才會比較划算。

但,不代表這樣, developer 就不需要撰寫 Integration Test 或不需要學會 Selenium 。

Integration Test 可以幫助 developer 在開發時,先把範圍限定下來,當每一個物件的單元測試寫完,測試通過時,也應該是整合測試通過的時候。因為單元測試通常是整合測試中,使用到的物件所 break down 出來的測試案例

而 Selenium 這類工具,真的可以節省許多 developer 在無謂的開啟網頁、輸入資料、網頁上操作與用眼睛驗證資訊的時間,即便不是自動測試,當做錄製、 replay 、自動填寫表單資料、自動操作,其投資報酬比也相當高。

基本上 developer 會用到的常見測試,就是這幾類。

到這,希望讀者思考一下,如果每一個功能的開發,每一個需求的開發,我們都先有了想法、期望的結果,並先將環境、相關資源、 prototype 先建立出來,接著建立好可以呈現最後執行結果的測試案例,最後就只是輕鬆的撰寫好每一塊肉,也就是 production code ,每一個綠燈,就代表一塊肉長好了,也可以保證每往前進一步,都不會有重來或改壞舊有程式的風險。

這才是這一系列測試相關的文章,最想帶出的重點。

TDD ,並不會造成額外的成本,因為它只是把測試的動作搬到前面的流程,因為:「測試案例可以幫助我們更順利、迅速、專注、輕鬆地開發符合使用者需求的程式」。

 

Sample Code

  1. Day 8 sample code and test cases.zip

對敏捷開發有興趣的朋友,可以參考我的粉絲專頁:91敏捷開發之路

對 TDD 課程有興趣的朋友,課程內容、大綱與學員心得,可以參考 skilltree 的公開課程:自動測試與 TDD 實務開發

若需要聯絡我,可以透過粉絲專頁私訊或是側欄的關於我。