撰寫 UI 測試
在本節中,您會協助 Andy 和 Amita 撰寫 Selenium 測試,以驗證 Amita 所描述的 UI 行為。
Amita 通常會在 Chrome、Firefox 和 Microsoft Edge 上執行測試。 在此執行同樣的作業。 您將使用的 Microsoft 裝載代理程式已預先設定為使用其中每一個瀏覽器。
從 GitHub 擷取分支
在本節中,您會從 GitHub 提取 selenium
分支。 然後,「簽出」或切換至該分支。 分支的內容可協助您跟隨 Andy 和 Amita 所撰寫的測試。
此分支包含您在先前課程模組中使用的 Space Game 專案。 其也包含可供開始使用的 Azure Pipelines 設定。
在 Visual Studio Code 中,開啟整合式終端。
若要從 Microsoft 存放庫下載名為
selenium
的分支,請切換至該分支,並執行下列git fetch
和git checkout
命令:git fetch upstream selenium git checkout -B selenium upstream/selenium
提示
如果您在上一個單元中跟隨 Amita 進行手動測試,您可能已經執行了這些命令。 如果您已在上一個單元中執行它們,您現在仍然可以再次執行它們。
請回想一下,upstream 指的是 Microsoft GitHub 存放庫。 您專案的 Git 組態了解上游遠端,因為您設定該關聯。 從 Microsoft 存放庫派生專案並在本機複製該專案時,就會對其進行設定。
不久後,您就會將此分支推送至自己的 GitHub 存放庫,名為
origin
。在 Visual Studio Code 中,可以選擇性地開啟 azure-pipelines.yml 檔案。 熟悉初始組態。
此組態類似於您在此學習路徑的先前課程模組中所建立的設定。 其只會建置應用程式的發行設定。 為了簡潔起見,其也會省略您在先前課程模組中設定的觸發程序、手動核准和測試。
注意
更健全的設定可能會指定參與組建流程的分支。 例如,為協助驗證程式碼品質,您可以在每次在任何分支上推送變更時執行單元測試。 您也可以將應用程式部署到執行更詳盡測試的環境。 但是,只有當您有提取要求、有候選版,或將程式碼合併到 main 時,才會進行此部署。
如需詳細資訊,請參閱使用 Git 和 GitHub 在組建管線實作程式碼流程和組建管線觸發程序。
撰寫單元測試程式碼
Amita 很高興學習如何撰寫程式碼來控制網頁瀏覽器。
她和 Andy 將合作撰寫 Selenium 測試。 Andy 已設定空的 NUnit 專案。 在整個過程中,他們會參閱 Selenium 文件、一些線上教學課程,以及他們在 Amita 手動執行測試時所做的筆記。 在本課程模組結束時,您將發現更多資源來協助您完成整個過程。
讓我們來複習 Amita 用來撰寫測試的過程。 隨後您可以在 Visual Studio Code 開啟 Tailspin.SpaceGame.Web.UITests 目錄中的 HomePageTest.cs。
定義 HomePageTest 類別
Andy:我們需要做的第一件事是定義我們的測試類別。 我們可以選擇遵循數個命名慣例的其中一個。 讓我們來呼叫我們的類別 HomePageTest
。 在這個類別中,我們會放置所有與首頁相關的測試。
Andy 會將此程式碼新增至 HomePageTest.cs:
public class HomePageTest
{
}
Andy:我們必須將此類別標示為 public
,以便 NUnit 架構可以使用此類別。
新增 IWebDriver 成員變數
Andy:接下來我們需要 IWebDriver
成員變數。 IWebDriver
是您用來啟動網頁瀏覽器並與網頁內容互動的程式設計介面。
Amita:我聽說過程式設計的介面。 可以告訴我更多資訊嗎?
Andy:將介面視為元件行為方式的規格或藍圖。 介面會提供該元件的方法或行為。 但是介面並未提供任何基礎詳細資料。 您或其他人將建立一或多個實作該介面的「具體類別」。 Selenium 提供我們所需的具體類別。
下圖顯示 IWebDriver
介面和一些實作此介面的類別:
下圖顯示 IWebDriver
提供的三種方法:Navigate
、FindElement
和 Close
。
這裡所顯示的三個類別分別是 ChromeDriver
、FirefoxDriver
和 EdgeDriver
,每個類別實作 IWebDriver
和其方法。 有其他類別 (例如 SafariDriver
) 也會實作 IWebDriver
。 每個驅動程式類別都可以控制其所代表的網頁瀏覽器。
Andy 會將名為 driver
的成員變數新增至 HomePageTest
類別,就像這個程式碼:
public class HomePageTest
{
private IWebDriver driver;
}
定義測試固件
Andy:我們想要在 Chrome、Firefox 和 Edge 上執行整組測試。 在 NUnit 中,我們可以使用「測試固件」來執行多次整組測試,亦即我們想要在其上進行測試的每個瀏覽器一次。
在 NUnit 中,您可以使用 TestFixture
屬性來定義您的測試固件。 Andy 會將這三個測試固件新增至 HomePageTest
類別:
[TestFixture("Chrome")]
[TestFixture("Firefox")]
[TestFixture("Edge")]
public class HomePageTest
{
private IWebDriver driver;
}
Andy:接下來我們需要定義測試類別的建構函式。 當 NUnit 建立這個類別的執行個體時,就會呼叫此函式。 作為其引數,建構函式會採用我們已附加至測試固件的字串。 程式碼看起來像這樣:
[TestFixture("Chrome")]
[TestFixture("Firefox")]
[TestFixture("Edge")]
public class HomePageTest
{
private string browser;
private IWebDriver driver;
public HomePageTest(string browser)
{
this.browser = browser;
}
}
Andy:我們已新增 browser
成員變數,以便可以在設定程式碼中使用目前的瀏覽器名稱。 接下來我們要撰寫設定程式碼。
定義設定方法
Andy:接下來,我們需要將 IWebDriver
成員變數指派給針對我們測試所在瀏覽器實作此介面的類別執行個體。 ChromeDriver
、FirefoxDriver
和 EdgeDriver
類別會分別針對 Chrome、Firefox 和 Edge 實作這個介面。
讓我們建立一個名為 Setup
的方法,以設定 driver
變數。 我們會使用 OneTimeSetUp
屬性來告訴 NUnit,每個測試固件執行一次此方法。
[OneTimeSetUp]
public void Setup()
{
}
在 Setup
方法中,我們可以使用 switch
陳述式,根據瀏覽器名稱將 driver
成員變數指派給適當的具體實作。 讓我們立即新增該程式碼。
// Create the driver for the current browser.
switch(browser)
{
case "Chrome":
driver = new ChromeDriver(
Environment.GetEnvironmentVariable("ChromeWebDriver")
);
break;
case "Firefox":
driver = new FirefoxDriver(
Environment.GetEnvironmentVariable("GeckoWebDriver")
);
break;
case "Edge":
driver = new EdgeDriver(
Environment.GetEnvironmentVariable("EdgeWebDriver"),
new EdgeOptions
{
UseChromium = true
}
);
break;
default:
throw new ArgumentException($"'{browser}': Unknown browser");
}
每個驅動程式類別的建構函式都會採取驅動程式軟體 Selenium 所需的選用路徑,以控制網頁瀏覽器。 稍後,我們將討論環境變數的角色,如下所示。
在此範例中,EdgeDriver
建構函式也需要其他選項,以指定我們想要使用 Chromium 版本的 Edge。
定義協助程式方法
Andy:我知道我們需要在整個測試中重複兩個動作:
- 在頁面上尋找元素,例如我們按一下的連結,以及我們預期會顯示的強制回應視窗。
- 按一下頁面上的元素,例如顯示強制回應視窗的連結,以及關閉每個強制回應的按鈕。
讓我們撰寫兩個協助程式方法,每個動作一個。 我們將從在頁面上尋找元素的方法開始著手。
撰寫 FindElement 協助程式方法
當您在頁面上尋找元素時,通常會回應某個其他事件,例如頁面載入或使用者輸入資訊。 Selenium 會提供 WebDriverWait
類別,可讓您等候條件成為 true。 如果在指定的時段內條件不為 true,則 WebDriverWait
會擲回例外狀況或錯誤。 我們可以使用 WebDriverWait
類別來等待要顯示的指定元素,並準備好接收使用者輸入。
若要在頁面上找出元素,您可以使用 By
類別。 By
類別所提供的方法,可讓您依其名稱、CSS 類別名稱、HTML 標籤 (或在我們的案例中依其 id
屬性) 尋找元素。
Andy 和 Amita 編寫 FindElement
協助程式方法的程式碼。 它會看起來像此程式碼:
private IWebElement FindElement(By locator, IWebElement parent = null, int timeoutSeconds = 10)
{
// WebDriverWait enables us to wait for the specified condition to be true
// within a given time period.
return new WebDriverWait(driver, TimeSpan.FromSeconds(timeoutSeconds))
.Until(c => {
IWebElement element = null;
// If a parent was provided, find its child element.
if (parent != null)
{
element = parent.FindElement(locator);
}
// Otherwise, locate the element from the root of the DOM.
else
{
element = driver.FindElement(locator);
}
// Return true after the element is displayed and is able to receive user input.
return (element != null && element.Displayed && element.Enabled) ? element : null;
});
}
撰寫 ClickElement 協助程式方法
Andy:接下來,讓我們撰寫一個協助程式方法來按一下連結。 Selenium 提供幾種方式來撰寫該方法。 其中一個是 IJavaScriptExecutor
介面。 有了此介面,我們可以使用 JavaScript 以程式設計的方式按一下連結。 這種方法很好用,因為其可以按一下連結,而不需要先將它們捲動成檢視。
ChromeDriver
、FirefoxDriver
和 EdgeDriver
,每一個都會實作 IJavaScriptExecutor
。 我們需要將驅動程式轉換成這個介面,然後呼叫 ExecuteScript
,在基礎 HTML 物件上執行 JavaScript click()
方法。
Andy 和 Amita 編寫 ClickElement
協助程式方法的程式碼。 它會看起來像此程式碼:
private void ClickElement(IWebElement element)
{
// We expect the driver to implement IJavaScriptExecutor.
// IJavaScriptExecutor enables us to execute JavaScript code during the tests.
IJavaScriptExecutor js = driver as IJavaScriptExecutor;
// Through JavaScript, run the click() method on the underlying HTML object.
js.ExecuteScript("arguments[0].click();", element);
}
Amita: 我喜歡新增這些協助程式方法的想法。 它們似乎普通到幾乎可在任何測試中使用。 我們稍後可在需要時新增更多協助程式方法。
定義測試方法
Andy:現在我們已準備好定義測試方法。 根據我們先前執行的手動測試,讓我們呼叫這個方法 ClickLinkById_ShouldDisplayModalById
。 好的做法是提供測試方法描述性名稱,以精確定義測試要完成的目標。 在這裡,我們想要按一下其 id
屬性所定義的連結。 然後我們想要也使用其 id
屬性驗證適當的強制回應視窗是否顯示。
Andy 會新增測試方法的起始程式碼:
public void ClickLinkById_ShouldDisplayModalById(string linkId, string modalId)
{
}
Andy:在新增更多程式碼之前,讓我們先定義此測試的用途。
Amita:我可以處理這個部分。 我們想要:
- 依其
id
屬性找出連結,然後按一下該連結。 - 找出產生的強制回應。
- 關閉強制回應。
- 確認已成功顯示強制回應。
Andy:太棒了。 我們也需要處理一些其他事情。 例如,如果無法載入驅動程式,我們需要忽略測試,而且只有在成功顯示強制回應時,才需要關閉強制回應。
在重新裝滿咖啡杯之後,Andy 和 Amita 會將程式碼新增至其測試方法。 他們會使用其所撰寫的協助程式方法來尋找頁面元素,然後按一下連結和按鈕。 結果如下︰
public void ClickLinkById_ShouldDisplayModalById(string linkId, string modalId)
{
// Skip the test if the driver could not be loaded.
// This happens when the underlying browser is not installed.
if (driver == null)
{
Assert.Ignore();
return;
}
// Locate the link by its ID and then click the link.
ClickElement(FindElement(By.Id(linkId)));
// Locate the resulting modal.
IWebElement modal = FindElement(By.Id(modalId));
// Record whether the modal was successfully displayed.
bool modalWasDisplayed = (modal != null && modal.Displayed);
// Close the modal if it was displayed.
if (modalWasDisplayed)
{
// Click the close button that's part of the modal.
ClickElement(FindElement(By.ClassName("close"), modal));
// Wait for the modal to close and for the main page to again be clickable.
FindElement(By.TagName("body"));
}
// Assert that the modal was displayed successfully.
// If it wasn't, this test will be recorded as failed.
Assert.That(modalWasDisplayed, Is.True);
}
Amita:目前為止,編碼看起來很棒。 但是,我們要如何將此測試連線到我們稍早收集的 id
屬性?
Mara:很棒的問題。 接下來我們將處理該問題。
定義測試案例資料
Andy:在 NUnit 中,您可以透過幾種方式提供資料給您的測試。 在這裡,我們會使用 TestCase
屬性。 這個屬性會接受引數,稍後其會在執行時傳回給測試方法。 我們可有多個 TestCase
屬性,而每個屬性都會測試應用程式的不同功能。 每個 TestCase
屬性都會產生一個測試案例,其會併入在管線執行結束時出現的報告中。
Andy 會將這些 TestCase
屬性新增至測試方法。 這些屬性描述 [下載遊戲] 按鈕、其中一個遊戲畫面,以及排行榜上的頂級玩家。 每個屬性都會指定兩個 id
屬性:一個供連結按一下,另一個用於對應的強制回應視窗。
// Download game
[TestCase("download-btn", "pretend-modal")]
// Screen image
[TestCase("screen-01", "screen-modal")]
// // Top player on the leaderboard
[TestCase("profile-1", "profile-modal-1")]
public void ClickLinkById_ShouldDisplayModalById(string linkId, string modalId)
{
...
Andy:針對每個 TestCase
屬性,第一個參數是供連結按一下的 id
屬性。 第二個參數是 id
屬性,適用於我們預期會出現的強制回應視窗。 您可以查看這些參數如何對應至測試方法中的兩個字串引數。
Amita:我看到了。 透過一些練習,我認為我可以新增自己的測試。 何時可以看到這些測試在管線中執行?
Andy:在我們透過管線推送變更之前,讓我們先確認程式碼會在本機編譯和執行。 我們會將變更認可並推送至 GitHub,並在我們確認一切正常運作之後,才會看到這些變更透過管線移動。 現在讓我們在本機執行測試。