工作小驛

          Ninja!

          BlogJava 首頁(yè) 新隨筆 聯(lián)系 聚合 管理
            103 Posts :: 0 Stories :: 36 Comments :: 0 Trackbacks
          筆記曾經(jīng)做過(guò)一次“程序員在項(xiàng)目開發(fā)中編寫單元測(cè)試的情況”的調(diào)查。調(diào)查結(jié)果:
          1.       嚴(yán)格的在項(xiàng)目中執(zhí)行TDD 幾乎沒有
          2.       為大部份業(yè)務(wù)方法編寫單元測(cè)試,并保證方法測(cè)試通過(guò)。 占16.6%
          3.       偶爾編寫單元測(cè)試,一般情況下不寫。 占58.3%
          4.       為了應(yīng)付項(xiàng)目檢查而寫單元測(cè)試,但并不保證方法是否測(cè)試通過(guò)。 占8.3%
          5.       從來(lái)不編寫單元測(cè)試。占16.6%
          因?yàn)檎{(diào)查具有一定的局限性或片面性,調(diào)查結(jié)果并不十分精確。也基本能夠反映國(guó)內(nèi)程序員編寫單元測(cè)試的狀況。很少有程序員能夠比較認(rèn)真的去編寫單元測(cè)試。那么到底又是什么原因呢?根據(jù)筆者參與的多個(gè)討論,主要有下面幾種原因使程序員不編寫單元測(cè)試。
          1.       為了完成編碼任務(wù),沒有足夠的時(shí)間編寫單元測(cè)試。編寫單元測(cè)試會(huì)導(dǎo)致不 能按時(shí)完成編碼任務(wù),推遲項(xiàng)目進(jìn)度。
          2.       單元測(cè)試的價(jià)值不高,完全是浪費(fèi)時(shí)間。
          3.       業(yè)務(wù)邏輯比較簡(jiǎn)單,不值得編寫單元測(cè)試。
          4.       不知道怎么編寫單元測(cè)試。
          5.       項(xiàng)目沒有要求,所以不編寫
          6.       在項(xiàng)目的前期還是盡量去編寫單元測(cè)試,但是越到項(xiàng)目的后期,就越失控。
           
          測(cè)試常常是程序員十分厭倦的一個(gè)項(xiàng)目活動(dòng)。測(cè)試能夠?yàn)槲覀儙?lái)什么?了解這些非常的重要。測(cè)試不可能保證一個(gè)程序是完全正確的,但是測(cè)試卻可以增強(qiáng)我們對(duì)程序的信心,測(cè)試可以讓我們相信程序做了我們期望它做的事情。測(cè)試能夠使我們盡量早的發(fā)現(xiàn)程序的bug.
          一個(gè)bug被隱藏的時(shí)間的越長(zhǎng),修復(fù)這個(gè)bug的代價(jià)就越大。在《快速軟件開發(fā)》一書中已引用了大量的研究數(shù)據(jù)。指出:最后才修改一個(gè)bug的代價(jià)是在bug產(chǎn)生時(shí)修改它的代價(jià)的10倍。
          在這里,我們需要討論的重點(diǎn)是單元測(cè)試。單元測(cè)試是一個(gè)方法層級(jí)上的測(cè)試,單元測(cè)試也是最細(xì)粒度的測(cè)試。用于測(cè)試一個(gè)類的每一個(gè)方法都已經(jīng)滿足了方法的功能要求。
          在現(xiàn)代軟件開發(fā)過(guò)程中,不管是xp還是rup都是十分重視單元測(cè)試。已經(jīng)把單元測(cè)試作為貫穿整個(gè)開發(fā)周期的一項(xiàng)重要的開發(fā)活動(dòng)。特別是在現(xiàn)代軟件開發(fā)過(guò)程中,有經(jīng)常集成和漸近提交的方法論。總結(jié)出了非常好的單元測(cè)試?yán)碚摵蛯?shí)踐:
            在編寫代碼之前先編寫單元測(cè)試,即測(cè)試先行
            單元測(cè)試是代碼的一部份,所有的代碼必須有單元測(cè)試,并使測(cè)試通過(guò)。(像在Spring這些優(yōu)秀的開源項(xiàng)目中在這方面做出了非常好的例子)
            在修改代碼之前先修改單元測(cè)試,并使它測(cè)試通過(guò)。
           
          在編寫代碼之前先編寫單元測(cè)試,會(huì)帶來(lái)非常多的好處:
            在編寫代碼之前先編寫單元測(cè)試,并不是編寫代碼之前需要一次性為所有的類都事先編寫單元測(cè)試。這需要有一個(gè)粒度的把握。最大的粒度應(yīng)該控制在一個(gè)類級(jí)別上,最合適的粒度是控制在一個(gè)方法級(jí)別上。先為某一個(gè)方法編寫測(cè)試代碼,然后再為該方法編寫實(shí)現(xiàn)代碼,直到其測(cè)試通過(guò)后再為另一個(gè)方法編寫測(cè)試代碼,如此循環(huán)。單元測(cè)試在這里已經(jīng)是一個(gè)契約規(guī)范了,它規(guī)范了方法應(yīng)該做什么,實(shí)現(xiàn)什么。測(cè)試代碼遠(yuǎn)遠(yuǎn)要比難以閱讀和不會(huì)及時(shí)更新的需求文檔更有價(jià)值。
           測(cè)試先行,鼓勵(lì)對(duì)需求的理解。如果沒有理解需求,你是不可能寫出測(cè)試代碼的,當(dāng)然你也不可能寫出好的實(shí)現(xiàn)代碼。
            測(cè)試代碼與其它文檔相比會(huì)更有價(jià)值。當(dāng)需求發(fā)生改變,實(shí)現(xiàn)代碼也相應(yīng)改變。而往往需求文檔,設(shè)計(jì)文檔得不到及時(shí)更新。測(cè)試代碼相比那些過(guò)期的文檔更具有價(jià)值。
            測(cè)試先行可以編寫出最大覆蓋率的測(cè)試代碼。如果在方法的實(shí)現(xiàn)代碼編寫完后再編寫測(cè)試代碼,這時(shí)開發(fā)人員總是編寫一個(gè)正確路徑的測(cè)試代碼。它已經(jīng)很難全面的去分析其它分支邏輯。
            如果我們采用測(cè)試先行,那么就自動(dòng)的完成了為所有的類都編寫測(cè)試這個(gè)實(shí)踐原則。為所有的類都編寫測(cè)試會(huì)將為你帶來(lái)非常多的好處:
            我們可以很好的使用自動(dòng)化測(cè)試來(lái)測(cè)試所有的類,特別是采用日構(gòu)建的系統(tǒng)。
             可以讓我們放心的為類或方法添加新的功能。我們可以很容易的修改測(cè)試代碼并驗(yàn)證修改后的代碼是有用的代碼。
             可以讓我們放心的對(duì)代碼進(jìn)行重構(gòu)和進(jìn)行設(shè)計(jì)優(yōu)化。重構(gòu)和設(shè)計(jì)優(yōu)化通常會(huì)關(guān)聯(lián)到多個(gè)類及多個(gè)方法。如果我們?yōu)樗械念惗季帉懥藴y(cè)試,我們就可以在重構(gòu)代碼后很輕松的進(jìn)行測(cè)試我們的修改是否正確。
             為所有的類編寫測(cè)試,可以讓我們很容易的修改bug。當(dāng)接到一個(gè)bug報(bào)告后,我們總是先修改測(cè)試代碼,然后修改實(shí)現(xiàn)代碼,使測(cè)試成功。這樣不會(huì)因?yàn)樾薷囊粋€(gè)問題而造成新問題的產(chǎn)生。
          良好的單元測(cè)試策略給我們?cè)鰪?qiáng)了對(duì)程序的信心,減少了bug的產(chǎn)生及bug的潛伏期。降低修改bug的代價(jià)。單元測(cè)試不會(huì)是項(xiàng)目開發(fā)周期的某一個(gè)生命周期,它貫穿于項(xiàng)目的整個(gè)生命周期,是一個(gè)非常重要的日常開發(fā)活動(dòng)。
           
          我們已經(jīng)知道了單元測(cè)試是多么重要的。為什么程序員仍然不編寫單元測(cè)試呢?為什么程序員總是有理由拒絕編寫單元測(cè)試呢?
          一、編寫單元測(cè)試,增加了工作負(fù)擔(dān),會(huì)延緩項(xiàng)目進(jìn)度?
             這是筆者在多次討論和調(diào)查中程序員拒絕編寫單元測(cè)試的最多理由。“為了完成編碼任務(wù),沒有足夠的時(shí)間編寫單元測(cè)試。編寫單元測(cè)試會(huì)導(dǎo)致不能按時(shí)完成編碼任務(wù),推遲項(xiàng)目進(jìn)度”。事實(shí)上真的是這樣的嗎?軟件有著其特殊的生命周期,軟件開發(fā)也具有特殊性。
          首先,我們需要提供給用戶的至少是一個(gè)能運(yùn)行的產(chǎn)品。絕對(duì)不能是一堆不能運(yùn)行的和充滿了異味的啞代碼。只有能夠運(yùn)行的,滿足客戶需求的代碼才是真正有用的代碼。這時(shí)代碼就變成產(chǎn)品了。
          很多程序員只關(guān)注編寫代碼的完成時(shí)間,而乎略了調(diào)試代碼,集成及修改和維護(hù)時(shí)間。
          如果沒有單元測(cè)試,開發(fā)活動(dòng)會(huì)是這樣的情景。
          以一個(gè)web應(yīng)用開發(fā)為例:業(yè)務(wù)代碼編寫完成->打包->發(fā)布到服務(wù)器->進(jìn)行功能測(cè)試->發(fā)現(xiàn)問題->修改代碼->再打包……如此循環(huán)。
          任何一個(gè)web程序員對(duì)于這種開發(fā)情景都不會(huì)感到陌生。往往不斷的打包,發(fā)布,功能測(cè)試的時(shí)間是代碼編寫的10倍以上。通過(guò)集成系統(tǒng)來(lái)發(fā)現(xiàn)程序的bug,我們往往很難一下子準(zhǔn)確的定位bug產(chǎn)生的地方。應(yīng)用服務(wù)器提供的錯(cuò)誤信息對(duì)于我們來(lái)說(shuō)是非常有限的。
          如果為每一個(gè)類都編寫單元測(cè)試并讓每一個(gè)方法測(cè)試通過(guò),又會(huì)是怎么樣的開發(fā)情景呢?
          編寫測(cè)試代碼->編寫業(yè)務(wù)代碼->運(yùn)行測(cè)試方法->修改代碼讓測(cè)試通過(guò)->所有的類都通過(guò)測(cè)試->打包->發(fā)布到服務(wù)器->進(jìn)行功能測(cè)試->發(fā)現(xiàn)bug->修改測(cè)試代碼->修改業(yè)務(wù)代碼->測(cè)試通過(guò)->再打包…如此循環(huán)。
          從上面的過(guò)程顯而易見,我們需要花費(fèi)更多的編碼時(shí)間。因?yàn)樾枰獮槊恳粋€(gè)業(yè)務(wù)類編寫測(cè)試代碼。但是它并不會(huì)導(dǎo)致我們總體需要花費(fèi)更多的時(shí)間。我們只是可以非常輕松的在ide環(huán)境中運(yùn)行測(cè)試方法。在代碼尚未打包發(fā)布之前我們就已經(jīng)確保了業(yè)務(wù)代碼的正確性。當(dāng)我們把所有通過(guò)測(cè)試的代碼集成到應(yīng)用服務(wù)器后,出現(xiàn)錯(cuò)誤的機(jī)率要少得多。當(dāng)集成測(cè)試后發(fā)現(xiàn)bug時(shí),我們也總是先修改測(cè)試類。保證在集成之前所有的類都經(jīng)過(guò)測(cè)試通過(guò)。這樣功能測(cè)試的時(shí)間就成數(shù)量級(jí)的減少,所以總的花費(fèi)時(shí)間要比沒有單元測(cè)試要少得多。
           另外,如果沒有單元測(cè)試,會(huì)經(jīng)常出現(xiàn)一些低級(jí)的錯(cuò)誤,如拼寫錯(cuò)誤,空指針異常等。就因?yàn)橐粋€(gè)小小的拼寫錯(cuò)誤而需要重新打包,發(fā)布一次。如果有單元測(cè)試,就可以避免這些低級(jí)的錯(cuò)誤。
          如果沒有單元測(cè)試,把代碼集成到應(yīng)用服務(wù)器后再發(fā)現(xiàn)錯(cuò)誤時(shí),我們往往更多的是憑借自己的經(jīng)驗(yàn)來(lái)判斷問題出在哪里。對(duì)于沒有經(jīng)驗(yàn)的程序員來(lái)說(shuō)只能是撞運(yùn)氣了。這就像是瞎子走路一樣,兩眼一把黑。如果每個(gè)類都有單元測(cè)試,就無(wú)需要這么痛苦了。
          這使得我回想起當(dāng)年做網(wǎng)絡(luò)系統(tǒng)。當(dāng)時(shí)的局域網(wǎng)絡(luò)都是采用環(huán)狀網(wǎng)絡(luò),還沒有現(xiàn)在的交換機(jī)來(lái)組星形網(wǎng)絡(luò)。環(huán)狀網(wǎng)絡(luò)的傳輸網(wǎng)絡(luò)采用同軸細(xì)纜線,網(wǎng)絡(luò)中的所有節(jié)點(diǎn)都在一條主干線上,網(wǎng)絡(luò)的兩端都會(huì)加上一個(gè)電阻來(lái)形成一個(gè)環(huán)。
          環(huán)狀網(wǎng)絡(luò)的最大的缺點(diǎn)就是當(dāng)任意一個(gè)節(jié)點(diǎn)有固障時(shí),整個(gè)網(wǎng)絡(luò)都不能連通。維護(hù)這種網(wǎng)絡(luò)是非常麻煩的。通常采用得比較多的方法就是“切香腸”法。把最后一個(gè)電阻取下來(lái),接到第二臺(tái)電腦的網(wǎng)絡(luò)節(jié)點(diǎn)的末端,檢查兩條線是否能連通。連通后再把電阻取下來(lái)到第三臺(tái)電腦的網(wǎng)絡(luò)節(jié)點(diǎn)的末端,連上第三臺(tái)電腦。這樣來(lái)依次檢查整個(gè)網(wǎng)絡(luò)的線路。
          后來(lái)發(fā)展了星形網(wǎng)絡(luò),也是現(xiàn)在局域網(wǎng)普遍采用的。有一臺(tái)交換機(jī),每一臺(tái)電腦連接到交換機(jī),任意一個(gè)節(jié)點(diǎn)網(wǎng)絡(luò)故障不會(huì)影響到其它節(jié)點(diǎn),檢查起來(lái)就非常方便了。沒有單元測(cè)試的代碼就像是環(huán)狀網(wǎng)絡(luò),而有測(cè)試的代碼就像星形網(wǎng)絡(luò)。
           其次,有可能我們第一次編寫的代碼是沒有問題的,但是到后來(lái)需求改變而修改了其中某些類的代碼,把它發(fā)布到了應(yīng)用服務(wù)器去測(cè)試,所要修改的內(nèi)容已經(jīng)測(cè)試通過(guò)了。但是因?yàn)槟承╊惖男薷膶?dǎo)致了其它類不能正常的工作。這種bug往往隱藏得非常深,因?yàn)橹灰挥|動(dòng)它,它就不會(huì)出現(xiàn)。可能會(huì)程序發(fā)布到生產(chǎn)環(huán)境之后才會(huì)被業(yè)務(wù)人員發(fā)現(xiàn)。如果每個(gè)類都有測(cè)試代碼,我們?cè)诖虬斑\(yùn)行所有測(cè)試代碼,就可以很容易的發(fā)現(xiàn)因?yàn)榇a修改帶來(lái)的連帶性錯(cuò)誤。
           其三,在離bug產(chǎn)生越近,修正bug就越容易;在bug產(chǎn)生越遠(yuǎn),修正bug的代價(jià)就越昂貴。假設(shè)我們?nèi)ゼ梢粋€(gè)星期(甚至更長(zhǎng)時(shí)間)前編寫的代碼,當(dāng)發(fā)現(xiàn)問題時(shí),我們已經(jīng)忘掉了很多重要的實(shí)現(xiàn)細(xì)節(jié),所以修改變得困難重重。
          編寫單元測(cè)試,并不會(huì)加重程序員的負(fù)擔(dān),反而提高了程序員對(duì)程序的信心,大大的減少了重復(fù)打包,發(fā)布,糾錯(cuò)誤的時(shí)間。這些花費(fèi)的時(shí)間遠(yuǎn)遠(yuǎn)要比編寫單元測(cè)試花費(fèi)的時(shí)間多幾個(gè)數(shù)量級(jí)。編寫單元測(cè)試,可以讓你更容易和更放心的去修改代碼,增加功能從而加快了項(xiàng)目的開發(fā)進(jìn)度。
          為什么我們總是要主觀的去認(rèn)為編寫單元測(cè)試會(huì)延緩項(xiàng)目進(jìn)度呢?與其痛苦的掙扎,還不如去嘗試一下好的實(shí)踐。
           
          二、業(yè)務(wù)邏輯簡(jiǎn)單,不值得編寫單元測(cè)試
            程序員是聰明的,程序員也總是自認(rèn)為是聰明的。認(rèn)為一些業(yè)務(wù)邏輯比較簡(jiǎn)單的類不必要編寫單元測(cè)試。我們必須承認(rèn),需求不斷變化,我們也必須要有勇氣去接受需求變化。編寫單元測(cè)試的另一個(gè)目的就是擁抱變化,而不是拒絕變化。編寫單元測(cè)試就是提高了我們對(duì)程序的信心。在敏捷軟件開發(fā)中,代碼為集體所有,項(xiàng)目組的任何一個(gè)人都可以去修改任何一個(gè)代碼文件。每當(dāng)我要去修改一個(gè)別人編寫的代碼時(shí),我總是多么的希望有程序的單元測(cè)試代碼,而往往都讓我非常的失望。一般我都得花費(fèi)很大的力氣去猜想原作者的原始意圖。也許你會(huì)說(shuō):“你可以去看需求文檔啊!你不會(huì)去看注釋嗎?”。但實(shí)際情況是,當(dāng)需求文檔完成了它的使命后,開發(fā)人員就把它扔到了一邊了,文檔總是過(guò)期的。沒有幾個(gè)項(xiàng)目組能夠使得需求,設(shè)計(jì)這些文檔與最新實(shí)現(xiàn)代碼保持一致。所以去看一個(gè)過(guò)期的文檔是沒有價(jià)值的。注釋也同樣,保持最新仍然是一個(gè)最大的問題,并且注釋能夠提供的信息是非常有限的。所以我最需要的就是看測(cè)試代碼了。測(cè)試代碼最能反映出方法最新的功能契約。由代碼的編寫者去寫的單元測(cè)試要比由其它人去編寫的單元測(cè)試要更完善,更準(zhǔn)確。
          很多問題恰恰就出在一些我們認(rèn)為簡(jiǎn)單的代碼中。除非是像一個(gè)JavaBean的getter和setter方法,因?yàn)檫@些方法可以通過(guò)IDE自動(dòng)代碼生成,沒有必要為它編寫測(cè)試。
          在項(xiàng)目開發(fā)中,我們需要經(jīng)常通過(guò)重構(gòu)來(lái)優(yōu)化代碼及改進(jìn)我們的設(shè)計(jì),當(dāng)我們對(duì)代碼進(jìn)行重構(gòu)之后,怎么能夠保證代碼仍然是正確的?那就是運(yùn)行所有被修改的代碼的測(cè)試。如果測(cè)試通過(guò),則說(shuō)明我們的重構(gòu)是正確。
          我們不能回避代碼的維護(hù)問題。代碼維護(hù)包括修正bug和增加功能。維護(hù)工作可能會(huì)距離代碼編寫完成有很長(zhǎng)一段時(shí)間。當(dāng)需要修改一個(gè)bug而修改了代碼,或增加一個(gè)新的功能而修改了代碼時(shí),又怎么能夠保證修改后的代碼仍然是正確的和沒有隱患的呢?也許你會(huì)說(shuō),發(fā)布到應(yīng)該服務(wù)器去測(cè)試就知道了。筆者曾經(jīng)發(fā)生過(guò)因?yàn)榫S護(hù)而導(dǎo)致了更嚴(yán)重問題發(fā)生的情況。一個(gè)系統(tǒng)在生產(chǎn)環(huán)境正常運(yùn)行很長(zhǎng)時(shí)間了。某一天,業(yè)務(wù)人員要求修改某一個(gè)功能,筆者按業(yè)務(wù)的要求實(shí)現(xiàn)了要修改的功能,業(yè)務(wù)也測(cè)試了修改后的功能,然后發(fā)布到了生產(chǎn)環(huán)境。程序下發(fā)兩個(gè)星期后,報(bào)了一個(gè)非常嚴(yán)重的生產(chǎn)問題上來(lái),以前能夠正常運(yùn)行的功能突然有問題了,導(dǎo)致了大量的生產(chǎn)數(shù)據(jù)錯(cuò)誤。這個(gè)問題是非常致命的,只能暫時(shí)停用系統(tǒng)。
          最后我查明原因是,出錯(cuò)的模塊與上次修改的代碼有關(guān)聯(lián),上次修改時(shí)沒有同時(shí)去修改現(xiàn)在出錯(cuò)的模塊。要是我能夠在修改代碼后,運(yùn)行所有的測(cè)試類,測(cè)試就肯定會(huì)報(bào)告不通過(guò)。也就不會(huì)把隱藏有這么嚴(yán)重錯(cuò)誤的程序下發(fā)到生產(chǎn)環(huán)境去。
            我們看看沒有寫單元測(cè)試是怎么進(jìn)行集成的。如果某些結(jié)果與我們所期望的不一致時(shí),我們可能會(huì)在程序中加上許多print語(yǔ)句,然后通過(guò)控制臺(tái)來(lái)監(jiān)視程序的運(yùn)行過(guò)程。采用print語(yǔ)句并不能夠保證我們的程序的正確性。最好的情況是,它只能保證一條正確的路徑,不能保證其它的分支。另外當(dāng)太多的print語(yǔ)句的信息在控制臺(tái)上,也會(huì)讓我們看不到想看到的信息,控制臺(tái)的信息是有限的。在開發(fā)測(cè)試時(shí),把調(diào)試信息打印在控制臺(tái)還可以接受,但是在生產(chǎn)環(huán)境,如果還有調(diào)試信息出現(xiàn)在控制臺(tái),那是絕對(duì)不可以接受的。我們經(jīng)常會(huì)忘記把調(diào)試的print語(yǔ)句及時(shí)的刪除掉,從而影響程序的性能。最關(guān)鍵的是,print語(yǔ)句不能保證程序的正確性,也不能為你節(jié)省開發(fā)的時(shí)間。只會(huì)給你帶來(lái)負(fù)面的影響。
           
          三、不知道怎么編寫單元測(cè)試
            如果你相信單元測(cè)試的價(jià)值,那么去學(xué)習(xí)如何編寫單元測(cè)試最終會(huì)讓你獲益的。
          以java開發(fā)為例,junit這樣的單元測(cè)試組件是非常易于學(xué)習(xí)和使用的。其它語(yǔ)言也有類似的單元測(cè)試組件。要相信這將是簡(jiǎn)單和能為你帶來(lái)價(jià)值的。但是筆者見過(guò)許多程序員編寫的單元測(cè)試完全沒有起到它應(yīng)有的作用。這也與不知道怎么編寫單元測(cè)試有關(guān)。所以我們應(yīng)該掌握一些編寫單元測(cè)試的基本原則:
            應(yīng)該為什么編寫測(cè)試:雖然我們說(shuō)為所有的代類都編寫單元測(cè)試,但是測(cè)試JavaBean的setter或getter方法無(wú)異于是自尋煩惱。編寫這樣的測(cè)試完全是浪費(fèi)時(shí)間。而且還增加了維護(hù)的困難。
            學(xué)會(huì)使用斷言:斷言就是讓我們?yōu)榉椒ㄔO(shè)置一個(gè)期望值。當(dāng)方法執(zhí)行結(jié)果與期望值不一致時(shí),測(cè)試組件就會(huì)報(bào)告測(cè)試不通過(guò)。我見過(guò)一些項(xiàng)目的單元測(cè)試不是使用斷言,而是自己編寫一個(gè)打印(println)工具類,可以詳細(xì)的在控制臺(tái)中打印出類的詳細(xì)成員信息,及集合的詳細(xì)信息。在單元測(cè)試中使用這個(gè)打印工具類來(lái)打印輸出結(jié)果。這看起來(lái)好像非常不錯(cuò)。但是不應(yīng)該使用這種方式來(lái)編寫單元測(cè)試
          使用打印工具類,需要程序員自已從控制臺(tái)去觀察程序的執(zhí)行結(jié)果。當(dāng)輸出信息非常多時(shí),控制臺(tái)信息是無(wú)法向上翻屏的。所以不能夠給我們提供更多的信息。所以這種方法也不能用于自動(dòng)化測(cè)試。
          使用打印工具類,造成了一種假像,測(cè)試報(bào)告我們的測(cè)試總是成功的!如果使用斷言,當(dāng)方法的執(zhí)行結(jié)果與我們?cè)O(shè)置的期望值不一致時(shí),則會(huì)詳細(xì)的報(bào)告測(cè)試失敗的情況。
          使用打印工具來(lái)代替斷言,造成測(cè)試的不充分,只會(huì)寫出一個(gè)低測(cè)試覆蓋率的測(cè)試。我們需要一個(gè)充分的測(cè)試。
            最大化測(cè)試覆蓋率:我們除了測(cè)試一個(gè)正確的路徑外,我們還需要測(cè)試方法的每一個(gè)分支邏輯。需要編寫盡可能多的測(cè)試程序邏輯的測(cè)試。寫一個(gè)充分的測(cè)試。
            避免重復(fù)的測(cè)試代碼:測(cè)試類也是非常重要的,與應(yīng)用代碼一樣。測(cè)試類包含的重復(fù)代碼越多,測(cè)試類自身出現(xiàn)的錯(cuò)誤也會(huì)越多。而我們需要做的編碼工作也就越多。
            不要依賴于測(cè)試方法的執(zhí)行順序:使用Junit來(lái)進(jìn)行單元測(cè)試,它不能保證測(cè)試方法按照我們的意圖的順序來(lái)執(zhí)行。當(dāng)一個(gè)測(cè)試類有多個(gè)測(cè)試方法時(shí),我們不能讓一個(gè)測(cè)試方法必須在某一個(gè)測(cè)試之后執(zhí)行才能成功。Junit不能為我們做這樣的保證,我們不能依賴于測(cè)試方法的執(zhí)行順序。
            針對(duì)接口測(cè)試:我們有“針對(duì)接口編程”的oo設(shè)計(jì)原則。同樣對(duì)于測(cè)試,我們也需要針對(duì)接口測(cè)試。也就是說(shuō)在編寫單元測(cè)試時(shí),測(cè)試對(duì)象總是使用接口,而不是使用具體類。
           
          四、項(xiàng)目沒有要求,所以不編寫
            的確在很多項(xiàng)目中,團(tuán)隊(duì)并沒有要求程序員為每一個(gè)類編寫單元測(cè)試。反而會(huì)要求我們編寫很多復(fù)雜的文檔。作為程序員我們需要明白:程序員是編寫單元測(cè)試的最大受益者。
          這不是項(xiàng)目經(jīng)理的事,也不是QA的事,而是程序員自身的事。因?yàn)閱卧獪y(cè)試是程序代碼的一部份。單元測(cè)試是最好的,最有價(jià)值的文檔,它應(yīng)該與代碼一起交付給客戶。
             單元測(cè)試代碼不是官僚,死板的文檔。它是生動(dòng)的,是程序員最有用的文檔。單元測(cè)試能夠提高程序員對(duì)程序的信心,能夠使用養(yǎng)成良好的設(shè)計(jì)原則:“針對(duì)接口編程,而不是具體類”。因?yàn)橐M(jìn)行單元測(cè)試,所以我們需要讓類獨(dú)立于其依賴對(duì)象(使用Mock或stub)進(jìn)行測(cè)試。這就迫使我們養(yǎng)成了良好的編程習(xí)慣。
            單元測(cè)試是改進(jìn)我們?cè)O(shè)計(jì)的保證。做為一個(gè)優(yōu)秀的程序員,是會(huì)經(jīng)常優(yōu)化代碼和設(shè)計(jì),所以經(jīng)常的進(jìn)行重構(gòu)。一個(gè)優(yōu)秀的程序員絕對(duì)不能容忍異味代碼。而單元測(cè)試就是我們進(jìn)行重構(gòu)的信心保證。
            單元測(cè)試是一個(gè)日常開發(fā)活動(dòng),它貫穿于項(xiàng)目的整個(gè)生命周期。做一個(gè)負(fù)責(zé)任的程序員總是為自己的代碼的質(zhì)量負(fù)責(zé)的。是否經(jīng)常改進(jìn)你的設(shè)計(jì),是否讓別人很輕松的使用和修改你的代碼。
          為所有類編寫單元測(cè)試應(yīng)該是一個(gè)程序員應(yīng)具有的素質(zhì)。項(xiàng)目有沒有要求,不應(yīng)當(dāng)成為不編寫單元測(cè)試的理由。
          五、為什么越在項(xiàng)目的后期,單元測(cè)試就越難以進(jìn)行下去?
            在很多項(xiàng)目的初期,項(xiàng)目中的大部分程序員都能夠自覺的去編寫單元測(cè)試。隨著項(xiàng)目的進(jìn)展,任務(wù)的加重,離交付時(shí)間越來(lái)越近,不能按時(shí)完成項(xiàng)目的風(fēng)險(xiǎn)越來(lái)越大,單元測(cè)試就往往成為犧牲品了。項(xiàng)目經(jīng)理因?yàn)檫M(jìn)度的壓力也不重視了,程序員也因?yàn)榫幋a的壓力和無(wú)人看官而不再為代碼編寫單元測(cè)試了。
            筆者所有親歷的項(xiàng)目都著像這么糟糕的情況發(fā)生。越是在項(xiàng)目的后期,能堅(jiān)持編寫單元測(cè)試的程序就在整個(gè)項(xiàng)目組中不會(huì)超過(guò)15%。為了追趕進(jìn)度,絕大多數(shù)程序員都把沒有經(jīng)過(guò)任何測(cè)試的代碼提交到版本服務(wù)器,項(xiàng)目經(jīng)理也不再追問,照單全收。這樣做的結(jié)果就是在后期,集成花費(fèi)的時(shí)間越來(lái)越多,幾個(gè)技術(shù)骨干人員只得日夜加班進(jìn)行系統(tǒng)集成。好不容易集成完了之后,下發(fā)給測(cè)試人員測(cè)試時(shí),bug的報(bào)告成數(shù)量級(jí)的增長(zhǎng)。程序員就日以繼夜的修改bug.還有非常多的bug被隱藏更深,一直潛伏到生產(chǎn)環(huán)境去。項(xiàng)目中,越來(lái)越多的人對(duì)項(xiàng)目失去信心,每一個(gè)人都在抱怨,數(shù)不清的bug,修正了一個(gè)bug,更多的bug報(bào)告上來(lái)。
          每天都在修改bug,但是每天又會(huì)報(bào)告上更多的bug。于是開始有人想逃離了,有人請(qǐng)假,也有人離職。當(dāng)項(xiàng)目總算結(jié)束時(shí),每一個(gè)的內(nèi)心都清楚,項(xiàng)目太爛了,還有很多的錯(cuò)誤還沒被測(cè)試出來(lái),趕快逃離這個(gè)項(xiàng)目組吧!一半的人病倒了,或?qū)?xiàng)目的維護(hù)失去了信心。
          為什么會(huì)這樣?有沒有宣導(dǎo)測(cè)試的重要性呢?
          在項(xiàng)目初期應(yīng)該進(jìn)行宣導(dǎo)單元測(cè)試的重要性。
          有沒有做過(guò)相關(guān)的培訓(xùn)工作?在項(xiàng)目啟動(dòng)時(shí),需要進(jìn)行一些相關(guān)的培訓(xùn),教授團(tuán)隊(duì)成員最基本的編寫單元測(cè)試的技巧。
          有沒有做過(guò)相應(yīng)的風(fēng)險(xiǎn)防范?越是工作資歷越深的程序員,就越會(huì)拒絕編寫單元測(cè)試,他們總是有太多的理由來(lái)拒絕編寫單元測(cè)試。這些頑固的老程序員往往負(fù)責(zé)著核心的代碼的編寫。我們知道20-80定律吧。80%的錯(cuò)誤是發(fā)生在20%的代碼之中的,往往最嚴(yán)重的錯(cuò)誤就發(fā)生在那些老鳥們的代碼中。有沒有在事先就做好風(fēng)險(xiǎn)防范,說(shuō)服他們編寫單元測(cè)試。
          有沒有做好測(cè)試相關(guān)的基礎(chǔ)工作。有沒有針對(duì)不同類型的程序編寫測(cè)試基類,讓編寫測(cè)試變成一項(xiàng)非常簡(jiǎn)單的工作。有一些代碼是依賴于特定的環(huán)境,如EJB訪問,JNDI訪問,web應(yīng)用程序依賴Servlet API等。測(cè)試這些程序是非常困難的。應(yīng)該編寫一些測(cè)試基類和測(cè)試stub,讓這些程序可以脫離于特定環(huán)境就像普通程序一樣進(jìn)行單元測(cè)試。讓普通程序員輕松的編寫測(cè)試代碼進(jìn)行程序測(cè)試。
          可以實(shí)行日構(gòu)建和測(cè)試覆蓋率檢查,沒有通過(guò)測(cè)試的代碼絕不允許放到版本服務(wù)器。檢查測(cè)試的覆蓋率。
           
            在現(xiàn)代軟件開發(fā)過(guò)程中,測(cè)試不再作為一個(gè)獨(dú)立的生命周期。單元測(cè)試成為與編寫代碼同步進(jìn)行的開發(fā)活動(dòng)。單元測(cè)試能夠提高程序員對(duì)程序的信心,保證程序的質(zhì)量,加快軟件開發(fā)速度,使程序易于維護(hù)。不管測(cè)試先行還是測(cè)試后行,沒有單元測(cè)試那是絕對(duì)不行的。
            弱者為失敗找理由,強(qiáng)者為成功找方法!今天你單元測(cè)試了嗎?
          posted on 2007-07-11 13:27 王君 閱讀(308) 評(píng)論(0)  編輯  收藏 所屬分類: J2SE
          主站蜘蛛池模板: 平顺县| 华池县| 谢通门县| 申扎县| 满洲里市| 观塘区| 韶山市| 拉萨市| 永登县| 松桃| 蒙自县| 岳池县| 卢氏县| 桃园市| 昭平县| 双鸭山市| 太谷县| 彭州市| 漳浦县| 铜川市| 微山县| 临洮县| 衡水市| 滕州市| 建始县| 泽普县| 定远县| 南澳县| 青海省| 新疆| 西峡县| 聂荣县| 佛冈县| 陆河县| 安塞县| 新安县| 韶山市| 崇礼县| 文山县| 乌拉特前旗| 湖北省|