持續整合/持續交付的軟體開發
在軟體工程中,持續整合 (CI) 實施了持續的質量控制流程——少量工作,頻繁執行。持續整合旨在透過替換完成所有開發後進行質量控制的傳統做法,來提高軟體質量並縮短交付時間。維基百科:持續整合[1]
持續整合 (CI) 工具不再僅僅是專案開發中“可有可無”的東西。作為一個花費了比我想談論更多的時間來瀏覽文件,並確保參考、可追溯性、文件版本和設計輸出在設計歷史檔案 (DHF) 中得到正確記錄的人,我希望闡明使用 CI 來自動化這種繁瑣且容易出錯的人工操作的價值。CI 不應該被視為“可有可無”的東西。相反,它是絕對必要的!

當我稱持續整合是絕對必要的時候,我的意思是 CI 工具和流程都是必不可少的。CI 工具接管了人類試圖使大量文件始終可追溯的嘗試(有時是微弱的嘗試),並迫使計算機系統做它最擅長的工作。使用 CI 工具不僅僅是那些喜歡將其納入的愛好者的深奧做法。正如你將在本章中學習的那樣,持續整合是優秀的開發團隊一直嘗試的事情,但他們往往未能利用軟體工具來簡化流程。更進一步,開發團隊可以使用 CI 工具來簡化他們以前從未夢寐以求的步驟!
持續整合是指持續編譯和構建專案樹以及持續測試、釋出和質量控制。這意味著在整個專案中,在每個階段,開發團隊都將擁有一個包含至少部分文件和測試的構建版本。CI 構建用於在特定時間執行特定任務。一般來說,這意味著構建是在與系統實際生產環境非常接近的環境中執行的。此外,CI 環境應該用於提供關於構建效能、測試、版本控制系統和工單系統的統計反饋。在開發環境中,團隊可以使用版本控制工具(例如 Subversion)來連結到工單。這樣,任何 CI 構建都將與特定的變更集相關聯,從而提供與問題、需求和最終的追蹤矩陣的連結。為此,正確使用 CI 環境可以成為 DHF。
開發團隊應該嘗試頻繁地執行持續整合構建,以確保提交和構建之間沒有額外的版本控制更新視窗,並且任何錯誤都可以在開發人員注意到並立即糾正之前出現。這意味著對於正在開發的專案,應該配置一個檢查來及時觸發構建。同樣,開發人員提交程式碼變更集時,一般來說最好驗證他們自己的變更集不會破壞持續整合構建。
請允許我說明一下“構建”這個詞。大多數軟體工程師認為構建是編譯和連結的輸出。我建議擺脫這種狹隘的定義並將其擴充套件。一個“構建”是完成(在編譯器意義上和更廣泛意義上)所有成功產品交付所必需的方面。CI 工具執行開發團隊告訴它執行的任何指令碼。因此,團隊可以自由地使用 CI 工具作為構建管理器(抱歉,構建經理,我並不是要威脅你的工作)。它可以編譯程式碼,建立安裝程式,捆綁任何和所有文件,建立釋出說明,執行測試並向團隊成員通知其進度。
雖然有很多工具,但我將重點介紹最流行的工具之一,Jenkins CI。這是最受歡迎的(開源)工具之一。Jenkins CI(以前被稱為 Hudson 的產品的延續)允許透過以下方式進行持續整合構建:
- 它與流行的構建工具(ant、maven、make)整合,以便它可以執行適當的構建指令碼,在與生產環境非常接近的環境中進行編譯、測試和打包。
- 它與版本控制工具整合,包括 Subversion,以便可以根據主幹中的專案位置設定不同的專案。
- 它可以配置為透過時間和/或變更集自動觸發構建。(例如,如果在專案的 Subversion 儲存庫中檢測到新的變更集,則會觸發新的構建。)
- 它報告構建狀態。如果構建失敗,可以配置它透過電子郵件通知個人。
上面的螢幕截圖展示了 Jenkins CI(或任何 CI 工具)的主頁可能是什麼樣子。可以將其配置為允許不同級別的使用者登入,並且可以按專案進行設定。此主頁列出了當前處於活動狀態的所有專案,以及狀態(有關構建的一些詳細資訊)和側面的某些配置連結。這些連結可能對普通使用者不可用。
單擊任何專案(“作業”)連結,可以檢視更多有關構建歷史記錄和狀態的詳細資訊。此影像向我們展示了 CI 環境中的概述螢幕可能是什麼樣子,但真正體現 CI 環境設定良好的打包益處的是,在詳細的專案級別。
在第一個問題被提出之前,先回答一下:是的,軟體專案應該有持續整合 (CI) 構建。這適用於大型團隊的專案,也適用於小型團隊的專案。雖然 CI 構建對於大型團隊的協作非常有用,但即使是最小的團隊也能從中獲得巨大的價值。即使是單獨工作的軟體工程師也能從持續整合構建中獲益。
在第一個問題被提出之前,先回答一下:是的,軟體專案應該有持續整合 (CI) 構建。這適用於大型團隊的專案,也適用於小型團隊的專案。雖然 CI 構建對於大型團隊的協作非常有用,但即使是最小的團隊也能從中獲得巨大的價值。即使是單獨工作的軟體工程師也能從持續整合構建中獲益。
好吧,我開始重複了,但追蹤是一件好事,有了這種設定,我就可以在各個地方進行追蹤!有了所有流程到位,從我們的標準操作程式到工作說明、用例和需求、工單到變更集,我們如何知道參與專案的團隊成員是否真正按照程式操作?CI 環境,至少在程式碼的持續開發方面,為所有其他活動提供了一個單一的概覽點。從這裡,我們可以看到變更集、工單、構建狀態和測試覆蓋率。使用適當的外掛,我們甚至可以深入瞭解正在開發的程式碼的質量。
當然,為了實現這一點,我們需要明智地使用工單系統。使用 Redmine 以及幾乎所有好的工單系統,我們可以捕獲軟體需求和軟體設計的元素作為父工單。這些父工單有一對多的子工單,子工單本身可以有子工單。父工單隻有在子工單完成時才能關閉或標記為完成。
在最基礎的層面上,Hudson 只做一件事:它執行我們告訴它執行的任何指令碼。Hudson 的強大之處在於我們可以告訴它執行任何我們想執行的指令碼,記錄結果,儲存構建工件,執行第三方評估工具並報告結果。透過 Subversion 整合,Hudson 將顯示與特定構建相關的變更集。它可以配置為以我們想要的任何時間間隔(夜間、每小時、每次程式碼提交時等)生成構建。
就我個人而言,每次我進行任何重要的程式碼提交時,我都會做的第一件事就是檢查 CI 構建是否成功。如果我破壞了構建,我就會著手解決問題(如果我無法快速解決問題,我會回滾我的變更集,以便 CI 構建繼續工作,直到我修復了問題)。
Jenkins 可以配置為在構建結果時向團隊成員傳送電子郵件,並且設定它以便在構建中斷時向開發人員傳送電子郵件可能很有用。
一般來說,您的專案的構建工件不應是程式碼庫的一部分(此規則有例外)。構建工件應該放在您的持續整合構建工具中,在那裡任何團隊成員都可以檢視和使用它們。這些工件包括測試結果、編譯的庫和可執行檔案。通常,釋出的構建是在某個開發人員的機器上本地建立的。這是一個嚴重的問題,因為我們無法很好地知道實際用於建立該構建的檔案。是否有配置更改?是否引入了錯誤?是否使用了錯誤的檔案版本?
雖然開發人員有充分的理由在本地生成和測試構建,但用於測試(或更重要的是釋出)的正式構建絕不能在本地建立。永遠不要。
構建工件未簽入原始碼控制程式碼庫的原因有很多,但最主要的原因是,我們不想對這些專案構建的環境做出任何假設。構建工件應保留在我們的 CI 環境中,在這裡我們知道生成構建的條件。
此外,由於這些構建在 CI 構建環境中易於訪問且已標記,任何團隊成員都可以輕鬆訪問任何給定的構建。特別是,可能需要使用特定構建來重新建立已釋出用於內部或外部使用的構建中的問題。由於我們知道構建的標籤(賦予它的版本號)以及構建的程式碼庫變更集號(因為我們的構建和安裝指令碼包含它),我們準確地知道從 CI 構建伺服器中提取哪個構建以重新建立必要的條件。
4. 單元測試反覆執行(反覆執行)
開發人員應盡一切努力防止 CI 構建中斷。當然,這並不總是有效。我由於以下原因無數次地破壞了 CI 構建:- 我忘記新增必要的庫或新檔案 - 我忘記提交配置更改 - 我不小心將更改包含在我的變更集中 - 它在本地工作但在 CI 構建伺服器上構建時失敗(這就是 CI 構建伺服器應儘可能模仿生產環境的原因) - 單元測試在本地工作但在 CI 構建伺服器上失敗,因為存在一些環境差異
如果沒有帶有良好單元測試的 CI 構建環境,此類問題只會在此後被發現,或者被其他沮喪的團隊成員發現。在這方面,CI 構建讓我們免受許多頭痛。
最難修復(更不用說找到)的軟體缺陷是不一致發生的缺陷。資料庫鎖定問題、記憶體問題和競爭條件會導致此類缺陷。這些缺陷很嚴重,但如果我們從未發現它們,我們如何修復它們呢?
最好擁有超越我們傳統上認為的“單元測試”的單元測試,並可能進一步採取一些步驟,例如自動化功能測試。這是團隊成員經常(錯誤地)覺得沒有足夠的時間完成所有工作的另一個領域。
隨著 Hudson 每次執行構建時執行所有這些測試,我們有時會遇到測試突然無故失敗的情況。它以前工作過,一小時前工作過,並且程式碼沒有發生任何變化,那麼這次它為什麼失敗了呢?請注意,這種情況會發生。
5. 與其他方便的功能整合,例如 Findbugs、PMD 和 Cobertura
不贅述,有很多很棒的工具可以與 Hudson 一起使用來評估程式碼是否存在潛在的錯誤、糟糕的編碼實踐和測試覆蓋率。這些工具確實很有用。使用它們。
釋出軟體時,通常會賦予其某種版本號(例如,1.0)。這很好,但它沒有告訴我們構建的具體內容。最好在發行版中包含 Subversion 變更集號,以便我們始終準確地知道構建包含什麼。我將在某個地方包含一個 build.xml(或 build.prop)檔案,其中包含發行版的版本號、Subversion 變更集號和構建日期。對於最後兩個值,這些值可以(也應該)由您的構建指令碼自動生成。
至於在 Linux/Unix 中實際使用 Subversion,所有命令都可以在命令列中使用。在 Windows 中工作時,我真的很喜歡使用 TortoiseSVN。它與 Windows 檔案資源管理器整合,顯示圖示以指示任何版本控制檔案的狀態。它還提供了一個很好的介面來檢視檔案差異(甚至檢視 Word 文件的差異)和程式碼庫歷史記錄。
長期以來,我們一直將我們的票據系統視為“錯誤跟蹤器”。以前最流行的開源工具之一 Bugzilla 甚至在其名稱中使用了“bug”一詞。但問題跟蹤並不意味著它只對跟蹤軟體缺陷有用。恰恰相反!它可以用於軟體設計和開發中的所有事情,從解決文件需求到捕獲軟體需求,再到處理軟體缺陷報告。
關於這一點,我想補充一點可能需要整篇文章才能說明。我認為最好避免使用標準文件來捕獲軟體用例、需求、危害等。透過在我們的問題跟蹤工具中捕獲與軟體專案相關的所有內容,我們可以利用 Trac 或 Redmine 等工具的功能來增強團隊協作和專案跟蹤。但我現在不會貪多嚼不爛。
當我第一次開始將我所有的想法寫下來時,我打算使用 Trac 作為我的示例 (http://trac.edgewall.org/)。Trac 是一個很棒的工具,但現在有更好的工具,那就是 Redmine (http://www.redmine.org/)。
Trac 的主要缺陷在於它並不適合(根本不適合)處理多個專案。一個 Trac 安裝只能與一個 Subversion 程式碼庫整合,並且票據系統只能處理一個專案。我仍然喜歡 Trac 用於將票據分組到衝刺中的方式,但在 Redmine 中使用子專案,可以實現類似的分組。過去,如果使用任何工具,我們使用 Bugzilla 或 Clearquest 來處理問題跟蹤。這些工具在當時非常出色,但它們與其他工具的整合度不高,也不包含維基、日曆或甘特圖等功能。(誠然,我已經很多年沒有使用 Clearquest 了,所以我真的不知道它是否已經解決了其中的一些需求。)在本地工作但在 CI 構建伺服器上失敗,因為存在一些環境差異
如果沒有帶有良好單元測試的 CI 構建環境,此類問題只會在此後被發現,或者被其他沮喪的團隊成員發現。在這方面,CI 構建讓我們免受許多頭痛。最難修復(更不用說找到)的軟體缺陷是不一致發生的缺陷。資料庫鎖定問題、記憶體問題和競爭條件會導致此類缺陷。這些缺陷很嚴重,但如果我們從未發現它們,我們如何修復它們呢?
最好擁有超越我們傳統上認為的“單元測試”的單元測試,並可能進一步採取一些步驟,例如自動化功能測試。這是團隊成員經常(錯誤地)覺得沒有足夠的時間完成所有工作的另一個領域。
隨著 Hudson 每次執行構建時執行所有這些測試,我們有時會遇到測試突然無故失敗的情況。它以前工作過,一小時前工作過,並且程式碼沒有發生任何變化,那麼這次它為什麼失敗了呢?請注意,這種情況會發生。
5. 與其他方便的功能整合,例如 Findbugs、PMD 和 Cobertura
不贅述,有很多很棒的工具可以與 Hudson 一起使用來評估程式碼是否存在潛在的錯誤、糟糕的編碼實踐和測試覆蓋率。這些工具確實很有用。使用它們。
那麼 Redmine 有什麼好的地方呢?
- 維基的力量:您的文件包含所有專案管理詳細資訊、工作說明、用例、需求等。再次說明,我認為這些資訊可以放在維基中,但對於有些人來說,這可能是他們不習慣的一步。
- 也就是說,所有開發人員設定、經驗教訓和其他非正式筆記都可以放在維基中。有一次,我花了將近 4 天的時間來跟蹤一個非常奇怪的缺陷。當我終於弄清楚所有事情時,我瞭解了很多關於其他人肯定會遇到的一個非常奇怪的問題。我建立了一個維基頁面來解釋這個問題。
- 維基的另一個強大功能是,在 Redmine(和 Trac)中,我們不僅可以連結到其他維基頁面,還可以連結到票據(問題)、專案、子專案和 Subversion 變更集。再次說明,更多跟蹤。很好。
- Subversion 整合:透過整合 Subversion 和 Redmine,我可以在這兩者之間相互連結。這些工作說明應該向團隊解釋我們如何使用這些流程,其中包括任何工單隻有在連結到 Subversion 的變更集後才能關閉(除非工單被拒絕)。Redmine 可以配置為在 Subversion 變更集提交中搜索關鍵字。例如,如果我正在檢入幾個解決問題 #501 的檔案,我可能會新增類似這樣的評論:“修正瞭如此這般,解決了問題 #501。”我們可以配置 Redmine 來尋找“fixes”這個詞並使用它。Redmine 可以將這個詞作為標記來關閉工單,並連結到我提交變更集時建立的變更集。同樣,當我們檢視 Subversion 歷史記錄時,我們會看到“#501”附加到變更集,並連結到相應的工單。跟蹤雙向工作... 太棒了!
- 多專案處理(以及與不同 Subversion 儲存庫的整合):這是我(以及其他人)轉向 Redmine 的主要原因。Trac 很好,但它只能處理單個專案。Redmine 可以處理多個專案,可以在整個公司範圍內用於所有開發工作,並且每個專案可以繫結到不同的 Subversion 儲存庫。此外,單個專案可以擁有多個子專案。這使我們能夠靈活地使用子專案來處理衝刺、特定分支版本等等。
- Hudson 整合:透過整合 Hudson,我無需離開 wiki 就可以檢視我的 CI 構建情況。不僅如此,我還可以從 wiki 或工單系統中的任何頁面連結到特定的 CI 構建。
- 完全可配置性:Redmine 中的所有內容都可以配置。是的,所有!我們甚至可以配置工單的流程。
我建議僅僅將功能需求留在軟體需求規格說明書中是不夠的。這無法提供足夠的跟蹤,也無法提供從想法到功能程式碼的清晰路徑。以下是我建議的步驟。
- 所有需求和軟體設計項都作為工單輸入。目前,它們只是沒有“子工單”的高階工單。
- 開發團隊在首席開發人員的組織下,將每個高階工單分解成儘可能多的子工單。使用工單系統,我們建立關係,以便父工單(即需求本身)只有在所有子工單完成時才能關閉。(注意:可能需要為每個工單要求相應的單元測試。)
- 風險(我不會在這篇文章中解釋風險和風險分析)透過文件、需求和測試的組合來緩解。我們可以利用工單系統來記錄我們的風險,並像需求一樣提供跟蹤。這並不能消除對可追溯性矩陣的需求,但它確實增強了我們建立和維護它的能力。(附帶說明,我認為使用 Redmine wiki 來記錄用例、需求、風險分析、軟體設計文件和可追溯性矩陣會很棒,從而允許在內部連結,但這可能難以推行。)
- 並非所有需求都是功能程式碼需求。許多是文件和/或質量需求。這些需求應該在同一個工單系統中記錄。使用系統標記工單型別的能力來處理不同類別的事實。透過這樣做,即使是文件需求也可以在我們系統中進行跟蹤。
- 我不是說工單在這麼早的階段就會被鎖定。絕不會!工單在整個專案設計和開發過程中被建立、關閉和修改。我們的專案計劃(在開始編寫程式碼之前建立)向我們解釋哪些工單需要在何時完成,重點關注最高階的工單。也就是說,我發現最好使用某種迭代方法(並允許開發團隊使用子迭代或“衝刺”。)
- ↑ "持續整合(維基百科)". 檢索於 2011-08-29.