大綱
- 構建
- 版本控制
- 部署
- 單元測試
- 架構文檔化
- 命名約定
- 數據庫伸縮性
- 自動化
- 反饋
- 實踐
引言:
持續集成的前身:
在使用持續集成之前,很多開發團隊都是用每日構建(nightly build)。當時,微軟使用這個實踐很多年了。誰破壞了構建,就要負責監視后續的構建構成,直至發現下一個破壞了構建的人。
為什么要使用持續集成?
對於大多數項目來說,采納持續集成實踐是向高效率和高質量邁進的一大步。它保證那些創建大型復雜系統的團隊具有高度的自信心和控制力。一旦代碼提交引入了問題,持續集成就能為我們提供快速的反饋,從而確保我們作為一個團隊所開發的軟件是可以工作的。
持續集成的主要關注對象是開發團隊。持續集成系統的輸出通常作為手工測試流程和后續發布流程的輸入。在軟件的發布過程中,很多浪費來自於測試和運維環節。
例如:我們常常看到:
- 構建和運維團隊的人員一直在等待說明文檔或缺陷修復
- 測試人員等待"好的"版本構建出來
- 在新功能開發完成一段時間后,才收到缺陷報告
- 開發完成時,才發現當前的軟件架構無法滿足該系統的一些非功能需求
持續集成的一些實踐:
采取一種更完整的端到端的方法來交付軟件。我們通過一鍵式方式把軟件的某個版本部署好,甚至可以將其一鍵式部署到生成環境中,這樣就可以建立了一個非常有效的反饋環--由於很容易將應用程序部署到測試環境中,所以團隊可以同時得到軟件功能和部署流程兩個方面的快速反饋。因為部署流程(無論是測試,集成)都是自動化的,所以可以頻繁且有規律的運行並被測試,從而降低發布風險,也降低了向開發團隊傳遞有關部署流程的知識時的風險。
從精益的角度來看,我們實現了一個"拉式系統",測試團隊只要自己單擊按鈕,就能輕松部署。管理人員也能容易得到一些關鍵的度量指標,比如周期時間,吞吐量以及代碼質量。
持續集成主要內容包括:
- 將軟件構建、集成、測試和部署全面實現自動化
- 在團隊和組織級別實現部署流水線
- 改進開發人元、測試人員和運維人員間的協作
- 在大型分布式團隊中增量並發開發軟件功能
- 實施高效的配置管理策略
- 分析並實現自動化驗收測試
- 容量測試和其他非功能需求的測試
- 實現持續部署和零停機發布
- 管理基礎設施、數據、組件和依賴
- 風險管理、符合度和審計
一.構建
什么是構建:
在現代軟件學中,可能經常聽到CMMI,ISO(大型團隊)、中小型團隊(敏捷開發、XP編程)
構建工具:MSBuild、Maven、Ant、NAnt、Gradle
Unit是整個金字塔的基石(在建築行業,基石是做建築物基礎的石頭),如果基石不穩,Service和UI何談有構建意義呢?只有基石穩如磐石,上層建築才夠堅固。
本來想拿瑞士做鍾表的例子來說明下,但同事說的汽車例子更好。一輛汽車由許多配件組成,如果有以下兩種選擇,你會選擇哪個呢?
1. 所有單元配件沒有測試過,在4S店,銷售人員告訴你:剛組裝好,已經開了一天,能跑起來,你可以試試;
2. 所有單元配件在生產過程已經經過嚴格測試,在4S點,銷售人員告訴你,已經通過國家認證,出廠合格,有質量保證,你可以試試;
MSBuild 是 Microsoft 和 Visual Studio的生成系統。它不僅僅是一個構造工具,應該稱之為擁有相當強大擴展能力的自動化平台。MSBuild平台的主要涉及到三部分:執行引擎、構造工程、任務。其中最核心的就是執行引擎,它包括定義構造工程的規范,解釋構造工程,執行“構造動作”;構造工程是用來描述構造任務的,大多數情況下我們使用MSBuild就是遵循規范,編寫一個構造工程;MSBuild引擎執行的每一個“構造動作”就是通過任務實現的,任務就是MSBuild的擴展機制,通過編寫新的任務就能夠不斷擴充MSBuild的執行能力。所以這三部分分別代表了引擎、腳本和擴展能力。
MSBuild的簡單介紹與使用
二.版本控制
配置管理:使用版本控制
版本控制系統(源代碼控制管理系統)是保存文件多個版本的一種機制。一般來說,包括Subversion、Git在內的開源工具就可以滿足絕大多數團隊的需求。所有的版本控制系統都需要解決這樣一個基礎問題: 怎樣讓系統允許用戶共享信息,而不會讓他們因意外而互相干擾?
如果沒有版本控制工具的協助,在開發中我們經常會遇到下面的一些問題:
一、 代碼管理混亂。
二、 解決代碼沖突困難。
三、 在代碼整合期間引入深層BUG。
四、 無法對代碼的擁有者進行權限控制。
五、 項目不同版本發布困難。
對所有內容都進行版本控制
版本控制不僅僅針對源代碼,每個與所開發的軟件相關的產物都應該被置於版本控制下,應當包括:源代碼、測試代碼、數據庫腳本、構建和部署腳本、文檔、web容器(tomcat的配置)所用的配置文件等。
保證頻繁提交可靠代碼到主干
頻繁提交可靠、有質量保證的代碼(編譯通過是最基本要求),能夠輕松回滾到最近可靠的版本,代碼提交之后能夠觸發持續集成構建,及時得到反饋。
提交有意義的注釋
強制要求團隊成員使用有意義注釋,甚至可以關聯相關開發任務的原因是。當構建失敗后,你知道是誰破壞了構建,找到可能的原因及定位缺陷位置。這些附加信息,可以縮短我們修復缺陷的時間。示例:團隊使用了svn和redmine。
有可以加強的部分
- Git要求每次提交都必須寫提交說明,可以借鑒
- 測試代碼、數據庫腳本單獨分支
- 構建腳本化
三.部署
3.1 最基本的部署流水線
這個流程的起點是開發人員向版本控制庫提交代碼。此時,持續集成系統對這次提交做出響應,觸發該流水線的一個實例。
第一個階段會編譯代碼,運行單元測試,執行代碼分析,創建軟件二進制包。如果所有的單元測試都通過了,並且代碼符合編碼標准,就將可執行代碼打包成可執行文件,並放到一個制品庫里中。有些在提交而極端,還會執行另外一些任務,比如為驗收測試准備數據庫。
第二個階段由運行時間較長的自動化驗收測試組成。所以持續集成服務器最好支持將測試分成多組的做法,以便在構建網絡中並行執行任務,提高執行效率。這個階段在第一個階段完成后自動觸發的。
部署流水線可能有分支出現,這樣就可以將該構建版本獨立部署到多個不同的環境中,比如部署到用戶驗收環境、容量測試環境和生成環境。這時,部署到相應的環境,就需要用到自動化部署腳本來執行這種部署過程。測試人員應當能看到需要測試的所有構建版本,以及他們的狀態。
目的:最快的得到反饋3.2部署流水線的相關實踐
- 只生成一次二進制包
我們將所有可執行代碼的集合稱作二進制包,例如.NET程序集。有時候代碼不需要編譯,那么這種情況下,二進制是所有文件的集合。一種相關的反模式就是一直使用源代碼而不是二進制包。因此,每次講一個修改部署時,需要自己親自從發布分支遷出源代碼並重新編譯二進制包。同時,還可能因編譯器和某個依賴的不同版本產生差異。
部署的可視化
四.單元測試(TDD)
測試分類:
BDD,TDD,ATDD
BDD主要是基於場景、面向需求的,ATDD面向驗收的,這里不做過多介紹了。
這里主要介紹TDD的一些開發
什么是TDD?
測試驅動開發
TDD的優點
TDD從一開就保證了代碼的質量:鼓勵開發人員只開發”最小化“的代碼完成特定測試功能。
遵循SOLID原則: SOLID (單一功能、開閉原則、里氏替換、接口隔離以及依賴反轉)
TDD確保了代碼與業務需求之間的高度一致性。
TDD鼓勵創建更簡單,針對性更強的API
TDD鼓勵更多的溝通,與企業,與團隊內部。
TDD有助於清除冗余代碼
TDD提供了內置的回歸測試。如果沒有單元測試來幫助查找和診斷缺陷時,大多數開發人員會使用調試器,在他們認為出現缺陷的地方設置斷點,有時將這種方法稱為"散彈槍方法"。
單元測試的特征:
- 與其他代碼隔離
- 有針對性
- 與其他開發人員隔離
- 可重復
- 可預測
自動化單元測試原則:
提交代碼、運行測試的重點是什么?快速捕獲那些因修改向系統中引入的最常見錯誤,並通知開發人員,以便他們能快速修復他們。提交階段提供反饋的價值在於,對它的投入可以讓系統高效且更快地工作。
- 隔離UI操作
UI應當作為更高層次的測試Level,需要花費大量時間准備數據,業務邏輯復雜,過早進入UI階段,容易分散開發的單元測試精力。 - 隔離數據庫以及文件讀寫網絡開銷等操作
自動化測試中如果需要將結果寫入數據庫,然后再驗證改結果是否被正確寫入,這種驗證方法簡單、容易理解,但是它不是一個高效的方法。這個應當從集成測試的Level去解決。
首先:與數據庫的交互,是漫長的,甚至有可能要投入維護數據庫的時間,那將成為快速測試的一個障礙,開發人員不能得到及時有效的反饋。假設,我需要花費一個小時,才能驗證完畢與數據庫交互的結果,這種等待是多么漫長呀。
其次,數據管理需要成本,從數據的篩選(線上數據可能是T級)到測試環境的M級別,如何把篩選合適的大小,這都使得管理成本增加(當然在集成測試中可以使用DBUnit來解決部分問題)。
最后,如果一定要有讀寫操作才能完成的測試,也要反思代碼的可測試性做的如何?是否需要重構。
單元測試決不要依賴於數據庫以及文件系統、網絡開銷等一切外部依賴。 - 使用隔離框架和依賴框架
可以使用模擬工具集:Rhino.Mock、Moq、Type Mock等來解決,研發團隊主要是基於Mockito的實踐。與需要組裝所有的依賴和狀態相比,使用模擬技術的測試運行起來通常是非常快,這樣子開發人員在提交代碼之后,可以在持續集成平台快速得到反饋。
實踐
常規
一個加密、解密方法的測試
[Test] [Ignore("這個測試有問題!")] public void TestEncrypt() { FileEncrypt fileEncrypt = new FileEncrypt(); String ii = fileEncrypt.EncryptContext("Hello World", "123"); ii = fileEncrypt.DecryptContext(ii, "123"); Assert.Pass(ii); }
Ignore標志的方法,當我們運行測試實例時,可以忽略
Category
可以只運行指定目錄的測試用例
Rhino.Mocks的簡單實用
[Test] [Category("模擬對象")] public void TestCustomer() { MockRepository mocks = new MockRepository(); ICustomer customer = mocks.StrictMock<ICustomer>(); customer.Expect(c => c.ShowTitle("")).Return("567"); Expect.Call(customer.Pid).Return(30); customer.Replay(); Assert.AreEqual(customer.ShowTitle(""), "567"); } [Test] public void SayHelloWorld() { MockRepository mocks = new MockRepository(); INameSource nameSource = mocks.DynamicMock<INameSource>(); Expect.Call(nameSource.CreateName(null, null)) .IgnoreArguments() .Do(new NameSourceDelegate(Formal)); mocks.ReplayAll(); const string expected = "Hi, my name is Ayende Rahien"; string actual = new Speaker("Bright", "Gong", nameSource).Introduce(); Assert.AreEqual(expected, actual); } delegate string NameSourceDelegate(string first, string surname); public class Speaker { private readonly string _firstName; private readonly string _surname; private readonly INameSource _nameSource; public Speaker(string firstName, string surname, INameSource nameSource) { this._firstName = firstName; this._surname = surname; this._nameSource = nameSource; } public string Introduce() { string name = _nameSource.CreateName(_firstName, _surname); return string.Format("Hi, my name is {0}", name); } }
紅燈、綠燈、重構
"紅燈、綠燈、重構"明確了開發人員在實施TDD時所要遵循的工作流。
- 紅燈階段
在開始使用TDD時,許多開發人員會問:"我怎么能為不存在的代碼編寫測試呢?"事實上,許多測試都是針對當期不存在的類或方法的。這意味着這些測試甚至不能編譯通過,其效果基本上與失敗測試相同。這沒問題,記住,這是因為有了這些測試,才需要有相應的代碼存在。
紅燈階段,主要是編寫一個會測試失敗的代碼,
比如[Category("simple")] [Test] public bool MyMEthod(int inputParameter) { throw new NotImplementedException(); }
- 綠燈階段
僅編寫適量的代碼,是新測試通過而不導致任何測試失敗。[Category("simple")] [Test] public bool MyMEthod(int inputParameter) { return false; }
[Category("simple")] [Test] public bool MyMEthod(int inputParameter) { if(inputParameter=60) { return false; } return true; }
-
重構階段:
給我們的代碼添磚加瓦,使代碼具有可維護性、可讀性或整體代碼質量。
重構示例:
我們用一個闖三關的游戲來簡單介紹
規則:勝方標記X或O,沒有玩家獲勝,則返回空字符
我們創建了一個測試方法
如下圖private IGameWinnerService _gameWinnerService; private char[,] _gameBoard; [SetUp] public void SetupUnitTests() { _gameWinnerService = new GameWinnerService(); _gameBoard = new char[3, 3] { {' ', ' ', ' '}, {' ', ' ', ' '}, {' ', ' ', ' '} }; } [Test] public void NeitherPlayerHasThreeInARow() { const char expected = ' '; var actual = _gameWinnerService.Validate(_gameBoard); Assert.AreEqual(expected, actual); }
傳遞一個空數組,即沒有一個玩家在一行中放入了3個標記,我們的期望是返回空字符,即沒有人獲勝
但上面的代碼不會編譯通過,因為我們沒有定義接口,IGameWinnerServicepublic interface IGameWinnerService { char Validate(char[,] gameBoard); }
但還是會失敗,因為我們沒有定義實現的類,GameWinnerService
public class GameWinnerService : IGameWinnerService { public char Validate(char[,] gameBoard) { throw new NotImplementedException(); } }
編譯可以通過了,但運行報錯,接下來完善這個方法
public class GameWinnerService : IGameWinnerService { private const char SymbolForNoWinner = ' '; public char Validate(char[,] gameBoard) { return SymbolForNoWinner; } }
五.架構代碼的文檔化
1.文檔有哪些
開發文檔、測試文檔、需求文檔、用戶手冊、技術手冊等
2.簡化文檔編寫
- 開發文檔:GhostDoc有收費版,免費版,Free版,平時就夠用了。
GHost可以基於策略的,給注釋添加規則,自定義宏
隨代碼提交到版本庫 -
需求文檔:
-
測試用例的導出
3.文檔的整合
- 現在的方式
OneNote+SharePoint+TFS+Project 協同辦公
OneNote:用戶手冊、技術手冊
SharePoint:需求文檔,側重於文件
TFS:開發文檔、用例(需求用例,測試用例)
Project:項目管理,多維度
實例:
SharePoint+TFS集成
SharePoint+OneNote集成,OneNote更改調用SharePoint的郵件提醒
研究Project2010+TFS2010項目需求管理功能
TFS-Project Server 集成的同步過程概述
4.文檔的持續更新
很多項目忽視了文檔,主要是因為團隊大了以后,一個函數、方法會有很多人修改,無法保證注釋文檔的持續更新。
5.文檔自動化發布
OneNote
6.文檔的檢索要方便
API函數、方法生成HTML文檔
支持目錄、索引、搜索
7.文檔的權限控制
8.需求變更管理
實例:
- 需求變更管理
需求的變更要被記錄下來
歷史記錄應可以查詢,並且結果清晰可見。
項目跟進 - 可以郵件提醒給相關人員
- 需求變更的審核
新建的需求,都是未審核需求,開發和測試人員都不可見,只有經過審核的需求才能看得到
8.實踐
六.命名約定
主要內容:
- 擴展型設計
- 錯誤定義:
- 類型設計規范
- 成員設計規范
- 框架設計規范
七.數據庫伸縮性
采用分庫的方法,可以把一個庫的壓力分擔在若干不同的庫上;這種方法很適用數據爆增的系統,只需要增加硬件就可以線性的解決性能和存儲的問題;如果考慮以后增長會很快,可在在分庫中增加分表;在分庫的基礎上可以在按功能或其他方式在分庫
分庫的幾個原則:
- 按功能分割
- 水平切分
- 避免分布式事務
- 用異步策略解耦程序
- 將過程轉變為異步的流
- 虛擬化所有層次
- 適當地使用緩存
八.自動化
SCRUM軟件開發過程的創始人曾經說過:
如果某個過程能夠確定下來(即能偶了解過程所涉及的所有細節,從而將其設計為可以重復地多次運行,並且安全能夠預測其結果),那么該過程就被稱為“確定過程”。從理論上講,一切確定的工程都可以被自動完成。另一方面,人們並未了解某個過程中的所有細節,只是知道到在某些初始條件下,通過某些調節和控制就能得到想要的結果,這樣的過程稱為“經驗過程”。
我們可以通過構建工具按照預定的設置,自動化運行,把錯誤信息報告給關鍵開發者,從而減少工作量。同時,自動化構建也是項目安全的保證。誰破壞了構建,一目了然。每當項目的核心代碼被修改時,整個應用程序就會被重新構建,隨后自動運行回歸測試,已確保這次修改沒有造成任何破壞。
- 自動化腳本
- 鈎子
九.反饋
常用的持續集成平台:jenkins、Teamcity
反饋平台:jenkins&sonarqube、TFS&Project&Sharepoint、Teamcity&NotCover
目的:發現潛在的bug,分析找出項目里的壞味道等,從而提高代碼質量
簡單介紹下sonarqube
Sonar是一個用於代碼質量管理的開源平台,用於管理源代碼的質量,可以從七個維度檢測代碼質量
通過插件形式,可以支持包括java,C#,C/C++,PL/SQL,Cobol,JavaScrip,Groovy等等二十幾種編程語言的代碼質量管理與檢測
為什么選擇Sonar
Developers' Seven Deadly Sins
1.糟糕的復雜度分布
文件、類、方法等,如果復雜度過高將難以改變,就會難以理解。同時,一般來說代碼越復雜,就越容易出錯。而且如果沒有自動化的單元測試,對於程序中的任何組件的改變都將可能導致需要全面的回歸測試。
2.重復
顯然程序中包含大量復制粘貼的代碼是質量低下的。
sonar可以展示源碼中重復嚴重的地方
3.缺乏單元測試
sonar可以很方便地統計並展示單元測試覆蓋率
4.代碼覆蓋
5.代碼標准
sonar可以通過PMD,CheckStyle,Findbugs等等代碼規則檢測工具規范代碼編寫。
sonar還支持多種語言C#,Java,Python,Android等
6.潛在問題代碼
7.設計及注釋架構
8.比較
可以根據基於指標,項目間的比較
9.擴展性:
sonar的集成是通過插件來擴展的。跟其他工具的集成
簡單介紹下Jenkins
Jenkins,之前叫做Hudson,是基於Java開發的一種持續集成工具,用於監控持續重復的工作,包括:
1、持續的軟件版本發布/測試項目。
2、監控外部調用執行的工作。
- 備份和恢復
備份和恢復非常簡單,就是簡單的copy Jenkins的目錄就好了:
All the settings, build logs, artifact archives are stored under the JENKINS_HOME directory. Simply archive this directory to make a back up. Similarly, restoring the data is just replacing the contents of the JENKINS_HOME directory from a back up.- 自動運行Build
觸發一個build有三種方式:
Builds in Jenkins can be triggered periodically (on a schedule, specified in configuration) 這里定義schedule的語法是unix常見的cron語法。
Or when source changes in the project have been detected
可以設置Jenkins定時檢查SVN或TFS 是否發生了變化,也可以手動檢查:http://YOURHOST/jenkins/job/PROJECTNAME/pollong。也可以設置Jenkins為post-commit,這個方式尤其適用於那些檢查是否代碼改變會花費很長時間的情況。- 因為性能問題,將build分布到多個slave節點去。
到Jenkins的管理界面,就可以方便的添加節點。配置節點時,需要提供節點所在的機器,登陸用戶名密碼,使用的目錄等。
但是slave並不需要再安裝Jenkins。jenkins會自動啟用slave agent,將build需要tools考到遠程機器上。
需要注意的是:the build results and artifacts will always end up on the master server. 因此不需要跑到各個節點去查看build產生的文件,log等。
其實在slave節點,會創建一個本地的workspace,並在運行時使用這個workspace。因為畢竟build運行在slave節點上,所以這個節點肯定要有運行build需要的所有因素。- 創建一個Project
因為Jenkins可以用於運行各種CI,測試,批處理任務等等,所以在Jenkins中將這些任務統稱為“free-style software project”.
目前:低成本的集成方案
Jenkins+MSTEST+MSBuild
十.實踐
工具:TFS+jenkins+sonar
TFS 作為版本控制的核心
jenkins 自動化構建的關鍵
sonar 靜態檢測的執行者,代碼質量反饋的中心
NUnit(推薦)或MSTEST 作為單元測試工具
依賴管理
工具:Nuget