跳轉到內容

持續整合/驗證軟體開發

來自華夏公益教科書

單元測試概述

[編輯 | 編輯原始碼]

在計算機程式設計中,單元測試是一種方法,透過該方法測試原始碼的各個單元以確定它們是否適合使用。單元是應用程式中最小的可測試部分。在程序式程式設計中,單元可以是單個函式或過程。在面向物件程式設計中,單元通常是方法。單元測試由程式設計師或偶爾由白盒測試人員在開發過程中建立。在 Java 世界中,我們有許多流行的選擇來實現單元測試,JUnit 和 TestNG 可以說是最流行的選擇。本文中提供的示例將使用 TestNG 語法和註釋。

傳統上(“傳統”是指它們相對簡短的歷史),單元測試被認為是非常簡單的測試,用於驗證軟體方法的基本輸入和輸出。雖然這可能是真的,並且這種簡單的測試可以提供一定價值,但單元測試可以實現更多。事實上,不僅可能,而且建議我們在單元測試框架內實現我們的大部分使用者驗收、功能以及可能的一些非功能測試。為了進一步提高質量,我們可以用單元測試來增強驗收。[6]Dean Leffingwell,敏捷軟體需求雖然我個人從未成為測試驅動開發的粉絲(我認為測試驅動開發所需的假設不允許真正的迭代方法),但我確實相信與開發並行建立單元測試會產生更高質量的軟體。在敏捷的世界中,這意味著在沒有相應的單元測試的情況下,任何功能需求(或使用者故事)都不被視為完全實現。這種對單元測試的嚴格看法可能有點極端,但並非毫無道理。

開發人員可能編寫的第一個單元測試可能非常簡單,以至於幾乎毫無用處。它可能看起來像這樣。

給定一個方法

public int doSomething (int a, int b) {        return c;}

一個簡單的單元測試可能看起來像這樣

public class MyUnitTests {    @Test    public void testDoSomething() {        assertEquals(doSomething(1, 2), expectedResult);    }}

給定一個非常簡單的方法,開發人員能夠斷言,本質上,a + b = c。這很容易編寫,並且幾乎沒有開銷,但它實際上不是一個非常有用的單元測試。

早期嘗試自動化功能測試

很久以前,我參與了一個專案,管理層在該專案中投入了大量時間和培訓來嘗試實施自動化測試。所選工具是 Rational Robot™(現為 IBM 產品)。Robot 等工具背後的理念是,測試建立者可以錄製測試宏,記錄驗證點並稍後重播宏,並記錄測試結果。Rational Robot 和 WinRunner 等工具試圖用錄製指令碼替換人工測試人員。這些自動化指令碼可以使用指令碼語言編寫,或者更常見的是透過記錄滑鼠移動、單擊和鍵盤操作來編寫。在這方面,這些測試自動化工具透過使用者介面允許黑盒測試。

在這個過度簡化的自動化測試檢視中,測試實現存在太多後勤問題,以至於不切實際。對使用者介面進行任何微小的更改都可能導致測試指令碼崩潰。負責維護這些自動化指令碼的人員經常發現自己花費更多時間維護測試,而不是將它們用於實際的應用程式測試。

Rational Robot 及其類似工具依然存在,但我指的是它們過去時態,因為在我看來,此類工具已被證明是失敗的。我這麼說,是因為我個人花了很多時間在這樣的工具中建立自動化指令碼,而且我後來很沮喪地得知它們不會被使用,因為隨著專案進展,大量介面程式碼會發生變化。此類變化是完全預期的,然而,錄製的自動化測試並不適合迭代開發環境或正在進行的專案。

使用單元測試框架自動化功能測試

大多數軟體專案,尤其是在任何敏捷環境中,都會經歷頻繁的更改和重構。如果傳統的單流程瀑布模型有效,那麼前面提到的那種錄製測試指令碼可能會正常工作,儘管好處很小。

但現在應該眾所周知的是,傳統的單流程瀑布模型已經失敗,我們生活在一個迭代/敏捷的世界中。因此,我們的自動化測試必須同樣適合持續變化。由於功能單元測試與白盒和黑盒級別的需求密切相關,因此開發人員,而不是測試人員,在建立自動化測試中起著不可或缺的作用。

為了實現這種級別的單元測試,必須有一個測試框架。這需要一些前期工作,建立此類框架的細節超出了本文的範圍。此外,測試框架的需求會因專案而異。

