天天躁日日躁狠狠躁AV麻豆-天天躁人人躁人人躁狂躁-天天澡夜夜澡人人澡-天天影视香色欲综合网-国产成人女人在线视频观看-国产成人女人视频在线观看

重溫大師經(jīng)典:Martin Fowler 的持續(xù)集成

  英文原文:Continuous Integration

  (作者:Martin Fowler,譯者:滕云)

  原文發(fā)布時(shí)間:2006年5月1日   翻譯時(shí)間:2012年2月25日

  持續(xù)集成是一種軟件開(kāi)發(fā)實(shí)踐,在實(shí)踐中項(xiàng)目成員頻繁地進(jìn)行集成,通常每個(gè)成員每天都會(huì)做集成工作,如此,每天整個(gè)項(xiàng)目將會(huì)有多次集成。每次集成后都會(huì)通過(guò)自動(dòng)化構(gòu)建(包括測(cè)試)來(lái)盡快發(fā)現(xiàn)其中的錯(cuò)誤。許多團(tuán)隊(duì)都發(fā)現(xiàn)這種方法大大地減少了集成問(wèn)題并且能夠快速地開(kāi)發(fā)出高內(nèi)聚性的軟件。本文簡(jiǎn)要地總結(jié)了持續(xù)集成技術(shù)及其現(xiàn)狀。

  我還清楚地記得我剛加入一個(gè)大型軟件項(xiàng)目時(shí)的情形,那時(shí)我正在英國(guó)一個(gè)電子公司做暑期實(shí)習(xí)。我的經(jīng)理(屬于QA部門)領(lǐng)我參觀了一個(gè)巨大并很壓抑的倉(cāng)庫(kù),里面堆滿了大塊頭的主機(jī)。經(jīng)理告訴我這個(gè)項(xiàng)目已經(jīng)開(kāi)發(fā)了有些年頭,現(xiàn)在正在做集成,并且已經(jīng)集成了好幾個(gè)月了。經(jīng)理還告訴我說(shuō),沒(méi)有人真正知道完成集成工作需要多少時(shí)間。由此我學(xué)到了軟件項(xiàng)目的一個(gè)通病:軟件集成是一個(gè)漫長(zhǎng)并且無(wú)法預(yù)測(cè)的過(guò)程。

  然而,軟件集成不必像這樣的。在ThoughtWorks的大多數(shù)項(xiàng)目還有世界上許多其它組織的軟件項(xiàng)目中,軟件集成并不是什么難事。每個(gè)開(kāi)發(fā)人員離共享的工程狀態(tài)只有咫尺之遙,并且可以在幾分鐘之內(nèi)將自己的代碼集成進(jìn)去。任何集成錯(cuò)誤都能被快速地發(fā)現(xiàn)并得到快速的修正。

  這種鮮明的對(duì)比并不是源自于后者有多么昂貴或復(fù)雜的工具,而關(guān)鍵在于每人都頻繁集成這種簡(jiǎn)單實(shí)踐,通常是每天向一個(gè)被管控的代碼庫(kù)集成。

  當(dāng)我向人們闡述這種實(shí)踐時(shí),通常得到兩種反應(yīng):“(在我們這里)行不通”和“無(wú)關(guān)緊要”。當(dāng)人們嘗試了這種實(shí)踐之后,他們發(fā)現(xiàn)其實(shí)做起來(lái)比說(shuō)起來(lái)簡(jiǎn)單,而且這樣的實(shí)踐對(duì)于開(kāi)發(fā)“至關(guān)重要”。因此有了第三種反應(yīng):“是的,我們就是這么做的,不然該怎么活啊?”

  “持續(xù)集成”源自于極限編程(XP),并且是XP最初的12種實(shí)踐之一。當(dāng)我以咨詢師的角色加入ThoughtWorks時(shí),我鼓勵(lì)我的團(tuán)隊(duì)使用這種技術(shù)。Matthew Foemmel將我的建議變成了實(shí)實(shí)在在的行動(dòng),由此軟件集成從少有發(fā)生并且復(fù)雜的狀態(tài)變成了一樁易事。Matthew和我將我們的經(jīng)驗(yàn)寫在了本文的第一版中,而本文也是我的個(gè)人網(wǎng)站上最受歡迎的文章之一。

  雖然持續(xù)集成并不需要使用特別的工具來(lái)部署,但是我們發(fā)現(xiàn)擁有一臺(tái)持續(xù)集成服務(wù)器將大有益處,其中最著名的有開(kāi)源的CruiseControl,該軟件最初由ThoughtWorks的幾個(gè)員工開(kāi)發(fā),現(xiàn)在由一個(gè)很大的社區(qū)維護(hù)著。后來(lái)幾款其它的持續(xù)集成服務(wù)器也相繼出現(xiàn)了,有開(kāi)源的,也有商業(yè)化的,包括ThoughtWorks Studios的Cruise。

  在開(kāi)發(fā)中使用持續(xù)集成

  對(duì)于我來(lái)說(shuō),解釋持續(xù)集成及其工作原理最簡(jiǎn)單的方法便是以一個(gè)小的軟件功能的開(kāi)發(fā)為例來(lái)進(jìn)行演示。假設(shè)我們需要向軟件添加一點(diǎn)功能,至于是什么樣的功能并不重要,我們假定它很小并且可以在幾個(gè)小時(shí)內(nèi)完成。

  首先我們需要在本地機(jī)器上保留一份當(dāng)前已經(jīng)處于集成狀態(tài)的代碼的拷貝。我通過(guò)代碼管理系統(tǒng)在代碼庫(kù)的主線(mainline)上拉下(check out)一份工作代碼拷貝。

  上一段文字主要針對(duì)使用代碼控制系統(tǒng)的人,對(duì)于不使用代碼控制系統(tǒng)的人來(lái)說(shuō)便是胡言亂語(yǔ)了。因此,我將向后者解釋一下。代碼控制系統(tǒng)用于將項(xiàng)目所有的代碼保存在一個(gè)代碼庫(kù)(repository)中,項(xiàng)目當(dāng)前的狀態(tài)通常被稱為主線。任何時(shí)候開(kāi)發(fā)人員都可以從主線上獲得一份拷貝到本地機(jī)器,這被稱為“checking out”。本地機(jī)器上的代碼拷貝稱為“working copy”。(多數(shù)時(shí)候,實(shí)際上你是在更新(update)本地代碼到主線狀態(tài),實(shí)踐中它們是一樣的效果。)

  現(xiàn)在,為了完成軟件的功能添加,我對(duì)本地代碼進(jìn)行修改,其中既包括修改產(chǎn)品代碼,也包括添加自動(dòng)化測(cè)試。持續(xù)集成非常看重測(cè)試,并且在軟件代碼本身中達(dá)到了測(cè)試自動(dòng)化——我將其稱為自測(cè)試代碼,通常使用流行的XUnit測(cè)試框架的一個(gè)版本。

  當(dāng)我完成了功能開(kāi)發(fā)(或者在我開(kāi)發(fā)過(guò)程的不同階段),我將在本地開(kāi)發(fā)機(jī)上完成自動(dòng)化構(gòu)建。構(gòu)建過(guò)程將編譯并鏈接本地代碼,然后跑自動(dòng)化測(cè)試。只有當(dāng)構(gòu)建和測(cè)試都沒(méi)有錯(cuò)誤時(shí),該次構(gòu)建才能算是好的構(gòu)建。

  有了本地的成功構(gòu)建,我便可以考慮將我修改的代碼提交到代碼庫(kù)了。但是,在我提交之前,其他開(kāi)發(fā)人員可能已經(jīng)向主線提交了他們的修改,所以首先我需要將他們的修改更新到我本地并且重新構(gòu)建。如果他人的修改與我的修改有沖突,那么在本地編譯或者測(cè)試階段將會(huì)發(fā)生錯(cuò)誤,這種情況下,我需要負(fù)責(zé)修改本地代碼直到與主線代碼保持適當(dāng)同步為止。

  當(dāng)本地代碼與主線代碼同步之后,我便可以向主線提交自己的修改了,代碼庫(kù)也得以更新。

  然而,單是提交了修改并不表示我的工作就完成了。我需要再次構(gòu)建,但這次是在一臺(tái)擁有主線代碼的集成機(jī)器上進(jìn)行。只有這次構(gòu)建成功了才表示我的任務(wù)完成。通常會(huì)出現(xiàn)這樣的情況:我忘了提交本地機(jī)器上的一些東西,因此代碼庫(kù)并沒(méi)有得到適當(dāng)?shù)母隆V挥形姨峤坏男薷脑诩蓹C(jī)器上成功構(gòu)建之后,我的工作才算完成。這樣的集成構(gòu)建可以由我手動(dòng)完成,也可以由Cruise自動(dòng)完成。

  當(dāng)兩個(gè)開(kāi)發(fā)者的代碼有沖突時(shí),通常會(huì)在第二個(gè)開(kāi)發(fā)者更新本地代碼時(shí)捕獲到,否則,集成構(gòu)建應(yīng)該會(huì)失敗。在這兩種途徑中,錯(cuò)誤都可以被快速地發(fā)現(xiàn)。在持續(xù)集成環(huán)境中,你決不應(yīng)該使失敗的集成構(gòu)建保留太長(zhǎng)時(shí)間。一個(gè)好的團(tuán)隊(duì)每天都應(yīng)該有許多成功的構(gòu)建。當(dāng)然,失敗的構(gòu)建也會(huì)時(shí)常發(fā)生,但需要盡快的修復(fù)。

  這樣做的結(jié)果是,我們總會(huì)得到一個(gè)穩(wěn)定并且工作正常的軟件。每個(gè)人都圍繞著一個(gè)共享并穩(wěn)定的基礎(chǔ)代碼庫(kù)工作,絕不離基礎(chǔ)代碼庫(kù)太遠(yuǎn)以至于需要很長(zhǎng)的時(shí)間將自己的修改集成到基礎(chǔ)代碼庫(kù)中。如此這般,我們花在找bug上的時(shí)間減少了,因?yàn)閎ug在頻繁的集成中經(jīng)常出現(xiàn)。

  持續(xù)集成實(shí)踐

  上文只是關(guān)于持續(xù)集成的一個(gè)概要和它在日常開(kāi)發(fā)中的工作原理。讓所有這些都能很好的運(yùn)作顯然不止于此。現(xiàn)在,就讓我們來(lái)看看有效持續(xù)集成所需的關(guān)鍵實(shí)踐。 

  維護(hù)一個(gè)單一的代碼庫(kù)

  軟件項(xiàng)目需要大量的文件協(xié)同工作來(lái)構(gòu)建出最終的產(chǎn)品。跟蹤所有的文件需要大量的工作,尤其是在多個(gè)開(kāi)發(fā)者參與的項(xiàng)目中。因此,我們可以并不驚奇的看到,不同的軟件開(kāi)發(fā)團(tuán)隊(duì)都在開(kāi)發(fā)用于管理這些文件的工具——源代碼管理工具,也叫配置管理,版本控制系統(tǒng),代碼庫(kù)等。這些工具是多數(shù)軟件項(xiàng)目不可分的組成部分。然而,令人傷心并吃驚的是,并不是所有的項(xiàng)目都使用了這樣的工具。我的確見(jiàn)到(雖然很少)不使用這些工具的項(xiàng)目,它們使用本地和共享磁盤這種混亂的結(jié)合來(lái)共同工作。

  因此,做為最基本的持續(xù)集成實(shí)踐,請(qǐng)保證你使用一款體面的代碼管理系統(tǒng)。成本不是問(wèn)題,有許多高質(zhì)量的開(kāi)源代碼管理工具存在。當(dāng)前的選擇為Subversion(譯者注:現(xiàn)在有了更新的hg和git)。(更老的開(kāi)源工具CVS如今仍然被大量使用,雖然比沒(méi)有強(qiáng),但是Subversion是更現(xiàn)代的選擇。)有趣的是,當(dāng)我和一些開(kāi)發(fā)者聊天時(shí),我發(fā)現(xiàn)相比起多數(shù)商業(yè)化的代碼管理系統(tǒng),他們更喜歡Subversion。據(jù)我所知,唯一值得花錢買的只有Perforce

  當(dāng)你有了代碼管理系統(tǒng)之后,確保每個(gè)開(kāi)發(fā)者都能方便的獲得到源代碼。不應(yīng)該有人還在問(wèn):“foo-whiffle 文件在哪兒?”所有東西都必須在代碼庫(kù)里。

  雖然許多團(tuán)隊(duì)都在使用代碼庫(kù),但是我經(jīng)常發(fā)現(xiàn),他們并不把所有東西都放在里面。如果大家需要使用一個(gè)文件,他們知道該文件放到代碼庫(kù)中,但是,構(gòu)建所需的所有都應(yīng)該包含在代碼庫(kù)里,包括測(cè)試腳本,屬性文件,數(shù)據(jù)庫(kù)模式文件,安裝腳本和第三方庫(kù)等。我所知道的有項(xiàng)目將編譯器加到代碼庫(kù)中的(對(duì)于早期脆弱的C++編譯器來(lái)說(shuō)非常重要)。基本原則是:在一臺(tái)新機(jī)器上check out代碼后構(gòu)建也能構(gòu)建成功。新機(jī)器上的東西應(yīng)該盡量的少,通常包括很大的,難于安裝的,并且穩(wěn)定的軟件,比如操作系統(tǒng),Java開(kāi)發(fā)環(huán)境或者數(shù)據(jù)庫(kù)管理系統(tǒng)等。

  你需要將構(gòu)建所需的所有東西都加到代碼管理系統(tǒng)中,同時(shí)也需要將大家經(jīng)常操作的東西方進(jìn)去,IDE配置便是一個(gè)很好的例子,這樣便于大家共享IDE配置。

  版本控制系統(tǒng)的一大功能是它允許你創(chuàng)建多個(gè)分支,以此來(lái)處理不同的“開(kāi)發(fā)流”。這種功能很有用,但卻經(jīng)常被過(guò)度使用以至給開(kāi)發(fā)者帶來(lái)了不少麻煩。所以,你需要將分支的使用最小化,特別建議使用主線,即項(xiàng)目中只有單一的開(kāi)發(fā)分支,并且每人在多數(shù)時(shí)間里都在“離線”工作。

  總之,你應(yīng)該將構(gòu)建所需的所有東西都放在代碼管理系統(tǒng)中,而不應(yīng)該將構(gòu)建的輸出放進(jìn)去。有些朋友確實(shí)將構(gòu)建輸出放在代碼管理系統(tǒng)中,但我認(rèn)為這是一個(gè)壞味道,可能導(dǎo)致更深的問(wèn)題——通常是你無(wú)法完成重新構(gòu)建。

  使構(gòu)建自動(dòng)化

  將源代碼變成一個(gè)能運(yùn)行的軟件系統(tǒng)通常是一個(gè)復(fù)雜的過(guò)程,包括編譯,文件搬移,加載數(shù)據(jù)庫(kù)模式等等。但其中大多數(shù)任務(wù)都是可以自動(dòng)化的,并且也應(yīng)該被自動(dòng)化。讓人去輸入奇怪的命令或點(diǎn)擊對(duì)話框是非常耗時(shí)的,而且從根本上來(lái)說(shuō)也是個(gè)錯(cuò)誤的做法。

  構(gòu)建所需的自動(dòng)化環(huán)境對(duì)于軟件系統(tǒng)來(lái)說(shuō)是一個(gè)通用功能。Unix的Make已經(jīng)誕生好多年了,Java社區(qū)有Ant, .NET社區(qū)有Nant,現(xiàn)在又有了MSBuild。當(dāng)你用這些工具構(gòu)建和啟動(dòng)系統(tǒng)時(shí),請(qǐng)確保只使用一個(gè)命令完成任務(wù)。

  一個(gè)常見(jiàn)的錯(cuò)誤是在自動(dòng)化構(gòu)建里并沒(méi)有完全包括構(gòu)建所需的東西。構(gòu)建過(guò)程中應(yīng)該從代碼庫(kù)里取得數(shù)據(jù)庫(kù)模式文件并自動(dòng)執(zhí)行之。結(jié)合我上文所講的原則來(lái)看,任何人都應(yīng)該能夠在一臺(tái)新機(jī)器上拉下代碼庫(kù)中的代碼,并只用一個(gè)命令將系統(tǒng)運(yùn)行起來(lái)。

  構(gòu)建腳本是多種多樣的,通常特定于某個(gè)平臺(tái)或社區(qū),但情況并不必須如此。我們的多數(shù)Java項(xiàng)目都使用Ant,而另外有些用Ruby(Ruby世界的Rake是一個(gè)非常不錯(cuò)的構(gòu)建工具)。我們用Ant完成了早期的一個(gè)微軟COM工程的構(gòu)建自動(dòng)化,并從中大獲裨益。

  大型的構(gòu)建通常需要很長(zhǎng)的時(shí)間,而在你只做了很小的修改的情況下,你是不想運(yùn)行所有的構(gòu)建步驟的。因此,優(yōu)秀的構(gòu)建工具能夠分析出哪些地方需要做相應(yīng)的修改,并將這個(gè)分析過(guò)程本身做為整個(gè)構(gòu)建過(guò)程的一部分。通常的做法是檢查源代碼和目標(biāo)文件的修改日期,只有當(dāng)源代碼的修改日期晚于其對(duì)應(yīng)的目標(biāo)文件時(shí)才執(zhí)行編譯。依賴關(guān)系因此變得微妙起來(lái)了:如果一個(gè)目標(biāo)文件發(fā)生了修改,那些依賴于它的文件也需要重新構(gòu)建。有些編譯器能夠處理這種依賴關(guān)系,而有些就不見(jiàn)得。

  根據(jù)自己的需要,你可以選擇不同的東西進(jìn)行構(gòu)建。構(gòu)建中既可以包括測(cè)試,也可以不包括,甚至可以包括不同的測(cè)試板塊。有些組件可以進(jìn)行單獨(dú)構(gòu)建。構(gòu)建腳本應(yīng)該能夠允許你針對(duì)不同的情形進(jìn)行不同的構(gòu)建目標(biāo)。

  我們大多數(shù)都使用IDE,而多數(shù)IDE都或多或少地集成了構(gòu)建管理功能。但是這樣構(gòu)建文件通常是特定于IDE的,而且非常脆弱。此外,它們需要依賴于IDE才能工作。雖然對(duì)于開(kāi)發(fā)者個(gè)人來(lái)說(shuō),在IDE中做這樣的構(gòu)建配置并無(wú)不妥,但對(duì)于持續(xù)集成服務(wù)器來(lái)說(shuō),一份能夠被其它腳本調(diào)用的主構(gòu)建腳本卻是至關(guān)重要的。比如一個(gè)Java項(xiàng)目,各個(gè)開(kāi)發(fā)者可以在自己的IDE中進(jìn)行構(gòu)建,但應(yīng)該還有一個(gè)Ant主構(gòu)建腳本來(lái)保證構(gòu)建能在集成服務(wù)器上順利完成。

  使構(gòu)建自測(cè)試

  傳統(tǒng)意義上的構(gòu)建包括只編譯,鏈接等過(guò)程。此時(shí)程序也許能運(yùn)行起來(lái),但這并不意味著系統(tǒng)就能正確地運(yùn)行。雖然現(xiàn)在的靜態(tài)語(yǔ)言已經(jīng)能夠捕捉到許多bug,但是漏網(wǎng)之魚卻更多。

  一種快速并高效發(fā)現(xiàn)bug的方法是將自動(dòng)化測(cè)試包含到構(gòu)建過(guò)程中。當(dāng)然,測(cè)試也不見(jiàn)得完美,但的確能發(fā)現(xiàn)很多bug——足夠多了。特別是隨著極限編程(XP)的升溫,測(cè)試驅(qū)動(dòng)開(kāi)發(fā)(TDD)也使自測(cè)試代碼流行起來(lái),越來(lái)越多的人開(kāi)始注意到這種技術(shù)的價(jià)值所在。

  經(jīng)常讀我著作的讀者可能知道我是一個(gè)TDD和XP的大粉絲,然而我想強(qiáng)調(diào)的是這兩種方法和自測(cè)試并沒(méi)有必然聯(lián)系。TDD和XP都要求先寫測(cè)試代碼,再寫功能代碼使測(cè)試通過(guò)。在這種模式下,測(cè)試既用于發(fā)現(xiàn)bug,又用于完成系統(tǒng)設(shè)計(jì)。這是非常好的,但對(duì)于持續(xù)集成來(lái)說(shuō)不必如此,因?yàn)榇藭r(shí)我們自測(cè)試代碼的要求并不那么高。(然而TDD是我寫自測(cè)試代碼的首選。)

  對(duì)于自測(cè)試代碼而言,你需要一組自動(dòng)化測(cè)試來(lái)檢測(cè)一大部分代碼庫(kù)中的bug。測(cè)試能通過(guò)一個(gè)簡(jiǎn)單的命令來(lái)運(yùn)行并且具備自檢功能。測(cè)試的結(jié)果應(yīng)該能指出哪些測(cè)試是失敗的。對(duì)于自測(cè)試的構(gòu)建來(lái)說(shuō),測(cè)試失敗應(yīng)導(dǎo)致構(gòu)建失敗。

  過(guò)去這些年里,TDD使開(kāi)源XUnit家族流行起來(lái),成為了理想的測(cè)試工具。在ThoughtWorks,XUnit已經(jīng)是非常有用的測(cè)試工具,我也經(jīng)常建議人們使用。這組工具起初由Kent Beck開(kāi)發(fā),它們使自測(cè)試環(huán)境的搭建變得非常簡(jiǎn)單。

  XUnit當(dāng)之無(wú)愧地是你進(jìn)行代碼自測(cè)試的起點(diǎn)。當(dāng)然,你也應(yīng)當(dāng)多看看那些更側(cè)向于端到端測(cè)試的工具,包括FITSeleniumSahiWatirFITnesse等等,我就不逐一列舉了。

  當(dāng)然,別指望測(cè)試就是萬(wàn)能的。常言道,測(cè)試并不代表就沒(méi)有bug。

  每人每天都向主線提交代碼

  集成首先在于交流,它使其他成員能夠看到你所做的修改。在這種頻繁的交流下,大家都能很快地知道開(kāi)發(fā)過(guò)程中所做的修改。

  在向主線提交代碼之前,開(kāi)發(fā)人員必須保證本地構(gòu)建成功。這當(dāng)然也包括使測(cè)試全部通過(guò)。另外,在提交之前需要更新本地代碼以匹配主線代碼,然后在本地解決主線代碼與本地代碼之間的沖突,再在本地進(jìn)行構(gòu)建。如果構(gòu)建成功,便可以向主線提交代碼了。

  在這種頻繁提交下,開(kāi)發(fā)者可以快速地發(fā)現(xiàn)自己代碼與他人代碼之間的沖突。快速解決問(wèn)題的關(guān)鍵在于快速地發(fā)現(xiàn)問(wèn)題。幾個(gè)小時(shí)的提交間隔使得代碼沖突也可以在幾個(gè)小時(shí)內(nèi)發(fā)現(xiàn),此時(shí)大家的修改都不多,沖突也不大,因此解決沖突也很簡(jiǎn)單。對(duì)于好幾周都發(fā)現(xiàn)不了的沖突,通常是很難解決的。

  在更新本地代碼庫(kù)時(shí)就進(jìn)行構(gòu)建,這意味著我們既可以發(fā)現(xiàn)文本上的沖突,又可以發(fā)現(xiàn)編譯沖突。既然構(gòu)建是自測(cè)試的,那么運(yùn)行時(shí)的沖突也可以被檢測(cè)出來(lái),而這樣的沖突往往是一些特別煩人的bug。由于提交間隔只有短短的幾個(gè)小時(shí),bug便沒(méi)多少藏身之處了。再者,因?yàn)槊看翁峤坏男薷亩疾欢啵憧梢允褂?a target="_blank">diff-debugging來(lái)幫你找出這些bug。

  我的基本原則是:每個(gè)開(kāi)發(fā)者每天都應(yīng)當(dāng)向代碼庫(kù)進(jìn)行提交。在實(shí)踐中,越是頻繁提交,可能導(dǎo)致沖突的地方就越少,因而也越容易發(fā)現(xiàn)。

  頻繁提交鼓勵(lì)開(kāi)發(fā)人員以幾個(gè)小時(shí)為單位來(lái)分割他們的代碼,這樣便于跟蹤進(jìn)度。通常,人們一開(kāi)始認(rèn)為在短短的幾個(gè)小時(shí)內(nèi)做不了什么事情,但我們發(fā)現(xiàn)找個(gè)導(dǎo)師和多實(shí)踐可以幫助他們學(xué)習(xí)。

  每次提交都應(yīng)在集成機(jī)上進(jìn)行構(gòu)建

  有了每日提交,也就又了每日測(cè)試,這應(yīng)該表明主線處于健康狀態(tài)。但是在實(shí)踐中,的確有出錯(cuò)的時(shí)候,原因之一在于紀(jì)律——有人并沒(méi)有在提交之前進(jìn)行本地更新和構(gòu)建。另外,不同開(kāi)發(fā)機(jī)之間的環(huán)境不同也是一個(gè)原因。

  因此,你應(yīng)該保證在集成機(jī)上進(jìn)行構(gòu)建,只有當(dāng)集成機(jī)上構(gòu)建成功后,才表明你的任務(wù)完成了。由于提交者需要對(duì)自己的提交負(fù)責(zé),他就得盯著主線上的構(gòu)建,如果失敗,馬上修改。如果下班之前你提交的修改失敗了,那么,對(duì)不起,請(qǐng)修改好了才回家。

  我見(jiàn)到過(guò)兩種方式來(lái)保證主線構(gòu)建的成功:一是手動(dòng)構(gòu)建,二是使用持續(xù)集成服務(wù)器

  手動(dòng)構(gòu)建是最簡(jiǎn)單的,基本上與開(kāi)發(fā)者在本地做的構(gòu)建差不多——先到集成機(jī)上拉下主線的最新代碼,然后運(yùn)行構(gòu)建命令,在構(gòu)建過(guò)程中你得盯著構(gòu)建過(guò)程,如果構(gòu)建成功,表明你的任務(wù)完成。(另見(jiàn)Jim Shore的描述。)

  持續(xù)集成服務(wù)器則一直監(jiān)視著代碼庫(kù),一旦檢測(cè)到有提交,便自動(dòng)拉下代碼到本機(jī),然后開(kāi)始構(gòu)建,并將結(jié)果通知提交者。只有當(dāng)提交者收到通知后——通常是以電子郵件的方式,才表明自己的任務(wù)完成。

  在ThoughtWorks,我們是持續(xù)集成服務(wù)器的忠實(shí)粉絲,我們領(lǐng)導(dǎo)了CruiseControlCruiseControl.NET的初期開(kāi)發(fā),此兩者均是廣為使用的CI服務(wù)器。從那時(shí)起,我們也開(kāi)發(fā)了商業(yè)化的Cruise。在幾乎每個(gè)項(xiàng)目中,我們都使用了CI服務(wù)器,并且結(jié)果是令人愉悅的。

  不是所有人都傾向于使用CI服務(wù)器的,Jim Shore便給出了一個(gè)很好的論述,在此論述中,他解釋了為什么他更傾向于手動(dòng)構(gòu)建。我同意他的看法——CI不過(guò)是安裝一些軟件而已,所有的實(shí)踐都應(yīng)當(dāng)旨在有效地完成持續(xù)集成。但同樣,許多使用CI服務(wù)器的團(tuán)隊(duì)的確發(fā)現(xiàn)CI服務(wù)器是很好的工具。

  有很多團(tuán)隊(duì)定期的進(jìn)行構(gòu)建,比如每晚構(gòu)建。這和持續(xù)構(gòu)建并不是一回事,而且對(duì)于持續(xù)集成來(lái)說(shuō),也是不夠的。持續(xù)集成的關(guān)鍵在于盡快地發(fā)現(xiàn)問(wèn)題。而每晚構(gòu)建意味著整個(gè)白天都發(fā)現(xiàn)不了bug,如此,需要很長(zhǎng)的時(shí)間發(fā)現(xiàn)并清楚這些bug。

  持續(xù)構(gòu)建的重點(diǎn)在于,如果主線構(gòu)建失敗,你應(yīng)該馬上進(jìn)行修改。在持續(xù)集成中,你一直是在一個(gè)穩(wěn)定的代碼庫(kù)基礎(chǔ)上進(jìn)行開(kāi)發(fā)。主線構(gòu)建失敗并不是一件壞事,但是,如果這樣的情況經(jīng)常發(fā)生,那么就意味著開(kāi)發(fā)人員對(duì)于本地更新并沒(méi)在意或者在提交之前并沒(méi)在本地構(gòu)建。主線構(gòu)建一旦失敗,必須馬上修正。為了避免主線構(gòu)建失敗,也許你可以試試 pending head

  快速構(gòu)建

  持續(xù)集成的關(guān)鍵在于快速反饋,需要長(zhǎng)時(shí)間構(gòu)建的CI是極其糟糕的。我的多數(shù)同事都認(rèn)為一個(gè)小時(shí)的構(gòu)建時(shí)間對(duì)于CI來(lái)說(shuō)決無(wú)道理可言。我也記得曾經(jīng)有團(tuán)隊(duì)夢(mèng)想著他們的構(gòu)建能有多么多么的快,但有時(shí)我們不得不面對(duì)很難快速構(gòu)建的情況。

  對(duì)于多數(shù)項(xiàng)目來(lái)說(shuō),將構(gòu)建時(shí)間維持在10鐘之內(nèi)是合理的,這也是XP的方針之一,我們多數(shù)項(xiàng)目也達(dá)到了這個(gè)目標(biāo)。這種做法是值得的,因?yàn)檫@樣省下的時(shí)間是為開(kāi)發(fā)者節(jié)約的。

  如果你的構(gòu)建長(zhǎng)到了一小時(shí),那么想使其加速便不是那么容易了。對(duì)于企業(yè)級(jí)應(yīng)用來(lái)說(shuō),我們發(fā)現(xiàn)構(gòu)建時(shí)間的瓶頸通常發(fā)生在測(cè)試上,特別是那些需要于外部交互的測(cè)試——比如數(shù)據(jù)庫(kù)。

  可能最好的解決辦法是引入階段性構(gòu)建(也叫構(gòu)建管道或者部署管道),因?yàn)闃?gòu)建事實(shí)上是分階段性的。代碼提交后首先觸發(fā)的是構(gòu)建稱為提交構(gòu)建,提交構(gòu)建應(yīng)該快速完成,而棘手的是怎么保持速度與查找bug之間的平衡。

  提交構(gòu)建成功后,其他人便可自信的工作了。但是,你可能還有其它跑得比較慢的測(cè)試需要寫,這時(shí)可以用額外的機(jī)器來(lái)專門跑這些耗時(shí)的測(cè)試。

  一個(gè)簡(jiǎn)單的例子是將構(gòu)建分為兩個(gè)階段,第一個(gè)階段完成編譯,并且跑那些不需要外部交互的單元測(cè)試,數(shù)據(jù)庫(kù)交互也通過(guò)stub的方式完全消除掉。這些測(cè)試可以很快跑完,原則是將其保持在10分鐘之內(nèi)。但是,對(duì)于那些需要大量外部交互——特別是涉及到真實(shí)數(shù)據(jù)庫(kù)交互時(shí)才能發(fā)現(xiàn)的bug,這個(gè)階段便無(wú)能為力了。第二個(gè)階段跑的測(cè)試則需要操作真實(shí)的數(shù)據(jù)庫(kù)了,同時(shí)還應(yīng)包括端到端測(cè)試。這個(gè)階段可能需要幾個(gè)小時(shí)。

  在這種情況下,通常將第一階段視為提交構(gòu)建,并將此做為主要的CI周期。第二階段則可在有必要時(shí)才進(jìn)行,如果這個(gè)階段構(gòu)建失敗,它也不需要像第一階段那樣“停下全部手頭的工作”,但也應(yīng)該得到盡快的修改。第二階段的構(gòu)建不見(jiàn)得需要保持一直通過(guò),對(duì)于已經(jīng)發(fā)現(xiàn)的bug來(lái)說(shuō),可以在之后幾天修改。對(duì)于這個(gè)案例來(lái)說(shuō),第二階段全是測(cè)試,因?yàn)橥ǔG闆r下最慢的即是測(cè)試。

  如果第二階段構(gòu)建發(fā)現(xiàn)了bug,通常意味著應(yīng)該在第一階段中引入新的測(cè)試來(lái)予以保證。

  當(dāng)然,以上的兩階段構(gòu)建只是一個(gè)例子,你完全可以加入多個(gè)構(gòu)建階段。提交構(gòu)建之后的其它構(gòu)建是可以并行完成的,如果這些階段的構(gòu)建需要好幾個(gè)小時(shí),那么可以用幾臺(tái)機(jī)器來(lái)并行完成。通過(guò)這種并行化,你可以將提交構(gòu)建之外的所有測(cè)試都引入到構(gòu)建過(guò)程中來(lái),比如性能測(cè)試。

  在與生產(chǎn)環(huán)境的拷貝環(huán)境中運(yùn)行測(cè)試

  測(cè)試旨在發(fā)現(xiàn)可能在生產(chǎn)環(huán)境中出現(xiàn)的問(wèn)題,因此如果你的測(cè)試環(huán)境與生產(chǎn)環(huán)境不同,那么測(cè)試很有可能發(fā)現(xiàn)不了生產(chǎn)環(huán)境中的bug。

  因此,你的測(cè)試環(huán)境應(yīng)該盡量與生成環(huán)境相同。使用相同的數(shù)據(jù)庫(kù),相同的操作系統(tǒng),并且版本都應(yīng)該一樣。另外,將生產(chǎn)環(huán)境中的庫(kù)文件也放到測(cè)試環(huán)境中,即使構(gòu)建時(shí)用不到這些庫(kù)。IP地址和端口號(hào)也應(yīng)當(dāng)相同,當(dāng)然還包括硬件。

  但事實(shí)上這是有限制的。如果你開(kāi)發(fā)的是桌面軟件,很難預(yù)測(cè)你的客戶在使用哪些第三方庫(kù)。再者,生產(chǎn)環(huán)境可能非常昂貴。即便存在這么多限制,你依然應(yīng)當(dāng)盡量去復(fù)制生產(chǎn)環(huán)境,并熟知因測(cè)試環(huán)境和生產(chǎn)環(huán)境的不同而可能導(dǎo)致的風(fēng)險(xiǎn)。

  如果你搭建的環(huán)境足夠簡(jiǎn)單并沒(méi)有多少煩人的外部交互,那么你的提交構(gòu)建便可在仿真環(huán)境中進(jìn)行。但是,由于系統(tǒng)反應(yīng)慢等原因,你可能需要test doubles。因此,通常情況是在人工環(huán)境下跑提交構(gòu)建以獲取速度,而用一個(gè)生產(chǎn)環(huán)境的拷貝環(huán)境來(lái)跑其它測(cè)試。

  我注意到,虛擬化技術(shù)越來(lái)越引起人們的興趣。由于虛擬機(jī)可以保存構(gòu)建所需的所有東西,故在虛擬機(jī)中運(yùn)行構(gòu)建和測(cè)試相對(duì)比較容易。另外,虛擬機(jī)技術(shù)也允許你在一臺(tái)機(jī)器上運(yùn)行多個(gè)測(cè)試,或者可以模擬多臺(tái)機(jī)器同時(shí)訪問(wèn)網(wǎng)絡(luò)的情況。隨著虛擬機(jī)性能逐漸提升,它將引起更多的注意。

  使任何人都能輕易獲得可執(zhí)行文件

  軟件開(kāi)發(fā)最困能的事情之一便是你不能保證所開(kāi)發(fā)的是正確的軟件。我們發(fā)現(xiàn)人們往往很難預(yù)知自己究竟想要什么,而相反,對(duì)已有的東西進(jìn)行評(píng)判和修改卻容易的多。敏捷開(kāi)發(fā)過(guò)程則恰恰是符合人類這種行為習(xí)慣的。

  為此,項(xiàng)目中的所有成員都應(yīng)能夠獲得最新的可執(zhí)行文件并能成功的運(yùn)行,目的可以包括做演示,瀏覽測(cè)試或者僅僅看看項(xiàng)目本周有何修改。

  這是很容易達(dá)到的:確保一個(gè)通用的地方來(lái)存放最新可執(zhí)行文件。在同一個(gè)地方存放多個(gè)可執(zhí)行文件也是很有用的。對(duì)于最新的可執(zhí)行文件,應(yīng)當(dāng)保證能夠通過(guò)提交測(cè)試。

  如果你的開(kāi)發(fā)過(guò)程有一個(gè)很好的迭代計(jì)劃,將每次迭代最后一次構(gòu)建生成的可執(zhí)行文件存放起來(lái)也是明智的做法。

  人人都能看到正在發(fā)生什么

  持續(xù)集成主要在于交流,因此應(yīng)當(dāng)保證每人都能輕易看到當(dāng)前系統(tǒng)的狀態(tài)和已做的修改。

  主線的構(gòu)建狀態(tài)是非常重要的,Cruise服務(wù)器包含一個(gè)網(wǎng)站,你可以在該網(wǎng)站上看到當(dāng)前的構(gòu)建狀態(tài)和最后一次主線構(gòu)建的結(jié)果,許多團(tuán)隊(duì)喜歡用比較顯眼的標(biāo)識(shí)來(lái)反應(yīng)構(gòu)建狀態(tài),比如在屏幕上放一盞燈,燈綠表示構(gòu)建成功,燈紅表示失敗。尤其常見(jiàn)的是lava lamps——不僅表明構(gòu)建狀態(tài),還可顯示構(gòu)建時(shí)間。如果紅燈中有了氣泡,則表明構(gòu)建已經(jīng)失敗了很長(zhǎng)一段時(shí)間了。每個(gè)團(tuán)隊(duì)都有自己的選擇,當(dāng)然,適合自己的才是最好的。

  對(duì)于手工完成的持續(xù)集成過(guò)程,這種可見(jiàn)性也是很重要的,構(gòu)建機(jī)器的顯示器應(yīng)該能顯示主線構(gòu)建的狀態(tài)。通常,正在做集成的人會(huì)放一個(gè)token在桌上來(lái)表明他正在做集成。人們喜歡在構(gòu)建成功后播放一些簡(jiǎn)單的聲音,比如鬧鈴之類的。

  當(dāng)然,CI服務(wù)器的網(wǎng)站可以展示更多的信息。Cruise不但能可以顯示是誰(shuí)在構(gòu)建,并且能顯示最新提交的修改。另外,Cruise還可以查看提交歷史,這樣,團(tuán)隊(duì)成員便可以很清楚項(xiàng)目的進(jìn)展情況。據(jù)我所知,有些團(tuán)隊(duì)的頭便是通過(guò)這種方式來(lái)了解項(xiàng)目成員的工作情況和整個(gè)系統(tǒng)的修改情況。

  使用CI網(wǎng)站的另一個(gè)好處是,哪怕不在一起工作的人都可以看到當(dāng)前項(xiàng)目的狀態(tài)。再者,你也可以將不同項(xiàng)目的構(gòu)建信息放到一起。

  并不是只有CI網(wǎng)站才能展示顯示構(gòu)建信息。由于構(gòu)建的不穩(wěn)定性是一直存在的,這時(shí)我們可以將全年的日歷畫在一張墻上,每天對(duì)應(yīng)一個(gè)方塊,如果構(gòu)建成功,QA則在該天的方塊貼上綠色標(biāo)簽,否則貼上紅色標(biāo)簽。時(shí)間一久,這份日歷便可顯示出項(xiàng)目的穩(wěn)定性進(jìn)展情況。

  自動(dòng)化部署

  做持續(xù)集成需要多種環(huán)境,不同的構(gòu)建階段需要不同的環(huán)境。每天,項(xiàng)目的可執(zhí)行文件都會(huì)在這些環(huán)境之間搬來(lái)移去,于是你希望將這些過(guò)程自動(dòng)化。因此,自動(dòng)化部署腳本便很重要了,不僅包括測(cè)試環(huán)境的腳本,也包括針對(duì)生產(chǎn)環(huán)境的部署腳本。雖然我們不是每天都向生產(chǎn)環(huán)境部署,但自動(dòng)化部署不僅可以加速部署過(guò)程,并且能夠減少部署錯(cuò)誤。

  如果你已經(jīng)有了生產(chǎn)環(huán)境的自動(dòng)化部署,那么也應(yīng)該考慮一下相應(yīng)的自動(dòng)化回滾。由于失敗是時(shí)而會(huì)發(fā)生的事情,在這種情況下,我們希望能快速回滾到失敗之前的狀態(tài)。這樣一來(lái),我們?cè)诓渴鹗且膊挥媚敲次肥孜肺擦耍谑俏覀兛梢灶l繁的發(fā)布軟件,用戶亦能盡快的享受到新的功能。(Ruby on Rails社區(qū)有一款名為Capistrano的工具即是一個(gè)典型的例子。)

  在集群環(huán)境中,我看到有每次只向一個(gè)節(jié)點(diǎn)部署的情況,由此在幾個(gè)小時(shí)之內(nèi)逐漸完成所有節(jié)點(diǎn)的部署。

  對(duì)于一些面向大眾的web應(yīng)用,我所了解的另外一種很有趣的部署方式是,先試驗(yàn)性針對(duì)一部分用戶進(jìn)行部署,再通過(guò)這些用戶的試用情況來(lái)決定是否向所有用戶部署。自動(dòng)化部署,做為CI的一項(xiàng)原則,能夠很好的勝任這些工作。

  持續(xù)集成的好處

  總的來(lái)說(shuō),我認(rèn)為持續(xù)集成的最大好處在于降低風(fēng)險(xiǎn)。我又想起了我在本文一開(kāi)始提到的那個(gè)項(xiàng)目——已經(jīng)處于項(xiàng)目的末期,但是仍然不知到何時(shí)才能結(jié)束。

  延期集成的缺點(diǎn)在于,很難預(yù)測(cè)集成到底要花多少時(shí)間,更糟的是,你很難了解集成的進(jìn)展情況。

  持續(xù)集成正好解決了這些問(wèn)題。每次集成的時(shí)間都不長(zhǎng),任何時(shí)候你都知道自己所處的情況,軟件的哪些地方在工作,哪些沒(méi)有。

  Bug——惡心的玩意兒,傷害我們的自信,攪亂我們的日程,還破壞我們的名聲。如果在生產(chǎn)環(huán)境中遇到了bug,那么用戶將會(huì)把氣往你身上撒。而在開(kāi)發(fā)環(huán)境中,bug攔著你的路,迫使你無(wú)法完成余下的工作。

  持續(xù)集成并不能消除bug,卻能幫你快速的發(fā)現(xiàn)bug并予以清除。這種情況下,持續(xù)集成更像是自測(cè)試的代碼。當(dāng)遇到bug時(shí),由于你只做了很小的修改,這樣便大大縮小的bug的查找范圍。另外,由于是你剛寫的代碼,你還記得很清楚,因此也使查找bug更加容易。你還可以使用diff debugging,將當(dāng)前的代碼版本和先前沒(méi)有bug的版本進(jìn)行比較。

  Bug也存在積累性,bug越多,越難清除。部分原因在于bug之間存在牽連。另外也存在心理因素,bug一多,人便沒(méi)那么多精力去修了——這就是所謂的“Broken Windows 綜合征”。

  因此,對(duì)于采用持續(xù)集成的團(tuán)隊(duì),bug將大大減少,不管是在生產(chǎn)環(huán)境,還是在開(kāi)發(fā)環(huán)境。但是,我想強(qiáng)調(diào)的是,你的獲益程度取決于測(cè)試的好壞程度。你或許已發(fā)現(xiàn),寫出好多測(cè)試并不難。然而,要達(dá)到低bug率的程度依然是需要時(shí)間的,你還得不斷地引入并改進(jìn)自己的測(cè)試。

  有了持續(xù)集成,頻繁部署也不是什么難事了。頻繁部署的價(jià)值在于,你的客戶可以快速的享用軟件的新功能,并能快速的提出反饋。這將有利于清除客戶和開(kāi)發(fā)之間的障礙——我認(rèn)為這是軟件開(kāi)發(fā)最大的障礙。

  引入持續(xù)集成

  然后你開(kāi)始試著玩持續(xù)集成了,但該從何入手呢?上文中我所羅列持續(xù)集成實(shí)踐可以給你帶來(lái)太多的好處,但是你并不必在一開(kāi)始就完全采用這些實(shí)踐的。

  做持續(xù)集成沒(méi)有套路,主要取決于你團(tuán)隊(duì)自身的情況,但是我們發(fā)現(xiàn)以下幾點(diǎn)對(duì)于持續(xù)集成來(lái)說(shuō)是比較通用的。

  (1)第一步需要將構(gòu)建自動(dòng)化,并將你所需的所有東西都放在代碼管理系統(tǒng)中,以至于可以通過(guò)一個(gè)命令來(lái)構(gòu)建整個(gè)系統(tǒng)。對(duì)很多項(xiàng)目來(lái)說(shuō),這并非易事。一開(kāi)始,你可以按照需要進(jìn)行構(gòu)建,或者可以只做自動(dòng)化的夜晚構(gòu)建。雖然,這些做法都不能稱為持續(xù)集成,但夜晚構(gòu)建確是一個(gè)好的起點(diǎn)。

  (2)在構(gòu)建中引入一些自動(dòng)化測(cè)試,試著確定出現(xiàn)問(wèn)題的主要范圍,并用自動(dòng)化測(cè)試去發(fā)現(xiàn)這些問(wèn)題。對(duì)于已有的項(xiàng)目來(lái)說(shuō),很難建立起一組好的快速測(cè)試,這時(shí)你就得另尋它路了。

  (3)使提交構(gòu)建快速完成。雖然好幾個(gè)小時(shí)的持續(xù)集成比沒(méi)有要好,但是如果你能將構(gòu)建時(shí)間縮短到幾十分鐘,或者就短短的10分鐘,這就再好不過(guò)了。

  (4)對(duì)于新項(xiàng)目,從項(xiàng)目開(kāi)始就采用持續(xù)集成。注意構(gòu)建時(shí)間,如果構(gòu)建時(shí)間違背了“10分鐘原則”,那么請(qǐng)盡快采取行動(dòng)。

  (5)尋找?guī)椭矣薪?jīng)驗(yàn)的人幫助你。和其它的新技術(shù)一樣,當(dāng)不知到結(jié)果會(huì)是什么樣時(shí),很難開(kāi)頭。找一個(gè)導(dǎo)師可能要花錢,但是不找的話,你所付出的代價(jià)是時(shí)間的浪費(fèi)和低下的生產(chǎn)力。(ThoughtWorks提供這樣的咨詢服務(wù),畢竟你可能遇到的問(wèn)題我們之前都遇到過(guò)。)  

  總結(jié)

  自Matt和我發(fā)布了本文的第一版之后,持續(xù)集成逐漸變成了軟件開(kāi)發(fā)的主流技術(shù),在ThoughtWorks,幾乎所有的項(xiàng)目都使用到持續(xù)集成,同時(shí)我們也看到世界上其他組織也在使用持續(xù)集成技術(shù)。相比起充滿爭(zhēng)議的極限編程來(lái)說(shuō),持續(xù)集成很少得到負(fù)面的評(píng)論。

  如果你還沒(méi)有采用持續(xù)集成,我強(qiáng)烈建議你試一試。如果你已經(jīng)采用了持續(xù)集成,本文可能會(huì)幫助你進(jìn)一步提高效率。這些年來(lái),我們已經(jīng)學(xué)到了許多關(guān)于持續(xù)集成的知識(shí),我們也希望有更多可以學(xué)習(xí)和改進(jìn)的地方。  

  延伸閱讀

  像本文這樣的文章通常只能涵蓋一些基本,但它卻是一種重要的話題,所以我在自己網(wǎng)站上放了一個(gè)guide page,那里你可以獲得更多的信息。

  如果想了解持續(xù)集成更多的細(xì)節(jié),我建議Paul Duvall(Jolt獎(jiǎng)得主)的Continuous Integration: Improving Software Quality and Reducing Risk。對(duì)于更寬泛的持續(xù)交付,可以看看Humble 和 Dave Farley的Continuous Delivery: Reliable Software Releases through Build, Test, and Deployment Automation

