TDD學習筆記【三】---是否需針對非public方法進行測試?

前言

在Visual Studio 2012 中,針對Unit Test 的部分,有一個重要的變更:html

本來針對「測試對象非public 的部分」,開發人員可經過Visual Studio 2010 自動產生的accessor ​​來進行測試。但在Visual Studio 2012 中,將此功能移除了。框架

Accessor ​​其背後的原理,是將對象經過很「髒」的反射方式,把對象內全部的東西public 出來。而且Visual Studio 在更新對象後,進行與設計測試時,會幫你作同步產生accessor ​​的動做。(實際的原理我沒有深刻研究,也不太肯定。但基本上的概念就是如此)ide

這個本來被認爲很方便、實用的功能(包括我好久以前寫測試時,也是這麼認爲),很抱歉,在Visual Studio 2012 後已經被移除了。單元測試

接下來本篇文章將會說明,單元測試是否應該對測試對象非public 的部份,進行單元測試。學習

單元測試的意義

一言以蔽之:「單元測試就是用來模擬外部如何使用測試目標對象,驗證其行爲是否符合預期」。測試

所以,有個重點是:外部如何使用測試目標對象。ui

讓咱們回到Object-Oriented 的封裝原則,封裝的用意在於:spa

  1. 隔離出對象的內部與外部。也就是定義「對象的邊界」,以及定義「外部可視部分」。
  2. 將外部使用端,不須要了解對象的內部信息,封裝起來。也就是「封裝細節」。
  3. 將對象內部的變化,封裝起來。也就是「封裝變化」。

有了對單元測試與封裝的認知後,接下來講明,爲何單元測試只須要針對測試目標對象public 的行爲,進行測試便可。設計

 

只測試Public 行爲?

根據單元測試的意義,以及封裝的用意,表明着「外部使用者本來就不須要了解,也根本不瞭解,測試目標對象非public的行爲」。單元測試既然是模擬外部使用端的動做,那固然只針對測試目標對象public的行爲進行模擬與驗證。code

但一些朋友確定有些疑惑,那非public 的method 該怎麼辦?不測嗎?那code coverage 怎麼提高?要怎麼知道這些非public 的行爲有沒如同預期般運做呢?

有這些疑問是正常的,由於我一開始也是有如出一轍的疑問,但開始接觸TDD 以後,反而更加了解了Unit Test 的本質。

所謂的非public 的行爲,其存在的緣由,必定是由於某一些public 的行爲會用到這些private 或protected 的method,若是對象中存在着跟public method 無關的private 或protected method,那在設計上就是個問題,這些非public 的method 根本就沒有存在的意義。由於外部使用測試目標對象時,徹底不會用到這些method,就像聲明瞭變量卻不去使用它同樣,沒有意義。

而當私有或受保護的方法與public 方法有關時,那針對公有方法的單元測試便會涵蓋到這些私有或受保護的方法,它們就是公有方法的一部分,對外部使用者來講,根本分辨不出來什麼是私或受保護的,由於只關注在對象外部可視行爲上。

因此,在實做單元測試上,假若測試對象一個public method 中,涵蓋了一個private method,而private method 中與外部對象或服務相依,那麼在測這個public method 時,要連private method 中相依的interface ,都要撰寫stub object 來模擬才行,這也是爲何單元測試被稱爲白箱測試的緣由。但仍是得強調一次,外部使用者是沒法分清楚哪一部分是public method 內容,哪一部分是非public method。

總結上面的說法,非public method 的測試涵蓋率​​,是依據public method 調用時的input 來決定。

有沒有可能,當public method 該測的都測了,甚至public method 主體內容涵蓋率都100% 了,非public 的部分涵蓋率卻很低?固然有可能,但這要釐清一下,沒有被涵蓋到的部份,是屬於什麼樣的代碼。

