原本只打算講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進入點
筆者通常測試的進入點有三個:
@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。
推薦原因:免費、簡單、擴充性高。
@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,並不會造成額外的成本,因為它只是把測試的動作搬到前面的流程,因為:「測試案例可以幫助我們更順利、迅速、專注、輕鬆地開發符合使用者需求的程式」。