測試自動化概觀

首先,先問問自己是否真的需要使用瀏覽器。很有可能,在某個時候,如果您正在開發複雜的 Web 應用程式,您將需要開啟瀏覽器並實際測試它。

然而,功能性的終端使用者測試(例如 Selenium 測試)執行成本很高。此外,它們通常需要大量的基礎設施才能有效地執行。一個好的原則是始終問問自己,您想要測試的內容是否可以使用更輕量級的測試方法(例如單元測試)或更底層的方法來完成。

一旦您確定要進行 Web 瀏覽器測試,並且您的 Selenium 環境已準備好開始編寫測試,您通常會執行以下三個步驟的組合

  • 設定資料
  • 執行一組離散的動作
  • 評估結果

您會希望盡可能縮短這些步驟;大多數情況下,一到兩個操作就足夠了。瀏覽器自動化有「不穩定」的聲譽,但實際上,那是因為使用者經常對它要求過高。在後面的章節中,我們將回到您可以使用的技術,以減輕測試中明顯的間歇性問題,特別是如何克服瀏覽器和 WebDriver 之間的競爭條件

通過保持測試簡短,並且僅在絕對沒有其他選擇時才使用 Web 瀏覽器,您可以進行許多測試,並將不穩定性降至最低。

Selenium 測試的一個明顯優勢是其固有的能力,可以從使用者的角度測試應用程式的所有組件,從後端到前端。換句話說,雖然功能測試的執行成本可能很高,但它們也一次涵蓋了大型的業務關鍵部分。

測試需求

如前所述,Selenium 測試的執行成本可能很高。在何種程度上取決於您正在對其執行測試的瀏覽器,但從歷史上看,瀏覽器的行為差異很大,因此跨多個瀏覽器進行交叉測試通常是一個既定的目標。

Selenium 允許您在多個作業系統上的多個瀏覽器上執行相同的指令,但是列舉所有可能的瀏覽器、它們的不同版本以及它們運行的許多作業系統將很快成為一項不小的任務。

讓我們從一個範例開始

Larry 撰寫了一個網站,使用者可以在其中訂購他們客製化的獨角獸。

一般工作流程(我們將其稱為「順利路徑」)如下所示

  • 建立帳戶
  • 設定獨角獸
  • 將其添加到購物車
  • 結帳並付款
  • 提供關於獨角獸的回饋

編寫一個大型 Selenium 腳本來執行所有這些操作是很誘人的——許多人會嘗試。抵制這種誘惑!這樣做將導致測試 a) 花費很長時間,b) 會受到一些常見的頁面渲染時序問題的影響,以及 c) 如果測試失敗,它不會給您一個簡潔、「一目了然」的方法來診斷問題所在。

測試此情境的首選策略是將其分解為一系列獨立、快速的測試,每個測試都有一個「存在理由」。

讓我們假設您要測試第二步:設定您的獨角獸。它將執行以下操作

  • 建立帳戶
  • 設定獨角獸

請注意,我們跳過了其餘步驟,我們將在此測試完成後,在其他小型、離散的測試案例中測試其餘的工作流程。

首先,您需要建立一個帳戶。在這裡,您需要做出一些選擇

  • 您要使用現有帳戶嗎?
  • 您要建立新帳戶嗎?
  • 在開始設定之前,是否需要考慮此類使用者的任何特殊屬性?

無論您如何回答這個問題,解決方案都是將其作為測試的「設定資料」部分。如果 Larry 公開了一個 API,使您(或任何人)能夠建立和更新使用者帳戶,請務必使用它來回答這個問題。如果可能,您希望僅在您「手邊」有一個使用者(您可以直接使用其憑證登入)後才啟動瀏覽器。

如果每個工作流程的每個測試都從建立使用者帳戶開始,則每個測試的執行時間將增加幾秒鐘。呼叫 API 和與資料庫交談是快速的「無頭」操作,不需要開啟瀏覽器、導航到正確的頁面、點擊和等待表單提交等昂貴的過程。

理想情況下,您可以使用一行程式碼來解決此設定階段,該程式碼將在啟動任何瀏覽器之前執行

