[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 進入點
筆者通常測試的進入點有三個:
- Business Logic Layer 的 public 入口。
- Data Access Layer 上, Dao 的開發。(可能為存取檔案或資料庫)
- 重要的 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。
推薦原因:免費、簡單、擴充性高。
- 免費:不管公司大小,誰都可以下載來玩。
- 簡單:連 PM、工讀生都可以輕鬆上手。
- 擴充性高:支援將錄製的腳本轉成多種語言,轉成語言後,即可透過 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 ,並不會造成額外的成本,因為它只是把測試的動作搬到前面的流程,因為:「測試案例可以幫助我們更順利、迅速、專注、輕鬆地開發符合使用者需求的程式」。
Sample Code
blog 與課程更新內容,請前往新站位置:http://tdd.best/