測試夾具成為複雜功能單元測試的重要組成部分。測試夾具是一個類,它包含執行此類單元測試所需的所有設定。它提供可以建立通用物件的方法(例如,測試伺服器和模擬介面)。測試夾具中包含的詳細資訊特定於每個專案,但一些常見方法包括測試設定、模擬和模擬物件建立和銷燬,以及宣告任何要在所有單元測試中使用的通用功能。為了更詳細地介紹測試夾具的建立,需要更多資訊,這裡無法提供。


鑑於在建立複雜單元測試時看似極端的開銷,我們可能會開始質疑其價值。毫無疑問,建立一個用途廣泛且有用的單元測試框架(包括測試夾具,它包含模擬執行環境以進行測試所需的所有必要物件和設定)需要大量前期成本。鑑於人工功能和使用者驗收測試仍然是專案的必要條件,似乎可能存在工作重疊。

但事實並非如此。

透過對一個可靠的單元測試框架進行一些前期建立,我們可以努力使建立單元測試變得簡單。我們甚至可以要求在允許任何功能需求實現(或工單)被視為完成之前,為其建立單元測試。此外,當我們發現潛在的功能問題時,我們有機會立即引入新的測試!“下面討論的硬體系統、軟體程式和一般質量保證系統控制對於醫療器械的自動化製造至關重要。軟體和相關裝置的系統驗證將確保符合 QS 規範;並減少混淆,提高員工士氣,降低成本,提高質量。此外,適當的驗證將使自動化生產和質量保證裝置順利地整合到生產運營中。醫療器械及其生產所使用的製造過程從簡單到非常複雜。因此,QS 規範需要並且是一個靈活的質量體系。隨著越來越多的裝置製造商轉向自動化生產、測試/檢驗和記錄系統,這種靈活性是寶貴的。[1]

什麼是好的單元測試?

托馬斯·H·法里斯在其著作《安全可靠的軟體》中描述了單元測試:軟體測試可以在軟體模組或單元完成時進行。單元測試對於在單元完成時測試單元非常有效,此時其他單元或元件尚未完成。測試仍然需要完成,以確保應用程式在所有軟體單元或元件一起執行時按預期工作 [2]。這是一個開始,但單元測試可以實現更多!法里斯繼續分解出許多不同類別的軟體 [3]

  • 黑盒測試
  • 單元測試
  • 整合測試
  • 系統測試
  • 負載測試
  • 迴歸測試
  • 基於需求的測試
  • 基於程式碼的測試
  • 基於風險的測試
  • 臨床測試


傳統上,這可能是一個合理的分解。但是,如果明智地使用,並在適當的框架下,我們可以使用模擬真實生產環境的有效單元測試來執行黑盒測試、整合測試、系統測試、負載測試、迴歸測試、基於需求的測試、基於程式碼的測試、基於風險的測試和臨床測試。本文的目的是不深入探討如何技術細節(解釋單元測試框架、夾具、模擬物件和模擬將需要更多空間)。相反,我只想指出由此產生的好處。為了實現這些好處,您的軟體團隊需要深入瞭解單元測試。這需要一些時間,但這將是非常值得的。

讓單元測試超越我們傳統意義上的“單元測試”,並更進一步,自動化功能測試是一個好主意)。這是另一個團隊成員經常(錯誤地)覺得沒有足夠時間完成所有工作的領域。

正如 Harris 所言:軟體測試和缺陷修復非常耗時,通常會佔用軟體組織所有工作量的超過一半 [3]。測試不必等到整個產品完成才開始;在每個程式碼迭代完成後,可以對迭代設計和開發的程式碼進行測試。在開始驗證之前,專案計劃或其他測試計劃文件應討論總體策略,包括要執行的測試型別、要執行的具體功能測試,以及指定測試目標,以確定產品何時準備充分以供釋出和分發 [4]。Harris 指出在我們受 FDA 監管的環境中非常重要的一點是,我們必須記錄和描述我們的測試。為了使我們的單元測試發揮作用,我們必須提供每個測試做什麼(即,它具體測試什麼)以及結果的文件。單元測試和可用工具(整合到我們的持續整合環境中)的優點在於,此過程以一種簡化的方式實現了可追溯性和測試條件的再現,這對於我們的 510k 至關重要!