// Create a user who has read-only permissions--they can configure a unicorn,
// but they do not have payment information set up, nor do they have
// administrative privileges. At the time the user is created, its email
// address and password are randomly generated--you don't even need to
// know them.
User user = UserFactory.createCommonUser(); //This method is defined elsewhere.

// Log in as this user.
// Logging in on this site takes you to your personal "My Account" page, so the
// AccountPage object is returned by the loginAs method, allowing you to then
// perform actions from the AccountPage.
AccountPage accountPage = loginAs(user.getEmail(), user.getPassword());
  
# Create a user who has read-only permissions--they can configure a unicorn,
# but they do not have payment information set up, nor do they have
# administrative privileges. At the time the user is created, its email
# address and password are randomly generated--you don't even need to
# know them.
user = user_factory.create_common_user() #This method is defined elsewhere.

# Log in as this user.
# Logging in on this site takes you to your personal "My Account" page, so the
# AccountPage object is returned by the loginAs method, allowing you to then
# perform actions from the AccountPage.
account_page = login_as(user.get_email(), user.get_password())
  
// Create a user who has read-only permissions--they can configure a unicorn,
// but they do not have payment information set up, nor do they have
// administrative privileges. At the time the user is created, its email
// address and password are randomly generated--you don't even need to
// know them.
User user = UserFactory.CreateCommonUser(); //This method is defined elsewhere.

// Log in as this user.
// Logging in on this site takes you to your personal "My Account" page, so the
// AccountPage object is returned by the loginAs method, allowing you to then
// perform actions from the AccountPage.
AccountPage accountPage = LoginAs(user.Email, user.Password);
  
# Create a user who has read-only permissions--they can configure a unicorn,
# but they do not have payment information set up, nor do they have
# administrative privileges. At the time the user is created, its email
# address and password are randomly generated--you don't even need to
# know them.
user = UserFactory.create_common_user #This method is defined elsewhere.

# Log in as this user.
# Logging in on this site takes you to your personal "My Account" page, so the
# AccountPage object is returned by the loginAs method, allowing you to then
# perform actions from the AccountPage.
account_page = login_as(user.email, user.password)
  
// Create a user who has read-only permissions--they can configure a unicorn,
// but they do not have payment information set up, nor do they have
// administrative privileges. At the time the user is created, its email
// address and password are randomly generated--you don't even need to
// know them.
var user = userFactory.createCommonUser(); //This method is defined elsewhere.

// Log in as this user.
// Logging in on this site takes you to your personal "My Account" page, so the
// AccountPage object is returned by the loginAs method, allowing you to then
// perform actions from the AccountPage.
var accountPage = loginAs(user.email, user.password);
  
// Create a user who has read-only permissions--they can configure a unicorn,
// but they do not have payment information set up, nor do they have
// administrative privileges. At the time the user is created, its email
// address and password are randomly generated--you don't even need to
// know them.
val user = UserFactory.createCommonUser() //This method is defined elsewhere.

// Log in as this user.
// Logging in on this site takes you to your personal "My Account" page, so the
// AccountPage object is returned by the loginAs method, allowing you to then
// perform actions from the AccountPage.
val accountPage = loginAs(user.getEmail(), user.getPassword())
  

您可以想像,UserFactory 可以擴展為提供諸如 createAdminUser()createUserWithPayment() 等方法。重點是,這兩行程式碼不會分散您對此測試最終目的的注意力:設定獨角獸。

頁面物件模型的複雜性將在後面的章節中討論,但我們將在此處介紹這個概念

您的測試應由從使用者角度執行的動作組成,這些動作在網站頁面的上下文中執行。這些頁面儲存為物件,其中將包含有關網頁如何組成以及如何執行動作的特定資訊——作為測試人員,您幾乎不需要關心這些資訊。

您想要什麼樣的獨角獸?您可能想要粉紅色,但不一定。紫色最近很受歡迎。她需要太陽眼鏡嗎?星星紋身?這些選擇雖然很困難,但卻是您作為測試人員的首要關注點——您需要確保您的訂單履行中心將正確的獨角獸發送給正確的人,而這一切都始於這些選擇。

請注意,在那段文字中,我們沒有提到按鈕、欄位、下拉式選單、單選按鈕或 Web 表單。您的測試也不應該!您希望像使用者試圖解決他們的問題一樣編寫程式碼。以下是一種方法(從之前的範例繼續)

// The Unicorn is a top-level Object--it has attributes, which are set here. 
// This only stores the values; it does not fill out any web forms or interact
// with the browser in any way.
Unicorn sparkles = new Unicorn("Sparkles", UnicornColors.PURPLE, UnicornAccessories.SUNGLASSES, UnicornAdornments.STAR_TATTOOS);

// Since we are already "on" the account page, we have to use it to get to the
// actual place where you configure unicorns. Calling the "Add Unicorn" method
// takes us there.
AddUnicornPage addUnicornPage = accountPage.addUnicorn();

// Now that we're on the AddUnicornPage, we will pass the "sparkles" object to
// its createUnicorn() method. This method will take Sparkles' attributes,
// fill out the form, and click submit.
UnicornConfirmationPage unicornConfirmationPage = addUnicornPage.createUnicorn(sparkles);
  
# The Unicorn is a top-level Object--it has attributes, which are set here.
# This only stores the values; it does not fill out any web forms or interact
# with the browser in any way.
sparkles = Unicorn("Sparkles", UnicornColors.PURPLE, UnicornAccessories.SUNGLASSES, UnicornAdornments.STAR_TATTOOS)

# Since we're already "on" the account page, we have to use it to get to the
# actual place where you configure unicorns. Calling the "Add Unicorn" method
# takes us there.
add_unicorn_page = account_page.add_unicorn()

# Now that we're on the AddUnicornPage, we will pass the "sparkles" object to
# its createUnicorn() method. This method will take Sparkles' attributes,
# fill out the form, and click submit.
unicorn_confirmation_page = add_unicorn_page.create_unicorn(sparkles)
  
// The Unicorn is a top-level Object--it has attributes, which are set here. 
// This only stores the values; it does not fill out any web forms or interact
// with the browser in any way.
Unicorn sparkles = new Unicorn("Sparkles", UnicornColors.Purple, UnicornAccessories.Sunglasses, UnicornAdornments.StarTattoos);

// Since we are already "on" the account page, we have to use it to get to the
// actual place where you configure unicorns. Calling the "Add Unicorn" method
// takes us there.
AddUnicornPage addUnicornPage = accountPage.AddUnicorn();

// Now that we're on the AddUnicornPage, we will pass the "sparkles" object to
// its createUnicorn() method. This method will take Sparkles' attributes,
// fill out the form, and click submit.
UnicornConfirmationPage unicornConfirmationPage = addUnicornPage.CreateUnicorn(sparkles);
  
# The Unicorn is a top-level Object--it has attributes, which are set here.
# This only stores the values; it does not fill out any web forms or interact
# with the browser in any way.
sparkles = Unicorn.new('Sparkles', UnicornColors.PURPLE, UnicornAccessories.SUNGLASSES, UnicornAdornments.STAR_TATTOOS)

# Since we're already "on" the account page, we have to use it to get to the
# actual place where you configure unicorns. Calling the "Add Unicorn" method
# takes us there.
add_unicorn_page = account_page.add_unicorn

# Now that we're on the AddUnicornPage, we will pass the "sparkles" object to
# its createUnicorn() method. This method will take Sparkles' attributes,
# fill out the form, and click submit.
unicorn_confirmation_page = add_unicorn_page.create_unicorn(sparkles)
  
// The Unicorn is a top-level Object--it has attributes, which are set here.
// This only stores the values; it does not fill out any web forms or interact
// with the browser in any way.
var sparkles = new Unicorn("Sparkles", UnicornColors.PURPLE, UnicornAccessories.SUNGLASSES, UnicornAdornments.STAR_TATTOOS);

// Since we are already "on" the account page, we have to use it to get to the
// actual place where you configure unicorns. Calling the "Add Unicorn" method
// takes us there.

var addUnicornPage = accountPage.addUnicorn();

// Now that we're on the AddUnicornPage, we will pass the "sparkles" object to
// its createUnicorn() method. This method will take Sparkles' attributes,
// fill out the form, and click submit.
var unicornConfirmationPage = addUnicornPage.createUnicorn(sparkles);

  
// The Unicorn is a top-level Object--it has attributes, which are set here. 
// This only stores the values; it does not fill out any web forms or interact
// with the browser in any way.
val sparkles = Unicorn("Sparkles", UnicornColors.PURPLE, UnicornAccessories.SUNGLASSES, UnicornAdornments.STAR_TATTOOS)

