持續集成方案(一)


大綱

 

  1. 構建
  2. 版本控制
  3. 部署
  4. 單元測試
  5. 架構文檔化
  6. 命名約定
  7. 數據庫伸縮性
  8. 自動化
  9. 反饋
  10. 實踐
引言:
持續集成的前身:

在使用持續集成之前,很多開發團隊都是用每日構建(nightly build)。當時,微軟使用這個實踐很多年了。誰破壞了構建,就要負責監視后續的構建構成,直至發現下一個破壞了構建的人。

為什么要使用持續集成?
    對於大多數項目來說,采納持續集成實踐是向高效率和高質量邁進的一大步。它保證那些創建大型復雜系統的團隊具有高度的自信心和控制力。一旦代碼提交引入了問題,持續集成就能為我們提供快速的反饋,從而確保我們作為一個團隊所開發的軟件是可以工作的。
    持續集成的主要關注對象是開發團隊。持續集成系統的輸出通常作為手工測試流程和后續發布流程的輸入。在軟件的發布過程中,很多浪費來自於測試和運維環節。
    例如:我們常常看到:
  • 構建和運維團隊的人員一直在等待說明文檔或缺陷修復
  • 測試人員等待"好的"版本構建出來
  • 在新功能開發完成一段時間后,才收到缺陷報告
  • 開發完成時,才發現當前的軟件架構無法滿足該系統的一些非功能需求
持續集成的一些實踐:

采取一種更完整的端到端的方法來交付軟件。我們通過一鍵式方式把軟件的某個版本部署好,甚至可以將其一鍵式部署到生成環境中,這樣就可以建立了一個非常有效的反饋環--由於很容易將應用程序部署到測試環境中,所以團隊可以同時得到軟件功能和部署流程兩個方面的快速反饋。因為部署流程(無論是測試,集成)都是自動化的,所以可以頻繁且有規律的運行並被測試,從而降低發布風險,也降低了向開發團隊傳遞有關部署流程的知識時的風險。

從精益的角度來看,我們實現了一個"拉式系統",測試團隊只要自己單擊按鈕,就能輕松部署。管理人員也能容易得到一些關鍵的度量指標,比如周期時間,吞吐量以及代碼質量。

持續集成主要內容包括:
  • 將軟件構建、集成、測試和部署全面實現自動化
  • 在團隊和組織級別實現部署流水線
  • 改進開發人元、測試人員和運維人員間的協作
  • 在大型分布式團隊中增量並發開發軟件功能
  • 實施高效的配置管理策略
  • 分析並實現自動化驗收測試
  • 容量測試和其他非功能需求的測試
  • 實現持續部署和零停機發布
  • 管理基礎設施、數據、組件和依賴
  • 風險管理、符合度和審計
一.構建

什么是構建
在現代軟件學中,可能經常聽到CMMI,ISO(大型團隊)、中小型團隊(敏捷開發、XP編程)


1417326311657

構建工具: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 最基本的部署流水線

1417340015222

這個流程的起點是開發人員向版本控制庫提交代碼。此時,持續集成系統對這次提交做出響應,觸發該流水線的一個實例。
第一個階段會編譯代碼,運行單元測試,執行代碼分析,創建軟件二進制包。如果所有的單元測試都通過了,並且代碼符合編碼標准,就將可執行代碼打包成可執行文件,並放到一個制品庫里中。有些在提交而極端,還會執行另外一些任務,比如為驗收測試准備數據庫。
第二個階段由運行時間較長的自動化驗收測試組成。所以持續集成服務器最好支持將測試分成多組的做法,以便在構建網絡中並行執行任務,提高執行效率。這個階段在第一個階段完成后自動觸發的。
部署流水線可能有分支出現,這樣就可以將該構建版本獨立部署到多個不同的環境中,比如部署到用戶驗收環境、容量測試環境和生成環境。這時,部署到相應的環境,就需要用到自動化部署腳本來執行這種部署過程。測試人員應當能看到需要測試的所有構建版本,以及他們的狀態。
目的:最快的得到反饋

3.2部署流水線的相關實踐
  • 只生成一次二進制包
    我們將所有可執行代碼的集合稱作二進制包,例如.NET程序集。有時候代碼不需要編譯,那么這種情況下,二進制是所有文件的集合。一種相關的反模式就是一直使用源代碼而不是二進制包。因此,每次講一個修改部署時,需要自己親自從發布分支遷出源代碼並重新編譯二進制包。同時,還可能因編譯器和某個依賴的不同版本產生差異。

部署的可視化

四.單元測試(TDD)

測試分類:

1417342844178

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標志的方法,當我們運行測試實例時,可以忽略
1417356530715
Category
1417355849884
可以只運行指定目錄的測試用例
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;
    }
    其實不然,返回false是我們的期望,我們希望在輸入60的時候,返回false,明顯只要返回false就足夠了。后面增加的測試會讓我們擴展方法。原則是,不要向代碼引入不必要的復雜度。
  • 重構階段:
    給我們的代碼添磚加瓦,使代碼具有可維護性、可讀性或整體代碼質量。
    重構示例
    我們用一個闖三關的游戲來簡單介紹
    規則:勝方標記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個標記,我們的期望是返回空字符,即沒有人獲勝
    但上面的代碼不會編譯通過,因為我們沒有定義接口,IGameWinnerService

    public 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可以基於策略的,給注釋添加規則,自定義宏
    隨代碼提交到版本庫
    1417334310049
  • 需求文檔:

  • 測試用例的導出

