iT邦幫忙

DAY 8
3

30天快速上手TDD系列 第 8

[Day 8]Integration Testing & Web UI Testing

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

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

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

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

上一篇文章:[Day 7]Unit Test - Stub, Mock, Fake簡介
本系列文章專區
@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的資訊,請參考小弟去年鐵人賽的文章:[如何提升系統品質-Day29]基礎建設-持續整合(CI)

@Web UI Testing - Selenium
基本上測試的粒度越大,測試花費的成本越小,但效益也越小,異動也越多。

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

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

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

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

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

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

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

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

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

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

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

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

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

這樣子存成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的參考,如下圖所示:

接著將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。

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

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

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

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

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

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

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

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

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

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


上一篇
[Day 7]Unit Test - Stub, Mock, Fake簡介
下一篇
[Day 9]Refactoring legacy code簡介
系列文
30天快速上手TDD31

2 則留言

0
ted99tw
iT邦高手 1 級 ‧ 2012-10-16 01:06:00

沙發

就算筆記本已經抄到快見底了,還是要趕緊操操操,抄抄抄~~~衝刺

0
pajace2001
iT邦研究生 1 級 ‧ 2012-10-16 11:03:51

泰大也失眠了嗎?這樣我都搶不到沙發了XD

我要留言

立即登入留言