// Since we are already "on" the account page, we have to use it to get to the
// actual place where you configure unicorns. Calling the "Add Unicorn" method
// takes us there.
val addUnicornPage = accountPage.addUnicorn()

// Now that we're on the AddUnicornPage, we will pass the "sparkles" object to
// its createUnicorn() method. This method will take Sparkles' attributes,
// fill out the form, and click submit.
unicornConfirmationPage = addUnicornPage.createUnicorn(sparkles)

  

現在您已經設定了您的獨角獸,您需要繼續進行步驟 3:確保它實際運作。

// The exists() method from UnicornConfirmationPage will take the Sparkles 
// object--a specification of the attributes you want to see, and compare
// them with the fields on the page.
Assert.assertTrue("Sparkles should have been created, with all attributes intact", unicornConfirmationPage.exists(sparkles));
  
# The exists() method from UnicornConfirmationPage will take the Sparkles
# object--a specification of the attributes you want to see, and compare
# them with the fields on the page.
assert unicorn_confirmation_page.exists(sparkles), "Sparkles should have been created, with all attributes intact"
  
// The exists() method from UnicornConfirmationPage will take the Sparkles 
// object--a specification of the attributes you want to see, and compare
// them with the fields on the page.
Assert.True(unicornConfirmationPage.Exists(sparkles), "Sparkles should have been created, with all attributes intact");
  
# The exists() method from UnicornConfirmationPage will take the Sparkles
# object--a specification of the attributes you want to see, and compare
# them with the fields on the page.
expect(unicorn_confirmation_page.exists?(sparkles)).to be, 'Sparkles should have been created, with all attributes intact'
  
// The exists() method from UnicornConfirmationPage will take the Sparkles
// object--a specification of the attributes you want to see, and compare
// them with the fields on the page.
assert(unicornConfirmationPage.exists(sparkles), "Sparkles should have been created, with all attributes intact");

  
// The exists() method from UnicornConfirmationPage will take the Sparkles 
// object--a specification of the attributes you want to see, and compare
// them with the fields on the page.
assertTrue("Sparkles should have been created, with all attributes intact", unicornConfirmationPage.exists(sparkles))
  

請注意,測試人員在這個程式碼中仍然只談論獨角獸——沒有按鈕、沒有定位器、沒有瀏覽器控制項。這種應用程式建模方法允許您保持這些測試級別的命令不變,即使 Larry 下週決定他不再喜歡 Ruby-on-Rails,並決定使用最新的 Haskell 綁定和 Fortran 前端重新實作整個網站。

您的頁面物件將需要一些小的維護,以符合網站重新設計,但這些測試將保持不變。採用這種基本設計,您將希望繼續完成您的工作流程,並盡可能減少面向瀏覽器的步驟。您的下一個工作流程將涉及將獨角獸添加到購物車。您可能需要多次迭代此測試,以確保購物車正確地保持其狀態:在您開始之前,購物車中是否有超過一隻獨角獸?購物車可以容納多少隻?如果您建立多個具有相同名稱和/或功能的獨角獸,它會崩潰嗎?它只會保留現有的獨角獸還是會添加另一個?

每次您完成工作流程時,您都希望盡量避免必須建立帳戶、以使用者身分登入和設定獨角獸。理想情況下,您將能夠通過 API 或資料庫建立帳戶並預先設定獨角獸。然後,您要做的就是以使用者身分登入,找到 Sparkles,然後將她添加到購物車。

是否要自動化?

自動化總是優勢嗎?何時應該決定自動化測試案例?

自動化測試案例並不總是優勢。有時手動測試可能更合適。例如,如果應用程式的使用者介面在不久的將來會發生顯著變化,那麼任何自動化都可能需要重寫。此外,有時根本沒有足夠的時間來建立測試自動化。在短期內,手動測試可能更有效。如果應用程式的截止日期非常緊迫,目前沒有可用的測試自動化,並且必須在該時間範圍內完成測試,那麼手動測試是最佳解決方案。

上次修改時間:2024 年 9 月 10 日:透明 png favicon (#1937)[部署網站] (03705be0833)