為了實現這一切,我們需要一個能夠進行應用程式啟動、模擬、模擬物件、模擬介面和臨時資料持久化的測試框架。這一切聽起來比實際情況要複雜得多,所以請不要擔心:好處遠大於成本。

單元測試的價值是什麼?

[編輯 | 編輯原始碼]

持續整合中的即時反饋:開發人員信心

我們經常將測試視為僅在軟體開發期間的特定時間進行的活動。最糟糕的情況是,軟體測試在開發完成後進行(這時才知道開發遠未完成)。在其他更加積極的環境中,它可能在每次迭代結束時進行。我們可以做得更好!如何讓複雜的單元測試在每次程式碼更改時持續進行驗證?在每次程式碼更改時執行完整的迴歸測試是可能的。聽起來像是很大的開銷,但事實並非如此。對專案來說,真正的成本不是對複雜的功能單元測試的忽視;危險在於我們把測試推遲到太晚,無法應對在某個預定的測試階段發現的關鍵問題。

消滅一個專案最有效的方法是將它組織起來,使測試成為一個對專案成功至關重要的活動,以至於我們不允許測試做它應該做的事情:在上線之前發現缺陷。

在最基本的層面上,持續整合構建環境只做一件事:它執行我們告訴它執行的任何指令碼。為此,重要的是 CI 構建執行單元測試,並且任何單個單元測試的失敗都被視為持續整合構建的失敗。Hudson 或 Jenkins-CI 等工具的強大之處在於,我們可以告訴它執行任何我們想要的東西,記錄結果,保留構建工件,執行第三方評估工具並報告結果。透過整合我們的軟體版本控制系統(例如,Subversion、Git、Mercurial、CVS 等),我們知道與特定構建相關的更改集。它可以配置為以我們想要的任何間隔生成構建(每晚、每小時、每次程式碼提交時等)。當測試失敗時,我們立即知道哪些更改集參與了。

就我個人而言,每次我進行任何重要的程式碼提交時,我都會做的第一件事就是檢查 CI 構建是否成功。如果我破壞了構建,我就會著手解決問題(如果我不能快速解決問題,我會撤回我的更改集,以便 CI 構建繼續執行,直到我修復了問題)。輕鬆重構

作為開發人員,重構可能是一件很可怕的事情。重構可能是引入嚴重缺陷的最有效方法,同時又做了一些看似無害的事情。但是,透過徹底的單元測試在每次提交的軟體更改集中執行完整的迴歸測試,開發人員可以確信他們簡單的程式碼更改不會引入缺陷。我們執行持續整合構建來執行我們的測試,原因有很多,其中最重要的原因之一是提醒開發人員他們的更改可能破壞了構建。

作為開發人員,我努力避免破壞持續整合構建。然而,當我確實破壞它時,我很高興知道導致問題的原因立即被發現了!當一個缺陷的發現直到開發階段結束才被發現時,修復缺陷的成本會變得高得多!每次程式碼更改都進行迴歸測試

我說的“重複”與可重複不同。重複測試的基本好處是,測試可以透過自動化執行的次數比人工測試人員多得多。有時,即使沒有相關的程式碼更改,而且讓我們感到驚訝的是,我們會看到一個測試突然失敗,而它之前多次成功。發生了什麼?


最難修復(更不用說找到)的軟體缺陷是那些不一致發生的缺陷。資料庫鎖定問題、記憶體問題、死鎖錯誤、記憶體洩漏和競爭條件會導致此類缺陷。這些缺陷很嚴重,但如果我們從未檢測到它們,我們如何修復它們?

如前所述,必須進行超出我們傳統意義上認為的“單元測試”的單元測試,並進一步進行幾步,自動化功能測試)。這是一個團隊成員經常(錯誤地)認為沒有足夠的時間來處理單元測試建立的領域。但是,在適當的框架下,單元測試的建立並不需要讓人感到不堪重負。

另一個偶爾出現的問題與軟體版本控制系統的誤用有關。許多開發人員知道,由於一個開發人員踩了另一個開發人員的修改而導致的意外程式碼更改會帶來的挫折感。雖然在正確使用的版本控制環境中,這是一個罕見的問題,但它確實仍然會發生,並且單元測試可以在構建時快速揭示此類問題。併發測試