it知識(shí)庫(kù)重溫大師經(jīng)典:Martin Fowler 的持續(xù)集成,轉(zhuǎn)載需保留來(lái)源!

鄭重聲明:本文版權(quán)歸原作者所有,轉(zhuǎn)載文章僅為傳播更多信息之目的,如作者信息標(biāo)記有誤,請(qǐng)第一時(shí)間聯(lián)系我們修改或刪除,多謝。

主站蜘蛛池模板: gogo亚洲肉体艺术照片9090 | 亚洲 自拍 偷拍 另类综合图区 | 无码一卡二卡三卡四卡 | 国产午夜免费不卡精品理论片 | 99久久国产宗和精品1上映 | 欧美日韩无套内射另类 | 久久久无码精品亚洲日韩按摩 | 人禽l交视频在线播放 视频 | 精品水蜜桃久久久久久久 | 最近的2019中文字幕国语版 | 国产高清视频在线观看97 | 野花韩国在线观看 | 桃色窝| 亚洲中文无码亚洲人在线观看- | 91精品视频网站 | 国产成人精品一区二区三区视频 | 最新无码二区日本专区 | 国产成人99久久亚洲综合精品 | 成人在线视频免费 | 男人插女人动态 | 久久精品国产免费播放 | 国产一区二区精品视频 | 伊人久久精品AV无码一区 | 益日韩欧群交P片内射中文 艺术片 快播 | 一本久道久久综合婷婷五月 | 我要女人的全黄录像 | 色欲AV精品人妻一区二区三区 | 国产精品久AAAAA片 | 高h原耽肉汁动漫视频 | 啊灬啊灬啊灬快灬深高潮啦 | 99视频免费观看 | 牛牛在线(正)精品视频 | 欧美整片华人play | 色综合久久久久久 | 高龄熟女50P | 中文字幕乱码一区AV久久 | 妖精视频在线观看高清 | 国产午夜亚洲精品区 | 高跟丝袜岳第一次 | 日日操夜夜摸 | 青青久在线视频免费观看 |