什么是微服務?
微服務的由來
微服務的前身是 Peter Rodgers 博士在 2005 年度云端運算博覽會上提出的微 Web 服務 (Micro-Web-Service) 。微軟的 Juval Löwy 隨后也提出了類似的想法,并提議將其作為微軟下一階段最主要的軟件架構。
2014年,Martin Fowler 與 James Lewis 共同提出了微服務的概念,給出了微服務的具體定義:從本質上來說,微服務是一種架構模式。它是面向服務型架構(SOA)的一種變體,提倡將單一應用程序劃分成一組小的服務,服務之間互相協調、互相配合,為用戶提供最終價值。每個服務運行在其獨立的進程中,服務與服務之間采用輕量級的通信機制互相溝通(通常是基于 HTTP 的 RESTful API)。每個服務都圍繞著具體業務進行構建,并且能夠被獨立地部署到生產環境、類生產環境等。另外,應盡量避免統一的、集中式的服務管理機制,對具體的一個服務而言,應根據業務上下文,選擇合適的語言、工具對其進行構建。
Martin Fowler 是國際著名的軟件專家,敏捷開發方法的創始人之一,現為 ThoughtWorks 公司的首席科學家。在面向對象分析設計、UML、模式、軟件開發方法學、XP、重構等方面,都扮演著舉足輕重的開創者角色。早在20世紀80年代,Fowler 就是使用對象技術構建多層企業應用的倡導者,他著有幾本經典書籍: 《企業應用架構模式》、《UML精粹》和《重構》等。
微服務與傳統開發方式的區別
與微服務架構相對應,傳統開發方式通常被稱為單體式架構(Monolithic Architecture)。所有功能都打包在一起,基本沒有外部依賴,其中包含了數據輸入/輸出、數據處理、業務實現、錯誤處理、前端顯示等所有邏輯。
下圖顯示的一個典型的單體式架構示意圖:
這種架構有其優點,包括:
開發團隊的組織架構簡單,便于集中式管理。
因為開發進度統一管理,避免重復開發的問題。
所有功能都集中在本地,不存在分布式的管理和調用損耗。
但是,隨著現代應用程序的日益復雜化,加上對于迭代速度的要求越來越高,這種架構的不足開始暴露出來:
效率低:所有開發人員都在同一個項目下修改代碼,經常需要相互等待對方的功能更新,代碼入庫時的沖突不斷,造成極高的開發成本。
維護難:各個模塊的代碼都耦合在一起,一方面新人不知道從何下手,一方面一旦出現問題(Bug),就需要大改。在某個模塊需要升級時,也不得不升級整個應用程序。
不靈活:構建(Build)時間過長,任何一個小量級的修改,都要重構整個項目,非常耗時。
穩定性差:一個微小的問題,都可能導致整個進程崩潰,使得整個應用程序無法工作。
擴展性不夠:難以分布式部署和擴容,無法滿足高并發下的業務需求。而且,一旦業務范圍擴展或者需求有所變化,難以復用原有的服務,必須重新開發。
如何解決這些問題?微服務架構逐漸浮出水面。從軟件開發的組織上來說,它的核心理念是按照業務邊界把整個系統劃分為若干個“子系統”。每個子系統的開發團隊之間,保持著合作(Inter-Operate)而不是整合(Intergrate)的關系。定義好每個子系統的邊界和接口,在一個團隊內自治。團隊按照這樣的方式組建,溝通的成本維持在系統內部,每個子系統就會更加內聚,彼此的依賴耦合能變弱,跨系統的溝通成本也就能降低。
這里不得不提及著名的“康威定律”(Conway's Law),這是微服務架構的一個核心理念。
Melvin Conway 在1967年提出了這個理念,原文是:“Organizations which design systems are constrained to produce designs which are copies of the communication structures of these organizations.”
用簡單的話來說,就是組織形式等同于系統設計,組織的溝通方式會通過系統設計表現出來。下面這幅著名的軟件企業組織圖,與這些企業的產品架構有著異曲同工的對應關系。
再以上面提到的單體式 App 為例,通過用微服務架構方式對其進行改造,將會變成下面這種結構:
除了解決單體式架構的幾個缺陷以外,微服務架構還具有下面這些優點:
部署、回滾變得更快、更簡便。
微服務架構中,提倡針對不同的業務特征選擇合適的技術方案,有針對性的解決具體業務問題,而不是像單塊架構中采用統一的平臺或技術來解決所有問題。這樣就實現了技術的多元化,無需長時間鎖定于某一種技術棧,便于采用最新的工具。
每個服務都可以單獨擴容。
在需要發布新功能時,可以用插件的形式添加到系統中而不需要重新部署整個系統。
微服務架構提供自主管理其相關的業務數據,這樣可以隨著業務的發展提供數據接口集成,而不是以數據庫的方式同其他服務集成。另外,隨著業務的發展,可以方便地選擇更合適的工具管理或者遷移業務數據。
當然也需要提到,微服務架構也存在著它的不足:
由于把每個子系統分配各不同的團隊,這不僅意味著系統內部通信需求的增加,也帶來了不同團隊之間交流成本的提高。
在對基于微服務架構的分布式系統進行測試時,復雜度會大幅度提高。
分布式部署會給團隊的 DevOps 能力提出更高的要求。
當服務數量增加時,管理的復雜度也會指數級增加。
DevOps(Development 和 Operations 的組合詞)是一種重視軟件開發人員和 IT 運維技術人員之間溝通合作的文化、流程或者實踐方式。透過完全自動的“軟件交付”和“架構變更”流程,使得構建、測試、發布軟件更加快捷、頻繁和可靠。
微服務架構對測試人員意味著什么?
介紹完微服務架構以后,回到主題上來:對于測試人員而言,微服務架構到底有什么特點呢?我把它歸結為以下幾點:
1.每個服務承擔一定的職責:“盡可能小,但是又達到必要的規模(as small as possible but as big as necessary)” 。
在問答網站 Quora 上,有一個著名的問題:什么是程序員覺得最浪費時間的事情?排名第一的回答中提到:“不必要的微服務。”
這句話揭示了開發團隊在轉向微服務架構時經常走入的誤區。“微”固然重要,但是首要的是提供“服務”,這才構成“微服務”的價值。盲目地切分功能(Feature),卻沒有起到解耦合的作用,只是會增加維護、測試的成本。畢竟,多一項服務,就會多出一系列的流水線和測試要求。因此,測試、質量人員在面臨團隊計劃采取微服務架構的決策時,必須要敢于質疑:是否有這樣做的必要?目的是讓決策人員意識到這種轉型的潛在成本,避免花無用功。
2.微服務之間通常通過 Rest over HTTP 連接。
最常見的連接/交互方式,即通過 POST、GET、PUT、DELETE 這些命令操作 API,通過 JSON 傳遞參數。以下面這個典型的制造型企業的運營系統為例。在從單體式架構轉為微服務之后,不同功能模塊之間將通過 Rest 方式互相訪問。
這種簡易、明確的交互方式為契約測試(Contract Test)提供了基礎。
3.每種服務不一定提供用戶界面。
這意味著每種服務的測試,并不一定能夠或者需要從 UI 完成。這對 API 級別的集成化測試提出了要求。
4.微服務通常還可以劃分為更小的模塊。
如下圖所示,一個典型的微服務可以分為這幾個模塊:資源、業務邏輯、數據存儲接口、外部通信接口等。
這意味著,在對微服務架構進行測試時,可以從不同的模塊著手,進行相應的模塊測試。
總結
簡單總結一下所學習的內容:
微服務架構是針對單體式架構的不足,隨著應用程序復雜度的增加、部署頻率加快的要求,應運而生。
微服務架構帶來了簡化部署、隔離功能/缺陷、便于升級/擴容等優點,但也具有提高交流成本、增加測試復雜度等不足。
對于測試人員,微服務架構具備一些特別需要注意的特征,要求采用不同的測試方法加以應對。
微服務對軟件測試提出的挑戰
在上一節里,我們學習了微服務的來源和主要特點。對于軟件測試人員而言,微服務架構對軟件測試帶來了哪些新的挑戰呢?我們應該用什么樣的策略和方法來迎接這些挑戰?
總體的測試策略
軟件測試的目的是確保軟件產品的質量符合預期。衡量測試質量的指標有很多,最常見的是測試覆蓋率和測試成本(包括測試所用時間、測試維護成本),而衡量測試效果的主要手段則是最終產品在實際使用中暴露出來的問題數量(Bug Number)。
具體到采用微服務架構的產品而言,Martin Fowler 在關于軟件測試的論述中提出了其目的:
開發團隊采用的任何測試策略,都應當力求為服務內部每個模塊的完整性,以及每個模塊之間、各個服務之間的交互,提供全面的測試覆蓋率,同時還要保持測試的輕便快捷。
因此,我們需要采取下面幾點測試策略:
我們一方面要保證從各個維度上,無一遺漏地對微服務進行全面的測試,特別是對于分布式的系統,系統的所有層次都必須被覆蓋到;另一方面又要確保測試執行的快捷,這樣才能保證持續集成/持續交付(CI/CD)的實現。
要確保測試策略的正確實施,工具和技術固然重要,然而,首先需要測試人員在團隊中樹立起提倡質量第一的“測試文化”:
無法通過測試的代碼不應該被合并到代碼倉庫里;
無法通過測試的代碼不應該被發布出去。
不能為了測試而測試,測試的真正目的是為了交付高質量的軟件給用戶,而不是把資源浪費在沒有實際意義的測試用例上。所有的測試層次、流程和用例,都應該有的放矢。
傳統測試方法面臨的挑戰
以一個常見的開發團隊為例,在采用了微服務架構之后,很可能同時會開發多個模塊(即微服務),每個微服務有不同的客戶要求、開發周期、開發進度和交付期限,但是整個團隊又必須保證能夠在固定的時間節點(譬如每月一次、每兩周一次,甚至每天一次或者多次),持續地、穩定地為用戶提供可以部署、使用的產品。這意味著,過去那種先等產品經理、業務部門提供需求,開發人員再進行開發,最后交給測試人員執行集成測試、端到端測試的方法,已經無法提供足夠的測試粒度和足夠快的響應速度。
歸結起來,與基于單體式架構的傳統測試方法相比,微服務架構對測試提出了以下挑戰:
服務/模塊/層次(layer)之間存在復雜的依賴性。
在單體式架構中,通常使用集成測試來驗證依賴是否正常。而在微服務架構中,服務數量往往很多,每個服務都是獨立的業務單元,服務之間主要通過接口進行交互,如何保證這些依賴的正常,是測試人員面臨的主要挑戰。這意味著,如果想單獨測試某一個服務,或者服務中的某個模塊,就必須剝離它們對于其他環節的依賴關系。這需要通過 Mock、Stub 等方法來實現。
不同的服務可能會在不同的環境/設置下運行。
特別是一些后端服務,與前端服務的運行環境可能截然不同。這時在考慮對每種服務設立自動化管線時,就必須有針對性的設置相應的環境配置。而且,在微服務架構中,每個服務都獨立部署,交付周期短且頻率高,人工部署已經無法適應業務的快速變化。因此如何有效地構建自動化部署體系,保證配置的穩定性、可重復性,是微服務測試面臨的另一個挑戰,必須與 DevOps 人員一同解決。
涉及多個服務的 UI 端到端測試(End-to-End 測試,簡稱 E2E 測試)非常容易出錯。
因為每種服務的開發進度不同,集成不同服務的端到端測試往往會因為某一個服務的微小改動而出錯。這種出錯是測試人員希望避免的干擾信息。這意味著,對端到端測試的設計,必須采取一定的防干擾、防誤報策略。
測試結果可能取決于網絡的穩定性。
微服務架構是基于分布式的系統,而構建分布式系統必然會帶來額外的開銷。
性能: 分布式系統是跨進程、跨網絡的調用,受網絡延遲和帶寬的影響。
可靠性: 由于高度依賴于網絡狀況,任何一次的遠程調用都有可能失敗,隨著服務的增多還會出現更多的潛在故障點。因此,如何提高系統的可靠性、降低因網絡引起的故障率,是系統構建的一大挑戰。
異步: 異步通信大大增加了功能實現的復雜度,并且伴隨著定位難、調試難等問題。
數據一致性: 要保證分布式系統的數據強一致性,成本是非常高的,需要在 C(一致性)A(可用性)P(分區容錯性)三者之間做出權衡。
特別是涉及到數據存儲和外部通信的部分,如果在測試中不擺脫這些因素的影響,就可能會得到一些隨機性的誤報,干擾測試結果。
故障分析的復雜度會隨著服務的增加而提高。
微服務架構中,因為每個服務都需要獨立地配置、部署、監控和收集日志,因此在發現問題之后,進行診斷分析時,搜集缺陷信息的成本呈指數級增長。
與交付周期不同的開發團隊之間的交流成本。
這一點雖然跟技術無關,但是實際上會對測試人員的工作造成很大的困擾。因為開發模式分解為負責不同服務的多個小組,測試人員往往每天要花費大量的時間,了解不同團隊的開發進度。如果還需要手動進行回歸測試(Regression Test),最終將會不堪重負。所以自動化測試是必須采取的手段和方向。
如何應對這些挑戰,我總結了下面這三個原則:
1.自動化:測試任務的增加,要求測試人員必須把主要的精力用于將測試自動化,擺脫手動測試帶來的沉重負擔。當然,自動化測試必須足夠穩定、穩健,不能動輒誤報,否則反而會導致很高的維護成本。
2.層次化:這意味著采用分層次的測試方法,粒度由細到粗,范圍由小到大。下圖說明了幾個主要層次之間的關系:
這就是 Mike Cohn 提出的測試金字塔(Test Pyramid),其中最重要的兩個原則是:
應該用不同的粒度來測試應用程序;
層次越高,測試越少。
最底層的是單元測試(Unit Test),粒度最細,速度最快,維護成本也最低。往上是針對每種服務內部的各種模塊、業務流程的測試。最上面是基于前端 UI 的測試,這部分的粒度最粗,范圍最大(因為會覆蓋大多數服務),但是維護成本最高,因為稍微有些細微的變化就可能需要調整腳本。而且,由于基于前端,需要設置很多響應時間和等待時間,所以速度最慢。
Mike Cohn 是 Scrum 軟件開發方法的提出者之一,也是 Scrum 聯盟的創始成員。他目前是 Mountain Goat Software 公司的所有者,致力于提供關于 Scrum 和 Agile 軟件開發技術的培訓。
3.可視化:為了降低交流成本,最好的辦法就是讓所有的測試結果可視化。這意味著將構建(Build)、測試(Test)、部署(Deploy)所有這些相關任務構建在一個流水線之中,讓所有團隊成員都可以隨時監控項目進度,找到阻礙項目的瓶頸。
以下面這個典型團隊為例,整個從開發、測試、構建到部署的一系列過程,都可以借助 Jenkins 或者 TeamCity 這樣的任務調度工具,完全可視化,再借助 SonarQube 這樣的代碼質量監控工具監控測試結果。Google Analytics 或者 Microsoft 的 Azure ApplicationInsight 等云端監控工具,則可以提供實時生產環境的客戶使用信息或者測試數據,讓整個團隊可以隨時把握產品的整個流水線的運行狀態。
在微服務架構中所采用的主要測試方法。如下圖所示,它們主要包括:
單元測試(Unit Test)
用于驗證微服務內部的類方法或函數的行為。它們會根據測試框架,執行代碼文件里的類方法或函數,提供不同的輸入,并驗證與每一個輸入相對應的輸出。
集成測試(Integration Test)
用于驗證微服務與外部模塊的通信或者交互行為。測試框架會啟動服務的一個實例,并調用服務的外部接口來執行業務邏輯。
組件測試 (Component Test)
即驗證微服務能否起到預期的作用。這需要把微服務周邊依賴的所有其他服務或者資源全部模擬化,從該服務外部“用戶”的角度來檢查服務能否提供預期的輸出。
端到端測試(End-to-end Test)
驗證整個系統的功能能否符合用戶的預期,一般是從 UI 層面進行測試,確保用戶體驗完全達到客戶要求。
探索測試( Exploratory Test,即手動測試)
這一步通常由業務專家型用戶執行,具體查看某個新添加的特性是否開發、部署成功。
總結
簡單總結一下所學習的內容:
微服務架構對軟件測試提出了很多全新的挑戰。
應對這些挑戰的方法包括:
自動化
層次化
可視化
怎么針對微服務架構做單元測試?
單元測試是開發人員編寫的一小段代碼,用于檢驗被測代碼的一個很小的、很明確的功能是否正確。通常而言,一個單元測試是用于判斷某個特定條件(或者場景)下某個特定函數的行為。例如,你可能把一個很大的值放入一個有序 list 中去,然后確認該值出現在 list 的尾部?;蛘?,你可能會從字符串中刪除匹配某種模式的字符,然后確認字符串確實不再包含這些字符了。
對于單元測試中單元的含義,一般來說,要根據實際情況去判定其具體含義,如 C 語言中單元指一個函數,Java 里單元指一個類,前端應用中可以指一個窗口或一個菜單等??偟膩碚f,單元就是人為規定的最小的被測功能模塊。
我們將探討在微服務架構下,單元測試的設計、實現和質量控制。
設計:定義測試邊界
要設計高效率(既運行快速又覆蓋率高)的單元測試,首要要準確地定義測試邊界。測試的目的就是為了驗證邊界里“黑盒”的行為是否符合預期,我們向黑盒輸入數據,然后驗證輸出的正確性。在單元測試里,黑盒指的是函數或者類的方法,目的是單獨測試特定代碼塊的行為。
但是在微服務架構中,很多時候黑盒的輸出需要依賴于其他的功能或者服務,即存在外部依賴。為了更好地理解這個概念,我們以一個簡單的注冊功能為例:
從圖中可以看出,這個函數包含了一些輸入和輸出。輸入參數包括基本的用戶注冊信息(姓名、用戶名和密碼),而返回新創建的用戶 ID。
但是在此過程中,還有一些不是很明顯的輸入數據。這個函數調用了兩個外部函數:db.user.inser() 是向數據庫插入數據;Password.hashAndsave() 是一個微服務,用于生成密碼的哈希值,再加以保存。在某些情況下,數據庫可能會返回錯誤,比如用戶名已經存在,導致數據庫插入失敗。另外,因為需要調用外部的微服務生成密碼哈希值,如果網絡連接出現問題,或者哈希值生成服務由于發生過載而導致服務超時,那么密碼保存就會返回錯誤。User.create() 函數必須能夠妥善地處理這兩種錯誤,這是測試的重點。
也就是說,為了全面地測試用戶注冊功能,單元測試所要做的不僅僅是簡單地輸入各種不同的參數,它還要能夠讓外部函數/微服務,能夠產生出指定的錯誤,再驗證函數的錯誤處理邏輯是否符合預期。
因此,為了在不依賴于外部條件的情況下制造出各種輸入數據,就需要使用 Stub 或者 Mock,中文可以理解為對函數外部依賴的模擬器。簡而言之,它意味著用一個假的版本替換了真實的對象(例如一個類、模塊、函數或者微服務)。假的版本的行為特征和真實對象非常類似,采用相同的調用方法,并按照你在測試開始之前預定義的返回方式,提供返回數據。測試框架在運行被測試的函數時,可以把對外部依賴函數/服務的調用,重定向到 Stub 上,這樣單元測試就可以在沒有外部服務的情況下進行,即保證了速度,又避免了網絡條件的影響。
這里再強調下 Stub 和 Mock 的區別,很多人經常搞混。Stub 就是一個純粹的模擬器,用于替代真實的服務/函數,收到請求返回指定結果,不會記錄任何信息。Mock 則更進一步,還會記錄調用行為,可以根據行為來驗證系統的正確性。
創建 Stub 的工具有很多,包括 Node.js/JavaScript 框架下的 sinon.js, testdouble.js 等;Python 下的 mock 等。
在剛剛提到的注冊函數和密碼哈希值生成、保存服務之間,插入一個 Stub(模擬器)的示意圖如下:
我們可以使用模擬器來達到各種目的:
模擬器可返回任意的設定值,用于模擬外部函數的輸出。這在測試罕見的邊界情況時會非常有用,比如有些錯誤場景可能很少發生或者非常難以重現。
模擬器也可以捕捉被測試函數傳給外部函數的參數,或者把這些參數記錄下來。這樣就可以驗證被測試函數需要調用哪些外部函數,以及需要傳給外部函數哪些參數。
通過對外部依賴函數使用模擬器,通??梢栽趲酌腌妰?,執行數千個單元測試。這樣,開發人員就可以把單元測試加入到日常的開發工作管線(Pipeline)當中,包括直接集成到常用的 IDE 里,或者通過終端命令行觸發。通過在編寫代碼的同時,頻繁運行單元測試,有助于盡早發現代碼中的問題。對于程序員來說,如果養成了對自己寫的代碼進行單元測試的習慣,不但可以寫出高質量的代碼,而且還能提高編程水平。
順便說一句,在微服務架構中,單元測試的作用不僅限于代碼開發,它們還對 DevOps/CI(持續集成)有很大的幫助,可以集成到代碼合并(Merge)流程里。
譬如,GitHub 支持對一些主流 CI 服務的狀態檢查。一般它會限制對“Master”主分支的提交權限,不允許開發人員直接向該分支提交代碼,而是要求他們把代碼先提交到其他分支上(提交 Pull Request),再由其他開發人員進行代碼審查(Code Review)。最后,在將代碼合并到主分支的時候,GitHub 要求先通過狀態檢查。這時,Jenkins、CircleCI 和 TravisCI 等 CI 服務都提供了狀態檢查鉤子(hook),它們會從分支上獲取代碼并運行單元測試。如果通過了,就允許合并代碼,否則就不允許。整個過程如下圖所示:
實現:單元測試的流程
單元測試的工具有很多,例如:
C++:Googletest、GMock
Java:Junit、TestNG、Mockito、PowerMock
JavaScript:Qunit、Jasmine
Python:unittest
Lua:luaunit
一個單元測試的實現主要分為以下幾步:
設置測試數據;
在測試中調用你的方法;
判斷返回的結果是否符合預期。
這三步可以簡化為“三 A 原則”: Arrange(設置)、Act (調用)、Assert(檢查)。
或者也可以借用 BDD(行為驅動測試)的概念,把單元測試的流程分為三步:Given(上下文)、When (事件)、Then(結果)。
下面我們來看一個真實的例子,這是一個名為 ExampleController 的類,用于在人名庫(PersonRepository)中查找人名。
@RestControllerpublic class ExampleController { private final PersonRepository personRepo; @Autowired public ExampleController(final PersonRepository personRepo) { this.personRepo = personRepo; } @GetMapping("/hello/{lastName}") public String hello(@PathVariable final String lastName) { Optional<Person> foundPerson = personRepo.findByLastName(lastName); return foundPerson .map(person -> String.format("Hello %s %s!", person.getFirstName(), person.getLastName())) .orElse(String.format("Who is this '%s' youre talking about?", lastName)); }} |
下面,我們將用 Junit,對類中的 hello(lastname)方法進行單元測試。
JUnit 是 Java 社區中知名度最高的單元測試工具,用于編寫和運行可重復的測試用例。JUnit 設計得非常小巧,但是功能卻非常強大。它誕生于 1997 年,由 Erich Gamma 和 Kent Beck 共同開發完成。其中 Erich Gamma 是經典著作《設計模式:可復用面向對象軟件的基礎》一書的作者之一,并在 Eclipse 中有很大的貢獻;Kent Beck 則是一位極限編程(XP)方面的專家和先驅。
public class ExampleControllerTest { private ExampleController subject; @Mock // 模擬器 private PersonRepository personRepo; @Before // 在每個測試方法之前執行 public void setUp() throws Exception { initMocks(this); subject = new ExampleController(personRepo); } @Test // 測試用例1 public void shouldReturnFullNameOfAPerson() throws Exception { Person peter = new Person("東", "王"); given(personRepo.findByLastName("王")) .willReturn(Optional.of(東)); String greeting = subject.hello("王"); assertThat(greeting, is("你好王東!")); } @Test // 測試用例2 public void shouldTellIfPersonIsUnknown() throws Exception { given(personRepo.findByLastName(anyString())) .willReturn(Optional.empty()); String greeting = subject.hello("王"); assertThat(greeting, is("這位王先生是誰?")); }} |
Arrange(設置)、Act (調用)、Assert(檢查)。
可以看到,首先我們用一個 Stub(模擬器),替換真正的 PersonRepository 類,這樣我們可以預先定義我們希望返回的值。
記下來,我們按照 3A 原則,編寫了兩個單元測試。第一個是正常運行的用例:
Arrange(設置):建立一個名為王東的人物,并且讓模擬器準備好,在輸入參數為王時,返回“王東”。
Act(調用):調用函數 hello("王")。
Assert(檢查):檢查返回結果是否為"你好王東!"。
第二是異常運行的測試用例:
Arrange(設置):讓模擬器準備好,在輸入任何參數時,均返回空值。
Act(調用):調用函數 hello("王")。
Assert(檢查):因為模擬器返回的是空值,這是檢查返回結果是否為"這位王先生是誰?"
通過這樣的正面和反面的測試用例,我們可以徹底地檢查 hello(lastname) 方法是否工作正常。
質量控制:監控測試覆蓋率
著重需要提及的一點是,測試人員應當設法將單元測試的覆蓋率作為一個重要的監控指標,記錄并可視化。例如,Teamcity 或者 Jenkins 這樣的流程化工具,支持用 dotCover 來統計流程中單元測試的覆蓋率,并將結果以 TXT 報告或者 HTML 的方式顯示在任務頁面上。進一步也可以將覆蓋率、測試結果的數據,自動輸出到 SonarQube 這樣的代碼質量監控工具之中,以便隨時檢查出測試沒有通過或者測試覆蓋率不符合預期的情況。
高覆蓋率的單元測試是保障代碼質量的第一道也是最重要的關口。從分工上來說,測試人員可能不會參與單元測試的開發與維護,但是測試人員應當協助開發人員確保單元測試的部署和覆蓋率,這是確保后續一系列測試手段發揮作用的前提。
總結
簡單總結一下所學習的內容:
用模擬器來定義單元測試的邊界,模擬對外界函數/服務的調用;
依照三 A 原則,實現單元測試;
使用流程化工具,實時監控單元測試的覆蓋率。
原文轉自:www.uml.org.cn/Test/202001103.asp