併發測試很棘手,並且在併發測試中,功能單元測試的重複和快速性質可以在人工測試人員無法做到的地方發揮作用。我個人目睹過很多次 CI 構建突然失敗,沒有明顯的原因。沒有與特定故障點相關的程式碼提交,但曾經成功的單元測試突然失敗了?為什麼?

這可能會發生(並且確實會發生),因為併發問題本質上是隨機的。有時它們發生的可能性很小,以至於我們在正常的測試過程中從未見過它們。但是,當持續整合環境每天執行併發測試數十次時,我們會增加發現隱藏和威脅性問題的可能性。此外,單元測試可以模擬多個併發使用者和程序,即使是一支人工測試人員隊伍也無法做到。可重複和可追溯的測試結果

這是使我們的單元測試符合我們在質量系統中制定的標準的關鍵,以便我們可以將它們用作我們提交的一部分(參見以下關於受監管環境需求的部分)。如果我們要付出努力,並且我們已經知道單元測試會導致軟體質量的提高,那麼我們為什麼不想包含這些測試結果呢?

我們的持續整合伺服器可以而且應該用來儲存我們的單元測試結果,與它執行的每次構建並排儲存。

當然,這不僅僅是 FDA 監管環境中的一個優勢。在任何軟體專案中,都很難重新建立發現缺陷的條件。透過 CI 構建在已知環境下使用已知檔案集(CI 構建工具從版本控制系統中提取)執行我們的構建和測試指令碼,可以在精確和特定條件下執行測試。


上面列出的功能單元測試的許多優點只有在單元測試與設計和開發一起編寫時才能獲得(測試驅動方法除外)。開發團隊必須在設計和活動進行時開發和觀察測試結果。這對質量保證團隊也有好處,正如 Dean Leffingwell 指出:全面的單元測試策略可以防止 QA 和測試人員將大部分時間花在尋找和報告程式碼級錯誤上,並允許團隊將重點轉移到更高級別的系統測試挑戰上。事實上,對於許多敏捷團隊來說,新增全面的單元測試策略是他們向真正的敏捷過渡的關鍵轉折點——並且是確定整體系統質量的“最划算的投資”。[7] 此外,功能單元測試的主要優勢可能是為開發團隊提供的即時反饋。一位作者將每次軟體更改後執行的單元測試稱為“提交測試”。[8]。針對每次簽入執行的提交測試為我們提供了有關最新構建中問題以及應用程式中小型錯誤的及時反饋。[8] - Jez Humble,David Farley,持續整合專案單元測試,它應該提供大量的覆蓋率(至少 80%),為團隊提供內建的軟體更改提交驗收標準。如果開發人員因程式碼更改導致 CI 構建失敗,那麼立即就知道相關的更改不符合最低接受標準,需要緊急關注。

Humble 和 Farley 繼續說道,關鍵的是,開發團隊必須立即響應在正常開發過程中發生的已接受測試的故障。他們必須判斷故障是由於引入了迴歸錯誤、對應用程式行為的故意更改,還是測試本身的問題。然後,他們必須採取適當的措施,使自動化的驗收測試套件再次透過。[8] - Jez Humble、David Farley,《持續整合:規管環境對 21 CFR Part 820(第 C 部分 - 設計控制)的要求》: (f) 設計驗證。每個製造商應建立和維護驗證裝置設計的程式。設計驗證應確認設計輸出滿足設計輸入要求。設計驗證的結果,包括設計、方法、日期和執行驗證人員的識別,應記錄在 DHF 中。[5]

簡而言之,我們的功能單元測試必須成為 DHF 的一部分,我們必須記錄每個測試,每個測試結果(成功或失敗),並將測試和結果與特定的軟體版本聯絡起來。在一個持續整合環境中,構建和構建結果(包括測試結果)儲存在伺服器上,並從我們的 DHF 中進行標記和連結,這使得此過程變得非常容易。事實上,在手動執行和記錄測試結果時,有時是一項乏味的任務,現在變得相當方便。