若是在非public method 中,沒被測試覆蓋的部份,是提醒、斷言之類的代碼,那麼是屬於正常的狀況。由於可能在調用非public method 以前,就已經先提醒了,致使非public method 中的提醒永遠不會發生。但,由於系統的健壯性考量,該斷言、提醒、驗證的部份,仍是不能少。由於不會知道將來其餘方法調用前,有沒作好提醒的部份。

那麼,在private或protected method中,非提醒、斷言的代碼,卻又沒被涵蓋到部分呢?這是個警訊,表明着這些代碼多是over design,或是根本沒有用處。由於這個對象全部對外的行爲,全部的可能性,都模擬過一次了,卻都不會用到這些沒被涵蓋到的代碼,這不就表明「這些代碼目前用不到」嗎?YAGNI原則就是在說這件事:「You ain't gonna need it !

只要public 的行爲如同預期,即便private 或protected 的method 是hard-code,是很沒彈性,是很愚蠢的寫法,對外部使用來講,根本就不在意,由於無感。

這也是TDD 所提倡的精神,若是全部使用行爲都符合預期,就表明功能完成了。並且依據測試來撰寫的生產代碼,幾乎不會出現測試涵蓋不到的code,由於生產代碼 是爲了知足測試而撰寫的。不須要存在用不到的生產代碼,所以,也能夠避免over design 的狀況。

 

針對非public 行爲測試又如何?

上面那一段的說明,確定仍是沒法說服全部人,「爲何要把已經存在的功能移除?」

不用accessor ​​的人大可不用,但已經在用,或真的得用的人,仍是但願能夠在VS2012 中繼續使用。

回到封裝的用意上,「封裝變化」一直是對象導向設計中很重要的設計原則。那些針對private與protected進行單元測試的朋友,有沒有過「由於一些需求更新,致使單元測試程式就須要跟着從新調整、設計或修改,並且頻率與範圍致使測試的維護成本增長很多」的經驗。若是有,這就是爲何不但願developer去針對非public method寫單元測試的緣由。

着重在非public method 的單元測試,說穿了只是寫給developer 爽而已。由於要封裝變化,纔會把這些內容變成private 或protected,以指望變化時對外部使用者來講,呈現無感,也就是下降耦合,也就是最小知識原則。

如今單元測試卻經過某些機制,來存取這些封裝起來的行爲,不是自討苦吃嗎?本來就知道,這些東西極可能會一直變化,卻又去存取它,測試它,致使單元測試所以維護與更新頻率增長,這不就違背了封裝的用意?

對使用對象的角度來講,使用端根本不關心這些變化,卻由於單元測試用髒方法硬幹到這些不公開的行爲,致使測試成本增長,進而致使一些不明就裏的developer喊出「測試很花成本,時間增長不少,很難維護」。我只想說:「這不是南北拳的問題,是你的問題。」

 

結論

說真的,剛知道Visual Studio 2012 把accessor ​​功能拿掉,我也一整個至關吃驚,以爲要強迫developer 用TDD 方式開發,也不用作到這麼絕吧。

但將對象導向的原則、TDD 的精神、單元測試的基本意義結合起來後,有了上述的思考歷程,就以爲只測試public method,不建議測試private 與protected method,是一件正確且重要的事。

因此將這樣的思考與推論過程,分享給各位朋友參考,不必定徹底符合Visual Studio 2012 移除accessor ​​的緣由,這只是我本身的理解與想法而已,但從我一開始接觸單元測試,怎麼測private method 就一直困擾我好久,雖然說腦殼中有點輪廓,卻一直沒法明確釐清。

 

補充

這邊有一篇寫的很不錯的文章,講的至關全面,包括概念、現實上的考量、過程當中的考量,都寫得很清楚。請參考:Testing Private Methods with JUnit and SuiteRunner

2012/11/09 補充:VS2012 將accessor ​​與自動產生單元測試代碼的功能移除的另外一個緣由是:由於本來accessor ​​的產生機制,與MS Test 的耦合度過高了。(在Visual Studio 2012 後,指望能夠很彈性的與其餘Unit Test Provider 結合。)

 

