在軟體開發生命週期中,測試套件的穩定性對於團隊的信心至關重要。本文將探討如何建立和維護對測試套件的信心,以及如何處理不穩定測試和隔離測試環境,並探討持續交付、佈署管道和功能旗標等相關概念,以確保軟體品質和交付效率。從端對端測試的價值與限制開始,逐步引導讀者理解測試套件信心的重要性,並提供評估和還原信心的實務方法。接著,文章將探討不穩定測試的常見原因和處理策略,以及如何有效隔離測試環境以減少幹擾。最後,文章將介紹持續交付和持續佈署的差異,並說明如何利用功能旗標控制新功能的釋出,以降低風險並提高靈活性。
軟體測試的信心建立與維護
在軟體開發過程中,測試是確保產品品質的關鍵步驟。然而,測試套件(test suite)的信心建立與維護卻是許多團隊所面臨的挑戰。本篇文章將探討如何建立和維護對測試套件的信心,以及其重要性。
端對端測試的價值與限制
端對端測試(end-to-end test)是一種測試方法,模擬使用者從頭到尾的操作流程,以驗證整個系統的功能是否正常。雖然端對端測試可以提供對系統整體功能的信心,但也存在一些限制。
端對端測試的例子
在某個電商系統中,端對端測試可能包括以下功能:
- 訂單處理
- 產品目錄搜尋
- 身份驗證
- 購物車功能
這些功能透過單一的端對端測試來驗證。然而,當端對端測試數量增加時,由於無關問題導致的失敗率也會增加。這會降低對測試套件的信心,並在團隊中產生許多其他問題。
測試套件的信心
想像你坐在飛機的駕駛艙中,飛行員正在進行飛行前的檢查。當檢查中的某一步驟失敗時,他卻說:「別擔心,這個步驟有時會失敗。」然後重新執行檢查步驟,這次竟然透過了。「看,我說沒問題吧。」你會對這次飛行感到放心嗎?恐怕不會。
信心的重要性
對測試套件的信心是一種資產。當測試套件變得不可預測時,其作為信心建立工具的價值就會降低。如果測試套件不能建立信心,那麼它存在的意義是什麼?許多組織忽略了測試套件應該提供的價值,反而盲目地遵循自動化測試的做法。
如何評估測試套件的信心
評估測試套件的信心更像是一種藝術而非科學。一個簡單的方法是觀察團隊成員對測試套件失敗的反應。如果測試失敗,工程師應該開始調查自己的程式碼,檢查最近的變更以及這些變更如何影響測試套件。但是,當對測試套件的信心低落時,工程師首先會做的卻是重新執行測試套件。
信心低落的跡象
- 工程師缺乏對自己變更的信任,認為變更不可能影響失敗的測試。
- 開始懷疑構建環境的問題,例如構建伺服器的變更。
這些跡象表明對測試套件的信心正在降低。
還原對測試套件的信心
還原對測試套件的信心並非一項艱鉅的任務。它需要找出不良測試的源頭,加以糾正,並提高問題的可見度。遵循測試金字塔(testing pyramid)的原則是實作這一目標的有效方法。
立即停止執行失敗的測試
當執行測試時,應立即停止執行並報告失敗,而不是繼續執行直到結束。這樣可以節省計算時間,並避免由於基礎問題導致的多個測試失敗。
分階段執行測試
將測試套件的執行分成多個階段,根據信心水平進行分組。如果單元測試(unit test)失敗,則繼續執行後續測試的價值不大。
良好的測試案例衛生
如果在生產環境中發現了錯誤,應建立新的測試案例以檢測該問題,避免同樣的錯誤再次發生。重複發現相同的錯誤會降低對測試套件和產品的信心。
提升測試套件的可信度:處理不穩定測試與隔離測試環境
在軟體開發過程中,測試套件的可信度至關重要。當測試結果不可靠時,不僅會浪費開發團隊的時間,還可能導致對軟體品質的誤判。其中,不穩定的測試(flaky tests)是常見的問題,尤其是在端對端(end-to-end)測試中。
識別與處理不穩定測試
首先,需要識別出哪些測試是不穩定的。通常,端對端測試由於其複雜性和對外部環境的依賴,更容易出現不穩定情況。記錄這些不穩定的測試,並將它們轉化為團隊的工作專案,安排時間進行改進。改進的方法包括調整測試方法、找出測試不可靠的原因,或最佳化元素查詢方式以減少記憶體消耗。
瞭解測試失敗的原因是維護測試套件的重要部分。即使每週只改進一個不穩定的測試,也是有價值的工作。如果經過大量工作後,某個測試仍然反覆失敗,且原因不在於測試案例本身,那麼可能需要考慮刪除或存檔這個測試。如果一個測試不可信,那麼它帶來的價值就值得懷疑。花在重複執行這個測試上的時間,可能會超過自動化帶來的節省。
不穩定測試的常見原因
不穩定的測試通常源於幾個主要問題:
- 測試案例理解不足:預期結果沒有考慮到某些情況。
- 資料衝突:前一次測試的資料與當前測試的預期結果相衝突。
- 載入時間變化:在端對端測試中,UI 元件的載入時間不同,可能導致超時錯誤。
隔離測試套件
當測試套件嚴重依賴整合測試和端對端測試時,測試隔離就變成了一個大問題。單元測試由於使用記憶體內整合或完全模擬的整合,因此相對容易隔離。然而,整合測試則更為棘手,通常在資料函式庫整合層面出現問題。
解決方案之一是為每個測試執行個體執行獨立的資料函式庫。這雖然可能耗費資源,但能有效避免資料衝突。另一種方法是使用隨機命名的資料函式庫,在測試開始時建立,結束後銷毀。然而,這需要確保測試完成後能夠正確清理資料函式庫。
此外,還需要考慮在測試失敗時如何處理資料函式庫。保留資料函式庫可以幫助故障排除,但需要確保在調查完成後手動刪除,以避免資源浪費。
限制端對端測試的數量
端對端測試由於與使用者介面緊密耦合,往往是最不穩定的。當介面發生小變化時,這些測試很容易受到影響。此外,在共用硬體上執行這些測試可能會遇到效能問題。因此,盡量減少端對端測試的數量,專注於更穩定、更易於維護的測試型別,是提高測試套件可信度的有效策略。
程式碼範例:使用 Docker 容器隔離測試環境
# 使用官方的 Python 映像作為基礎映像
FROM python:3.9-slim
# 設定工作目錄
WORKDIR /app
# 複製需求檔到容器中
COPY requirements.txt .
# 安裝依賴
RUN pip install --no-cache-dir -r requirements.txt
# 複製應用程式碼到容器中
COPY . .
# 執行測試命令
CMD ["pytest", "-v"]
內容解密:
- 使用官方 Python 映像:這減少了設定 Python 環境的複雜度。
- 設定工作目錄:明確指定容器內的工作目錄,避免檔案混亂。
- 安裝依賴:透過
requirements.txt安裝專案依賴,保持環境的一致性。 - 複製應用程式碼:將當前目錄下的所有檔案複製到容器的工作目錄。
- 執行測試命令:使用
pytest執行測試,並透過-v引數輸出詳細資訊。
這個範例展示瞭如何使用 Docker 容器技術隔離測試環境,提高測試的可靠性和可重複性。
品質作為調味料:測試套件的信心建立
端對端測試(End-to-End Testing)一直是個令人頭痛的問題。它們通常不可靠,卻又極為重要。以下是我能給出的最佳建議:
- 限制端對端測試的數量:只針對應用程式的關鍵路徑操作進行測試,例如電子商務網站的登入和結帳流程。
- 將端對端測試的功能性測試與效能測試分開:效能會受到測試環境中許多因素的影響,因此效能測試應該單獨進行。
- 盡可能隔離端對端測試:在同一台機器上執行兩個端對端測試可能會導致看似隨機的問題。額外的硬體成本遠低於解決這些問題的人力成本。
避免虛榮指標
當談到測試套件的信心時,人們總是會想到使用指標來衡量品質。這是一個好的反應,但你需要注意使用的指標型別。尤其是,你應該避免使用虛榮指標(Vanity Metrics)。
虛榮指標是指那些容易被操縱且不能提供使用者所需資訊的資料點。例如,「註冊使用者數量」就是一個常見的虛榮指標。擁有三百萬註冊使用者聽起來很不錯,但如果只有五個使用者定期登入,那麼這個指標就會產生誤導。
測試覆寫率:一個虛榮指標的例子
測試覆寫率(Test Coverage)是另一個常見的虛榮指標。它衡量的是測試套件中程式碼路徑被執行的比例。這個數字通常很容易透過工具獲得,並且可以成為開發人員、QA 和產品團隊之間的共同目標。但是,測試覆寫率並不一定能反映測試的品質或具體被測試的內容。
持續佈署 vs. 持續交付
大多數人不需要持續佈署(Continuous Deployment)。持續佈署是指每一次提交到主分支都會觸發自動佈署流程,將最新變更直接佈署到生產環境。
持續交付:確保應用程式始終可佈署
持續交付(Continuous Delivery)則是一種實踐,旨在確保應用程式始終處於可佈署狀態。這意味著在發布週期中,主分支或主幹分支永遠不會損壞,無論發布頻率如何。
兩者的主要區別在於,持續佈署每次提交都會自動佈署到生產環境,而持續交付則是確保程式碼可以在需要時佈署,而不必等待大規模的發布。
為什麼持續交付是必要的
持續交付是實作持續佈署的必經之路。雖然持續佈署有很多好處,但並不是所有公司都適合。持續佈署需要非常穩固的自動化測試和程式碼審查流程。
對於許多團隊和組織來說,在談論持續佈署之前,還有很多障礙需要克服。建立信心十足的測試套件和避免虛榮指標是邁向持續交付和持續佈署的重要步驟。
軟體開發中的品質管理與持續交付
在軟體開發過程中,如何確保程式碼的品質以及持續交付的能力,是許多技術團隊面臨的重要課題。當企業從傳統的版本釋出模式轉向持續佈署(Continuous Deployment)或持續交付(Continuous Delivery)時,會遇到諸多挑戰。
從傳統釋出模式到持續交付
許多技術團隊需要數週時間來佈署系統更新,而轉向持續佈署似乎是一個巨大的飛躍。對於大多陣列織來說,持續交付是一個更為實際且可行的目標。持續佈署的理念是每次程式碼提交後立即進行佈署,這是一個理想的目標,但許多組織距離實作這一目標還很遙遠。
傳統的內部流程通常圍繞著「釋出」這一概念構建。當實施持續佈署時,甚至「釋出」的概念也需要在公司內部進行改變。許多內部流程,如Sprints、專案計劃、幫助檔案、培訓等,都圍繞著這一概念進行。
佈署管道(Deployment Pipelines)
那麼,如何在不頻繁佈署的情況下,確保程式碼的可佈署性?這就需要依賴於測試套件(Testing Suite)。測試套件是團隊評估程式碼變更是否會破壞應用程式佈署能力的重要依據。持續交付的核心是建立一系列結構化、自動化的步驟,即佈署管道,以驗證程式碼變更的可行性。
佈署管道的定義
佈署管道是一系列結構化、自動化的步驟,用於驗證程式碼變更,例如執行單元測試和應用程式封裝。管道是一種自動化的方式,以驗證程式碼的可佈署性。
佈署管道的最終結果通常是產生一個包含執行應用程式所需所有程式碼的建置成品(Build Artifact)。這包括第三方函式庫、內部函式庫和實際的程式碼本身。建置成品的型別取決於所構建的系統,例如在Java生態系統中可能是JAR或WAR檔案,在Python生態系統中可能是PIP檔案或wheel檔案。
建置成品(Artifacts)
建置成品是應用程式或其元件的可佈署版本,是建置過程的最終輸出。建置成品的型別會根據佈署策略和應用程式所使用的語言而有所不同。
一個典型的佈署管道可能包含以下步驟:
- 簽出程式碼
- 對程式碼執行靜態分析,如語法檢查
- 執行單元測試
- 執行整合測試
- 執行端對端測試
- 將軟體封裝成可佈署的成品(如WAR檔案、RPM或zip檔案)
這些步驟在每次需要合併到主分支的變更時都會執行。管道作為變更品質和可佈署性的訊號,最終產生可佈署到生產環境的成品。
功能旗標(Feature Flags)
無論是採用持續佈署還是持續交付,都可以使用功能旗標來保護使用者免受每次佈署所帶來的變更。功能旗標是一種條件邏輯,允許將程式碼路徑與旗標或訊號量繫結,從而控制程式碼路徑的啟用或停用。
功能旗標的定義
功能旗標是一種條件邏輯,允許將程式碼路徑與旗標或訊號量繫結。如果未啟用,程式碼路徑則保持休眠狀態。功能旗標允許將程式碼路徑的佈署與對使用者的釋出分離。
功能旗標的值通常在資料函式庫中儲存為布林值:true表示啟用,false表示停用。在更進階的實作中,可以為特定客戶或使用者啟用功能旗標,而不對其他人啟用。
舉例來說,假設有一個新的演算法用於在訂單頁面為使用者提供推薦。使用功能旗標,可以將新演算法的佈署與其對使用者的釋出分離。下面是一個簡單的實作範例:
public class RecommendationEngine {
private boolean useNewAlgorithm = false; // 從資料函式庫或組態中取得
public List<Product> getRecommendations(User user) {
if (useNewAlgorithm) {
return newAlgorithm.getRecommendations(user);
} else {
return oldAlgorithm.getRecommendations(user);
}
}
}
功能旗標的作用
功能旗標允許開發團隊在不影響使用者的情況下佈署新功能,並在適當的時候對特定使用者或全部使用者開放新功能。這種機制大大提高了軟體開發的靈活性,並使得新功能的釋出更加可控。
5.6 執行管線
管線執行領域充斥著各種選擇和被濫用的術語。這些工具的市場宣傳導致了諸如「持續整合」等術語的誤用。這是一個讓我頗為頭痛的問題,但我會省略那些冗長的細節。因此,我將使用「管線執行器」這個術語來指代一類別允許在工作流程中根據條件執行各種步驟的工具。
使用功能旗標控制推薦引擎
class RecommendationObject(object):
use_beta_algorithm = True
def run(self, customer):
if self.use_beta_algorithm == True:
return BetaAlgorithm.run()
else:
return AlphaAlgorithm.run()
class AlphaAlgorithm(object):
# 目前的實作細節
class BetaAlgorithm(object):
# 先前的實作細節
內容解密:
RecommendationObject類別使用use_beta_algorithm變數來決定是否執行BetaAlgorithm或AlphaAlgorithm。- 這種方法允許在不佈署新程式碼的情況下更改應用程式的行為,僅需更新資料函式庫中的值即可。
- 功能旗標使得在新演算法出現問題時,可以輕易地回復變更。
管線執行器的重要性
圖5.2展示了一個管線的例子。流行的工具包括Jenkins、CircleCI、Travis CI和Azure Pipelines等。
定義:
管線執行器是一類別允許在工作流程中根據條件執行各種步驟的工具。它們通常具有與程式碼倉函式庫、構件倉函式庫和其他軟體構建工具的整合。
選擇合適的管線執行器
大多數管線執行器的功能相似,在整合、鉤子等方面具有相當的功能。最終,這些管線仍然執行由組織編寫的程式碼。如果測試指令碼不穩定或不一致,無論使用什麼構建工具,都無法使測試指令碼突然變得毫無錯誤。
管線設計的最佳實踐
- 將管線分成獨立的階段,以便快速獲得開發人員的反饋並協助定位問題。
- 確保每個階段都有明確的成功訊號,例如產生一個構建構件作為管線的最後一步。
圖表說明:管線流程範例
@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle
title 軟體測試套件信心建立與維護
package "資料視覺化流程" {
package "資料準備" {
component [資料載入] as load
component [資料清洗] as clean
component [資料轉換] as transform
}
package "圖表類型" {
component [折線圖 Line] as line
component [長條圖 Bar] as bar
component [散佈圖 Scatter] as scatter
component [熱力圖 Heatmap] as heatmap
}
package "美化輸出" {
component [樣式設定] as style
component [標籤註解] as label
component [匯出儲存] as export
}
}
load --> clean --> transform
transform --> line
transform --> bar
transform --> scatter
transform --> heatmap
line --> style --> export
bar --> label --> export
note right of scatter
探索變數關係
發現異常值
end note
@enduml此圖示展示了一個典型的構建管線流程,每個步驟都檢查是否成功,如果任何步驟失敗,則構建失敗並離開。只有當提交透過所有測試時,才會生成一個構件進行佈署。
管理測試基礎設施的重要性
在軟體開發過程中,測試基礎設施的管理往往被忽視。測試環境的組成包括了多個關鍵元件,如持續整合伺服器、產出的構件儲存、測試伺服器、被測試的原始碼、執行的測試套件,以及測試套件所需的函式庫和依賴項。這些元件跨越了多個責任線,需要妥善管理。
測試環境的組成
- 持續整合伺服器:負責執行自動化測試,確保程式碼變更的安全性。
- 產出的構件儲存:儲存由管道產生的構件,供佈署和其他流程使用。
- 測試伺服器:提供執行測試的環境,需要與生產環境相似。
- 被測試的原始碼:開發人員提交的程式碼,需要經過測試驗證。
- 執行的測試套件:一系列自動化測試,驗證程式碼的品質。
- 函式庫和依賴項:測試套件執行所需的外部資源。
內容解密:
這些元件共同構成了測試環境的基礎。其中,持續整合伺服器是核心,負責排程和執行測試。產出的構件儲存和測試伺服器為測試提供了必要的資源。被測試的原始碼是測試的物件,而執行的測試套件則是驗證程式碼品質的關鍵。最後,函式庫和依賴項確保了測試的順利進行。
管理測試基礎設施的挑戰
由於測試環境涉及硬體和多個團隊的協作,其管理往往變得複雜。維運團隊通常負責管理硬體資源和網路存取許可權,但開發團隊也需要參與測試基礎設施的管理。
誰應該負責管理測試環境?
筆者認為,維運團隊應該擁有測試環境的管理權。理由如下:
- 生產環境的知識:維運團隊對生產環境有深入的瞭解,可以確保測試環境與生產環境的一致性。
- 靜態依賴項的管理:維運團隊可以管理資料函式庫版本等靜態依賴項,確保測試環境的穩定性。
內容解密:
維運團隊對生產環境的瞭解使得他們能夠更好地管理測試環境。例如,當生產環境進行了補丁升級時,維運團隊可以確保測試環境也進行相應的更新,以保持一致性。
持續整合與自動化測試
持續整合是一種軟體開發實踐,要求開發人員定期將程式碼變更合併到主幹分支。持續整合伺服器會執行自動化測試,以確保程式碼變更的安全性。
內容解密:
持續整合伺服器(如Jenkins、Circle CI等)會執行一系列自動化測試,以驗證程式碼變更是否安全。如果測試透過,則允許將程式碼合併到主幹分支。
內容解密:
這些書籍提供了關於持續整合和持續交付的深入知識,有助於讀者更好地理解軟體開發的最佳實踐。