設計驗證也是如此:(g) 設計驗證。每個製造商應建立和維護驗證裝置設計的程式。設計驗證應在定義的操作條件下,對初始生產單位、批次或批次,或其等效物進行。設計驗證應確保裝置符合定義的使用者需求和預期用途,並且應包括在實際或模擬使用條件下對生產單位進行測試。設計驗證應包括軟體驗證和風險分析(如適用)。設計驗證的結果,包括設計、方法、日期和執行驗證人員的識別,應記錄在 DHF 中。[5] 由於我們的 CI 環境在給定時間點打包構建和測試條件,我們可以輕鬆地滿足 21 CFR 820 第 C 部分第 820.30 條 (f) 和 (g) 中規定的要求。我們只需讓我們的 CI 環境做它最擅長的事情,而人類測試員可能需要花費數小時才能準確地完成的事情。記錄方法

如上所述,所有這些測試確實對建立良好的軟體非常有幫助。但是,如果沒有在我們的 FDA 監管環境中明智地使用這些測試,它們在任何可審計能力方面都沒有用。我們有必要記錄我們在標準操作程式和工作說明中使用和記錄單元測試的方法,並且以與記錄任何手動驗證和確認測試活動相同的方式記錄此方法。

為此,有必要使我們的單元測試及其輸出成為我們設計歷史檔案 (DHF) 的一部分。每個測試都必須可追溯,這意味著單元測試被賦予唯一的識別符號。這些唯一識別符號可以使用我們按邏輯單元(例如,按功能區域)組織測試並將測試按順序標記的方法輕鬆分配。標記和追溯測試

我過去使用的一種方法是分配一些高階數字識別符號和用於特定測試的二級子識別符號。例如,我們可能具有以下功能區域:使用者會話、審計日誌、資料輸入、資料輸出和 Web 使用者介面測試(這些是功能區域的通用示例)。對於這些功能區域,我將使用測試命名註釋,使用以下高階識別符號標記每個測試:1000:使用者會話測試 2000:審計日誌測試 3000:資料輸入測試 4000:資料輸出測試 5000:Web 使用者介面測試 在每個測試中,然後有必要更進一步,將一些順序識別符號應用於每個測試。例如,使用者測試包可能包括針對使用者登入、使用者登出、會話過期和多使用者登入併發測試等功能需求的測試。

在這種情況下,我們將標記測試如下:1000_010:使用者登入 1000_020:使用者登出 1000_030:會話過期 1000_040:多個併發使用者登入 使用 TestNG 語法以及適當的 Javadoc 註釋,很容易標記和描述測試,以便在我們的 DHF 中包含測試非常簡單。 /** * 測試基本使用者登入和使用有效使用者的會話建立。 * * @throws Exception */@Test(dependsOnMethods = {"testActivePatientIntegrationDisabled"}, groups = {"TS0005_AUTO_VE1023"})public void testActivePatientIntegrationEnabled() throws Exception { Fixture myApp new Fixture(); UserSession mySession = fixture.login(“test_user”, “test_password”); assertNotNull(mySession); asertTrue(mySession.active());} 我們為這些測試選擇的任何編號都可以,只要我們在專案級別的文件(例如驗證計劃或主測試計劃)中記錄我們對測試標記的方法即可。此類決策留給為 FDA 監管專案設計和應用質量系統的人員。正如我們大多數人現在所知,FDA 並沒有告訴我們如何去做,而是告訴我們必須建立一個良好的質量系統,透過設計來追溯或要求,將歷史記錄納入我們的 DHF,並且能夠重現構建和測試條件。

如果我把這一切聽起來太容易了,那是因為我認為它很容易。我們經常把 cGMP 指南看作是對生產力的巨大阻礙。但我們可以控制使事情變得儘可能高效。

可追溯性矩陣

[edit | edit source]

使單元測試在可審計的方式下可使用的一個關鍵因素是將它們納入可追溯性矩陣。與任何測試一樣,要求、設計元素和危害必須透過使用可追溯性矩陣相互追溯。專案團隊必須記錄要求透過規範和測試的可追溯性,以確保所有要求都已測試並正確實現(產品要求可追溯性矩陣)。[9]Thomas H. Farris,《安全可靠的軟體》 隨著每個自動化測試的標記,我們可以使用內建的 JUnit 或 TestNG 功能(以及 XSLT,如果我們願意),建立與構建編號和變更集相關聯的輸出,並在我們的跟蹤矩陣中進行追溯。我們的測試的輸出(這些測試在每次持續整合構建期間執行)可能如下所示

TEST NAME     STATUSTS0005_AUTO_VE1022     PASSTS0005_AUTO_VE1023     PASS     TS0005_AUTO_VE1024     FAILTS0005_AUTO_VE1025     SKIP…