讀者的疑問

針對讀者的一些疑問,我就補充在文末,你們若還有什麼想了解或發問的,歡迎留言。

 

Q1. 文章上只提到了 public, protected, private,那麼 internal 呢?

答: 這是一個很棒的問題,由於我文中的確沒提到internal 的部份。

首先internal 的定義/用意,是指在同一組件內才能看的到,也就是我這對象但願在我這組件裏是公開的,但組件外的人看不到也用不到。(這樣設計能夠有效控制相依範圍)

而單元測試如前面所說,是針對「對象」的互動,來進行模擬使用。那聲明成internal 的對象,到底要不要測試,固然要,由於的確有其餘對象會使用它,咱們就要思考:「怎麼使用它」。

但通常測試項目的角度來看,是參考生產代碼 的library,因此對測試項目的角度,是看不到生產代碼 裏面聲明成internal 的對象的,​​但我又想去測試生產代碼 中internal 的對象,該怎麼辦?

在.NET中至關簡單,只須要經過:InternalsVisibleToAttribute這個屬性設定便可。將生產代碼 library指定給test project可見,就能夠解決這個問題。

 

Q2. 若沒針對private測試,當發生問題時,我怎麼知道是哪一段code錯了?或是它沒被涵蓋到,就表明沒有受到測試保護。

答:這個問題,就是慢慢消化這篇文章,並實際動手作以後,就會漸漸的撥開雲霧見青天了。

當只用測試的思惟來看,那不去「針對」private method 測試,是一件很奇怪的事,由於它活着,但沒有測試能夠立刻知道它對不對。

這也是跨入TDD 的其中一道門檻。回過頭來看前幾篇的宗旨,系統的存在,到底爲了什麼?

爲了能夠正確的知足使用者的需求,外部使用的需求。既然用了對象導向來設計,既然把這些東西封裝起來,外部的使用者就根本看不到、用不到,也不應看到用到。而咱們封裝的意義就在於封裝變化。這時候用其餘方式,硬幹進去對象中去測試private method,也只是增長本身將來的負擔,由於它確定會一直變。

本來private 的改變,能夠幾乎不影響任何部分,除了對象自己內部。因此它能夠放變化。

如今外面看的到這個方法,你就不能輕易改變,一旦要改,可能會影響許多測試程式,反卻是生產代碼 不會有太多影響。但測試程式若是所以要維護或是要重寫,這就都是根本不必的東西。

最後,如​​果你用TDD的方式開發,就根本更不會碰到這個問題。

由於,你只針對public 行爲,來進行預期,永遠切入點都是撰寫public 的內容。大概只有重構的時候,會出現private 跟protected。而這個時候,被放到private 的方法,固然是你本來放在public 方法內的內容。

那若是本來public 方法code coverage 是100%,那也不會由於你搬到private,code coverage 就變成50%。若是出現了由於重構,就沒有涵蓋到的範圍,那就是over design 的bad smell,是個徵兆。

這邊就是須要搭配TDD 與Refactoring 的手法,才能一體成型,享受其美妙之處而無後顧之憂。再強調一次,private / protected的方法內容,在TDD裏面,基本上都是由於refactoring的extract method所產生的,都是一些本來放在public / internal的function內容。而不會是直接動手去寫private function,除非你是top-down的先訂出程序的框架。但最終,private function仍屬於public function內容的一部分。

因此要特別知足的應該是:您是否有針對外部可見的行爲,進行了全部具表明性的情境來作測試。若是真的涵蓋了全部,包括exception handling,那麼這個對象內,沒被涵蓋到的部份,基本上均可以刪除了。毫不會對外部使用形成任何影響。

 

備註:這個系列是我畢業後時隔一年從新開始進入開發行業後對大拿們的博文摘要整理進行學習對自個人各個欠缺的方面進行充電記錄博客的過程,非原創,特此感謝91 等前輩

相關文章
相關標籤/搜索