3.文檔的整合

1417344914786

知識庫:
Wiki:
新聞:
1417338569296

1417338499681

4.文檔的持續更新

很多項目忽視了文檔,主要是因為團隊大了以后,一個函數、方法會有很多人修改,無法保證注釋文檔的持續更新。

5.文檔自動化發布

OneNote

6.文檔的檢索要方便

API函數、方法生成HTML文檔
支持目錄、索引、搜索

例子
1417336789443


1417336897465

新聞、維科的搜索
1417338720421

7.文檔的權限控制
8.需求變更管理

實例:

  • 需求變更管理
    需求的變更要被記錄下來
    歷史記錄應可以查詢,並且結果清晰可見。
    1417334925821
    項目跟進
  • 可以郵件提醒給相關人員
    1417346208292
  • 需求變更的審核
    新建的需求,都是未審核需求,開發和測試人員都不可見,只有經過審核的需求才能看得到
8.實踐
六.命名約定
主要內容:
  • 擴展型設計
  • 錯誤定義:
  • 類型設計規范
  • 成員設計規范
  • 框架設計規范
七.數據庫伸縮性

采用分庫的方法,可以把一個庫的壓力分擔在若干不同的庫上;這種方法很適用數據爆增的系統,只需要增加硬件就可以線性的解決性能和存儲的問題;如果考慮以后增長會很快,可在在分庫中增加分表;在分庫的基礎上可以在按功能或其他方式在分庫

分庫的幾個原則:

  1. 按功能分割
  2. 水平切分
  3. 避免分布式事務
  4. 用異步策略解耦程序
  5. 將過程轉變為異步的流
  6. 虛擬化所有層次
  7. 適當地使用緩存
八.自動化

SCRUM軟件開發過程的創始人曾經說過:
如果某個過程能夠確定下來(即能偶了解過程所涉及的所有細節,從而將其設計為可以重復地多次運行,並且安全能夠預測其結果),那么該過程就被稱為“確定過程”。從理論上講,一切確定的工程都可以被自動完成。另一方面,人們並未了解某個過程中的所有細節,只是知道到在某些初始條件下,通過某些調節和控制就能得到想要的結果,這樣的過程稱為“經驗過程”。

我們可以通過構建工具按照預定的設置,自動化運行,把錯誤信息報告給關鍵開發者,從而減少工作量。同時,自動化構建也是項目安全的保證。誰破壞了構建,一目了然。每當項目的核心代碼被修改時,整個應用程序就會被重新構建,隨后自動運行回歸測試,已確保這次修改沒有造成任何破壞。

  • 自動化腳本
  • 鈎子
九.反饋

常用的持續集成平台:jenkins、Teamcity
反饋平台:jenkins&sonarqube、TFS&Project&Sharepoint、Teamcity&NotCover

目的:發現潛在的bug,分析找出項目里的壞味道等,從而提高代碼質量

簡單介紹下sonarqube

Sonar是一個用於代碼質量管理的開源平台,用於管理源代碼的質量,可以從七個維度檢測代碼質量
通過插件形式,可以支持包括java,C#,C/C++,PL/SQL,Cobol,JavaScrip,Groovy等等二十幾種編程語言的代碼質量管理與檢測
為什么選擇Sonar
1417331539622

Developers' Seven Deadly Sins
1.糟糕的復雜度分布
文件、類、方法等,如果復雜度過高將難以改變,就會難以理解。同時,一般來說代碼越復雜,就越容易出錯。而且如果沒有自動化的單元測試,對於程序中的任何組件的改變都將可能導致需要全面的回歸測試。
1417331808057
2.重復
顯然程序中包含大量復制粘貼的代碼是質量低下的。
sonar可以展示源碼中重復嚴重的地方
1417331842140
3.缺乏單元測試
sonar可以很方便地統計並展示單元測試覆蓋率
1417331932006
4.代碼覆蓋
5.代碼標准
sonar可以通過PMD,CheckStyle,Findbugs等等代碼規則檢測工具規范代碼編寫。
sonar還支持多種語言C#,Java,Python,Android等
6.潛在問題代碼
7.設計及注釋架構
1417332324322
8.比較
可以根據基於指標,項目間的比較
1417333166557

1417332851029
9.擴展性:
sonar的集成是通過插件來擴展的。跟其他工具的集成

1417332473801


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
1417351747340

十.實踐

工具:TFS+jenkins+sonar
TFS 作為版本控制的核心
jenkins 自動化構建的關鍵
sonar 靜態檢測的執行者,代碼質量反饋的中心
NUnit(推薦)或MSTEST 作為單元測試工具
依賴管理
工具:Nuget


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM