系列文章目錄:
一.測試象限(Brain Marick)
二.測試金字塔(Mike Cohn)
1.單元測試
通常只測試一個函數或方法調用,通過TDD或者基於屬性而寫的測試就屬於這一類,在UnitTest中,我們不會啟動服務,對且對外部文件和網絡連接的使用也很有限,通常我們需要大量的單元測試。
單元測試是幫助開發人員,是面向技術而非業務的。
2.服務測試
對於包含多個服務的系統,一個服務測試只測試其中一個單獨服務的功能。只測試一個單獨的服務可以提高測試的隔離性,這樣我們可以更快地定位並解決問題,為了達到這種隔離性,我們需要給所有的外部合作者打樁,以便只把服務本身保留在測試范圍內。(打樁是指為被測服務的請求創建一些有預調響應的服務。)
對於每一位下游合作者,我們都需要一個打樁服務,然后在運行服務測試的時候啟動它們,在測試過程中連接這些打樁服務,為了模仿真實的服務,我們還需要配置打樁服務返回請求響應。例如,我們可以配置積分賬戶為不同的客戶返回不同的預設積分。
另一種替換打樁有方式是mock。與打樁相比,mock還會進一步驗證請求本身是否被正確調用,如果與期望請求不匹配,測試便會失敗,過度使用mock會讓測試變得脆弱。兩者之間需要一些權衡。
我們可以使用一些打樁的軟件來協助我們測試,如Mountebank等。
3.UI測試(端到端測試)
對於包含多個微服務的系統來說,“端到端的測試”這個名稱相比“UI測試”可能更適合,端到端測試會覆蓋整個系統,這類測試通常用圖形界面來模仿用戶交互動作。這類的測試會覆蓋大范圍的產品代碼,因此當它們通過時你會感覺很好。
因為端到端測試涉及到多個微服務,我們可以用扇入模型來解決多個微服務同時開發,同時測試的問題。
雖然扇入模型可以來解決一些難題,但是端到端測試仍然有着很多的缺點。
首先,端到端測試非常脆弱。
有時測試失敗並不是因為功能真的被破壞了,而是由其他一些原因引起的,比如其他服務停止或者網絡故障等,這些跟測試的功能本身沒有關系。
包含在測試中的服務數量越多,測試就會越脆弱,不確定性也就越強。
一個包括脆弱測試的測試套件往往成為Diane Vaughn所說的異常正常化的受害者,也就是說,隨着時間的推移,我們對事情出錯變得習以為常,並開始接受它們是正常的。
Martin Flower在http://martinflower.com/articles/nonDeterminism.html中提議,在碰到脆弱的測試時應該立刻記錄下來,當不能立即修復時,需要把它們從測試套件中移除,然后就可以不受打擾地安心修復他們。修復時,看能否通過重寫來避免被測代碼運行在多個線程中,再看是否能讓運行的環境更穩定,更好的辦法是,看能否用不易出問題的小范圍測試取代脆弱的端到端測試。
單個微服務的測試可以由這個特性團隊來維護。那么涉及到多個微服務的端到端測試該由誰負責?
雖然這種責任分配方式還在探索當中,但將測試對所有人開放或者把這些測試交給專門的團隊來維護都是錯誤的,前者可能會導致團隊成員可以在無須對測試套件質量有任何理解的情況下隨意添加測試,這會導致測試用例爆炸,有時導致測試沒有真正的擁有者,當測試失敗時,每個人都認為這是別人的問題,而不在乎測試是否通過;后者則會導致開發人員沒有在第一時間得到自己代碼的測試反饋,這會出現很大問題。一個可能的解決方案是,讓各特性團隊共享端到端測試套件的代碼權,對測試套件聯合負責。
端到端的測試通常需要的時間最長,如果經常有與功能破壞無關的測試失敗,這就是個災難,這樣即使真的是功能被破壞了,也需要花很多長時間才能發現,而此時大家已經轉而做其他的事情了,切換大腦的上下文來修復測試是很痛苦的。
我們可以用Selenium Grid工具通過並行運行測試來改善緩慢的問題,但是長期的積累,測試用例越來越多,但我們卻不敢刪除測試,而這可能是端到端測試面臨着管理、維護困難的局面。我們應該改變我們的端到端測試思路,把測試整個系統的重心放到少量核心的場景上來,把任何在這個核心場景之外的功能放到相互隔離的服務測試中覆蓋。
同時,人們容易有這樣的想法:既然所有服務在這些版本下能夠一起工作,為什么不一起部署它們呢?如果接受了這個觀念,慢慢的這成了常態,我們就會丟棄微服務的主要優勢之一,獨立於其他服務單獨部署一個服務的能力。不用很長時間,本來分享地很好的服務就會與其他服務糾纏得越來越緊密,最終系統雜亂無序,你必須同時部署多個服務。這非常糟糕。
4.三種測試之間的權衡
越靠近金字塔的頂端,測試覆蓋的范圍越大,同時我們對被測后的功能也越有信心,缺點則是需要更長的時間運行測試,反饋周期也會變長,測試失敗后很難定位哪個功能被破壞;而靠近金字塔的底部,測試更快,反饋周期也會變短,測試失敗后更容易定位破壞的功能和代碼,CI也很短。一般來說,順着金字塔向下,下一層的測試數量要比上一層多一個數量級。
三.消費者驅動測試
使用之前的端到端測試,我們試圖解決的關鍵問題是什么?是試圖確保部署新的服務到生產環境后,變更不會破壞新服務的消費。有一種不需要用真正的消費者也能達到相同的目的,我們可以使用消費者驅動的契約(CDC)。當使用CDC時,我們會定義服務的消費者的期望,這些期望最終會變成對生產者運行的測試代碼,如果使用得到,CDC應該成為服務提供者CI的一部分,這樣可以確保如果這些契約被破壞了,服務提供者無法部署。
讓我們回頭看一下前面圖片中的例子,客戶服務有兩個獨立的消費者:幫助台和Web客戶端,我們將創建兩個測試集合(其實就是模擬消費者),每個集合分別體現幫助台和Web客戶端對客戶服務的使用方式。因為這些CDC是對客戶服務如何工作的期望,所以,客戶服務本身的所有下游依賴都可以使用打樁,從測試范圍來看,它與測試金字塔中的服務測試位於同一層,但側重點不同,這些測試重在消費者如何使用服務。
Pack(https://github.com/realestate-com-au/pact )是一個消費者驅動的測試工具,開始時,消費者使用Ruby DSL來定義生產者的期望,然后啟動本地一個mock服務器(以后就可以單獨地來測試消費者),並對其運行這些期望來生成pact規范文件,pack規范文件是一個標准的JSON規范;在生產者這邊,你可以使用 JSON Pact規范來驅動對生產者API的調用,然后驗證響應以測試消費者的規范是否被滿足,因為生產者代碼需要訪問Pact文件,所以我們這里采用JSON。
Pack的JSON規范是由消費者生成的,該規范需要成為一個生產者可訪問的構建物,你可以把它存儲在CI/CD倉庫或Pack Broker中。
另外Pacto(https://github.com/thoughtworks/pacto )和janus(https://github.com/gga/janus )也是開源的消費契約測試工具。
四.線上測試
在生產環境我們也需要進行測試,我們可以在高峰或請求來臨之前進行測試,這樣可以發現特定環境中的問題。
1.冒煙測試
我們可以對生產環境進行冒煙測試,用來幫助我們識別與環境有關的任何問題,我們應該把冒煙測試加入到我們的部署腳本中。
2.藍綠發布
使用藍綠發布時,我們會部署兩份軟件,但只有一個接受真正的請求。我們對新部署的版本運行一些測試,等測試沒有問題時,我們再切換生產負荷到新部署的版本,通常情況下,我們會保留舊版本一小段時間,這樣如果發生任何錯誤,能夠快速恢復到舊的版本。這樣可以降低風險,還可以大幅減少發布軟件所需要的停機時間,我們甚至可以達到零宕機部署。
3.金絲雀發布
金絲雀發布是指通過將部分流量引流到新部署的系統,來驗證系統是否按預期執行,與藍綠發布不同,金絲雀發布過程中,新舊版本共存的時間更長,而且經常會調整流量。
當考慮使用金絲雀發布時,你需要選擇是引導部分生產流量到金絲雀,還是直接復制一份生產請求,如果你選擇復制一份生產請求,然后引導師復制的請求至金絲雀,這樣現行的版本和金絲雀版本將面對相同的請求,只是生產環境的請求是外部可見的,這樣方便大家對新舊版本做比較,同時又避免金絲雀發布失敗時影響客戶的請求,但復制請求的工作可能會很復雜,尤其是在請求不冪等的情況下。
4.灰度發布
灰度發布是指在黑與白之間,能夠平滑過渡的一種發布方式。AB test就是一種灰度發布方式,讓一部分用戶繼續用A,一部分用戶開始用B,如果用戶對B沒有什么反對意見,那么逐步擴大范圍,把所有用戶都遷移到B上面來。灰度發布可以保證整體系統的穩定,在初始灰度的時候就可以發現、調整問題,以保證其影響度。
這些部署技術的原則,無外乎說明:更快的從錯誤中修復比千方百計地防范錯誤更重要,當然這不是說防范錯誤不重要,而是因為錯誤不可避免。
五.非功能測試
非功能測試包括一些服務延遲測試、並發量測試、負載量測試、安全性測試、性能測試等。微服務拆分之后,會導致一個功能引發多個微服務的協同工作,這樣跨網絡邊界調用的次數明顯增加了,因此性能測試也是一件非常重要的事。
參考
《微服務設計》(Sam Newman 著 / 崔力強 張駿 譯)