當然,我們希望所有自動化測試都透過,但當它們失敗時,我們需要記錄它。我認為將所有測試結果放在 DHF 中沒有必要。相反,DHF 可以指向持續整合構建伺服器,在那裡,自動化測試結果與每個構建捆綁在一起。最後,在衝刺或迭代結束時,最終鎖定構建的適當測試結果被捕獲到 DHF 中並進行適當的追溯(根據標準操作程式)。

我們的 SOP 和工作說明將要求我們證明測試和測試結果的可追溯性,無論是手動測試還是自動化單元測試。正如我們一直在做的手動測試一樣,測試必須追溯到軟體需求、設計規範、危害和風險。目標只是證明我們已經測試了我們設計和實現的內容,對於自動化測試,這很容易實現!我們是否仍然需要手動測試?

是的!絕對的!手動測試仍然(並將永遠)必不可少的原因有很多:安裝確認和環境測試。手動測試和自動化測試都是有效的,並且都有價值,兩者都不應被視為對方的替代品。

我記得小時候上空手道課。有一天,我從課上回家,非常自豪,因為我已經學會了如何格擋拳擊。“用拳頭打我吧,”我對我的朋友說。

按照我的要求,他打了我胸部,我未能格擋拳頭。這個拳頭不是按照我預期的(我們在空手道課上練習的方式)打出來的。

“不,不,不!你打我錯了!”我只知道如何格擋一種拳頭,當以不同的方式被打時,我的格擋就不起作用了。對我來說,這堂空手道課突出了異常和錯誤之間的區別。自動化測試可以很好地提供錯誤測試覆蓋率。但是,當遇到意想不到的東西時,它們本身並不能提供創造力來找到問題。

我們,開發人員和測試人員,需要想出創造性的方法來攻擊我們的系統。這就是手動測試允許一定程度的“創造性”攻擊,而這些攻擊在單元測試開發期間可能不會被考慮。手動測試還能更深入地瞭解可用性和使用者互動問題。

也許更重要的是,手動測試可以提供有關一般應用程式可用性和使用者互動的反饋。

為此,在手動測試期間發現的缺陷應導致自動化測試。

其他注意事項

測試夾具

[edit | edit source]

模擬物件

[edit | edit source]

避免使用單例設計模式!

[edit | edit source]

記憶體資料庫

[edit | edit source]

記憶體 Servlet 容器

[edit | edit source]

參考資料

[edit | edit source]

[1] 裝置建議:法規和指南,軟體驗證指南,http://www.fda.gov/MedicalDevices/DeviceRegulationandGuidance

[2] 安全可靠的軟體 - 為軟體醫療裝置組織建立高效有效的質量體系,作者 Thomas H. Farris。ASQ 質量出版社,威斯康星州密爾沃基,2006 年,圖 4.9,“軟體測試型別”,第 120 頁

[3] 安全可靠的軟體 - 為軟體醫療裝置組織建立高效有效的質量體系,作者 Thomas H. Farris。ASQ 質量出版社,威斯康星州密爾沃基,2006 年,第 118 頁

[4] 安全可靠的軟體 - 為軟體醫療裝置組織建立高效有效的質量體系,作者 Thomas H. Farris。ASQ 質量出版社,威斯康星州密爾沃基,2006 年,圖 4.9,“軟體測試型別”,第 118 頁

[5] CFR - 美國聯邦法規第 21 章。子部分 C - 設計控制,第 820.30 節 設計控制

[6] 敏捷軟體需求,作者 Dean Leffingwell。Addison-Wesley。版權所有 © 2011,Pearson Education, Inc.,馬薩諸塞州波士頓,第 61 頁

[7] 敏捷軟體需求,作者 Dean Leffingwell。Addison-Wesley。版權所有 © 2011,Pearson Education, Inc.,馬薩諸塞州波士頓,第 196 頁

[8] 持續交付,作者 Jez Humble,David Farley。Addison-Wesley,版權所有 © 2011,Pearson Education, Inc.,馬薩諸塞州波士頓,第 124 頁

[9] 安全可靠的軟體 - 為軟體醫療裝置組織建立高效有效的質量體系,作者 Thomas H. Farris。ASQ 質量出版社,威斯康星州密爾沃基,2006 年,圖 4.9,“軟體測試型別”,第 123 頁

華夏公益教科書