單元測試① | 單元測試入門——優(yōu)秀基因

1. 單元測試入門——優(yōu)秀基因

單元測試最初興起于敏捷社區(qū)盒蟆。1997年趾娃,設(shè)計(jì)模式四巨頭之一Erich Gamma和極限編程發(fā)明人Kent Beck共同開發(fā)了JUnit庶弃,而JUnit框架在此之后又引領(lǐng)了xUnit家族的發(fā)展赞草,深刻的影響著單元測試在各種編程語言中的普及千劈。當(dāng)前,單元測試也成了敏捷開發(fā)流行以來的現(xiàn)代軟件開發(fā)中必不可少的工具之一辛孵。同時(shí)丛肮,越來越多的互聯(lián)網(wǎng)行業(yè)推崇自動(dòng)化測試的概念,作為自動(dòng)化測試的重要組成部分魄缚,單元測試是一種經(jīng)濟(jì)合理的回歸測試手段宝与,在當(dāng)前敏捷開發(fā)的迭代(Sprint)中非常流行和需要。

然而有些時(shí)候冶匹,這些單元測試并沒有有效的改善生產(chǎn)力伴鳖,甚至單元測試有時(shí)候變成一種負(fù)擔(dān)。人們盲目的追求測試覆蓋率徙硅,往往卻忽視了測試代碼本身的質(zhì)量,各種無效的單元測試反而帶來了沉重的維護(hù)負(fù)擔(dān)搞疗。

本篇講義將會(huì)集中的從單元測試的入門嗓蘑、優(yōu)秀單元測試的編寫以及單元測試的實(shí)踐等三個(gè)方面展開探討。

文中的相關(guān)約定:

文中的示例代碼塊均使用Java語言匿乃。
文中的粗體部分表示重點(diǎn)內(nèi)容和重點(diǎn)提示桩皿。
文中的引用框部分,一般是定義或者來源于其它地方幢炸。
文中標(biāo)題的【探討】泄隔,表示此部分講師與學(xué)員共同探討并由講師引導(dǎo),得到方案宛徊。
文中的代碼變量和說明用方框圈起來的佛嬉,是相關(guān)代碼的變量、方法闸天、異常等暖呕。

1.1 單元測試的價(jià)值

  • 什么是單元測試

    在維基百科中,單元測試被定義為一段代碼調(diào)用另一段代碼苞氮,隨后檢驗(yàn)一些假設(shè)的正確性湾揽。

    以上是對單元測試的傳統(tǒng)定義,盡管從技術(shù)上說是正確的,但是它很難使我們成為更加優(yōu)秀的程序員库物。這些定義在諸多討論單元測試的書籍和網(wǎng)站上霸旗,我們總能看到,可能你已經(jīng)厭倦戚揭,覺得是老生常談诱告。不過不必?fù)?dān)心,正是從這個(gè)我們熟悉的毫目,共同的出發(fā)點(diǎn)蔬啡,我們引申出單元測試的概念。

    或許很多人將軟件測試行為與單元測試的概念混淆為一談镀虐。在正式開始考慮單元測試的定義之前箱蟆,請先思考下面的問題,回顧以前遇到的或者所寫的測試:

    • 兩周或者兩個(gè)月刮便、甚至半年空猜、一年、兩年前寫的單元測試恨旱,現(xiàn)在還可以運(yùn)行并得到結(jié)果么辈毯?
    • 兩個(gè)月前寫的單元測試,任何一個(gè)團(tuán)隊(duì)成員都可以運(yùn)行并且得到結(jié)果么搜贤?
    • 是否可以在數(shù)分鐘以內(nèi)跑完所有的單元測試呢谆沃?
    • 可以通過單擊一個(gè)按鈕就能運(yùn)行所寫的單元測試么?
    • 能否在數(shù)分鐘內(nèi)寫一個(gè)基本的單元測試呢仪芒?

    當(dāng)我們能夠?qū)ι鲜龅膯栴}唁影,全部回答“是”的時(shí)候,我們便可以定義單元測試的概念了掂名。優(yōu)秀的測試應(yīng)該以其本來的据沈、非手工的形式輕松執(zhí)行。同時(shí)饺蔑,這樣的測試應(yīng)該是任何人都可以使用锌介,任何人都可以運(yùn)行的。在這個(gè)前提下猾警,測試的運(yùn)行應(yīng)該能夠足夠快孔祸,運(yùn)行起來不費(fèi)力、不費(fèi)事肿嘲、不費(fèi)時(shí)融击,并且即便寫新的測試,也應(yīng)該能夠順利雳窟、不耗時(shí)的完成尊浪。如上便是我們需要的單元測試兆龙。

    涵蓋上面描述的要求的情況下习瑰,我們可以提出比較徹底的單元測試的定義:

    單元測試(Unit Test),是一段自動(dòng)化的代碼,用來調(diào)動(dòng)被測試的方法琴儿,而后驗(yàn)證基于該方法或類的邏輯行為的一些假設(shè)董虱。單元測試幾乎總是用單元測試框架來寫的捂敌。它寫起來很順手轰胁,運(yùn)行起來不費(fèi)時(shí)。它是全自動(dòng)的掉盅、可信賴的也拜、可讀性強(qiáng)的和可維護(hù)的。

    接下來我們首先討論單元測試框架的概念:

    框架是一個(gè)應(yīng)用程序的半成品趾痘÷框架提供了一個(gè)可復(fù)用的公共結(jié)構(gòu),程序員可以在多個(gè)應(yīng)用程序之間進(jìn)行共享該結(jié)構(gòu)永票,并且可以加以擴(kuò)展以便滿足它們的特定的要求卵贱。

    單元測試檢查一個(gè)獨(dú)立工作單元的行為,在Java程序中侣集,一個(gè)獨(dú)立工作單元經(jīng)常是一個(gè)獨(dú)立的方法键俱,同時(shí)就是一項(xiàng)單一的任務(wù),不直接依賴于其它任何任務(wù)的完成世分。

    所有的代碼都需要測試编振。于是在代碼中的滿足上述定義,并且對獨(dú)立的工作單元進(jìn)行測試的行為臭埋,就是我們討論的單元測試党觅。

    ?

  • 優(yōu)秀單元測試的特性

    單元測試是非常有威力的魔法,但是如果使用不當(dāng)也會(huì)浪費(fèi)你大量的時(shí)間斋泄,從而對項(xiàng)目造成巨大的不利影響。另一方面镐牺,如果沒有恰當(dāng)?shù)木帉懞蛯?shí)現(xiàn)單元測試炫掐,在維護(hù)和調(diào)用這些測試上面,也會(huì)很容易的浪費(fèi)很多時(shí)間睬涧,從而影響產(chǎn)品代碼和整個(gè)項(xiàng)目募胃。

    我們不能讓這種情況出現(xiàn)。請切記畦浓,做單元測試的首要原因是為了工作更加輕松”允現(xiàn)在我們一起探討下如何編寫優(yōu)秀的單元測試,只有如此讶请,方可正確的開展單元測試祷嘶,提升項(xiàng)目的生產(chǎn)力屎媳。

    根據(jù)上一小節(jié)的內(nèi)容,首先我們列出一些優(yōu)秀的單元測試大多具備的特點(diǎn):

    1. 自動(dòng)的论巍、可重復(fù)的執(zhí)行的測試
    2. 開發(fā)人員比較容易實(shí)現(xiàn)編寫的測試
    3. 一旦寫好烛谊,將來任何時(shí)間都依舊可以用
    4. 團(tuán)隊(duì)的任何人都可運(yùn)行的測試
    5. 一般情況下單擊一個(gè)按鈕就可以運(yùn)行
    6. 測試可以可以快速的運(yùn)行
    7. ……

    或許還有更多的情形,我們可以再接再厲的思考出更多的場景嘉汰〉べ鳎總結(jié)這些,我們可以得到一些基本的應(yīng)該遵循的簡單原則鞋怀,它們能夠讓不好的單元測試遠(yuǎn)離你的項(xiàng)目双泪。這個(gè)原則定義了一個(gè)優(yōu)秀的測試應(yīng)該具備的品質(zhì),合稱為A-TRIP

    • 自動(dòng)化(Automatic)
    • 徹底的(Thorough)
    • 可重復(fù)(Repeatable)
    • 獨(dú)立的(Independent)
    • 專業(yè)的(Professional)

    接下來密似,我們分別就每一個(gè)標(biāo)準(zhǔn)進(jìn)行分析和解釋焙矛,從而我們可以正確的理解這些。

    • A-TRIP 自動(dòng)化(Automatic)

      單元測試需要能夠自動(dòng)的運(yùn)行辛友。這里包含了兩個(gè)層面:調(diào)用測試的自動(dòng)化以及結(jié)果檢查的自動(dòng)化薄扁。

      1. 調(diào)用測試的自動(dòng)化:代碼首先需要能夠正確的被調(diào)用,并且所有的測試可以有選擇的依次執(zhí)行废累。在一些時(shí)候邓梅,我們選擇IDE(Integration Development Environment,集成開發(fā)環(huán)境)可以幫助我們自動(dòng)的運(yùn)行我們指定的測試邑滨,當(dāng)然也可以考慮CI(Continuous Integration日缨,持續(xù)集成)的方式進(jìn)行自動(dòng)化執(zhí)行測試。
      2. 結(jié)果檢查的自動(dòng)化:測試結(jié)果必須在測試的執(zhí)行以后掖看,“自己”告訴“自己”并展示出來匣距。如果一個(gè)項(xiàng)目需要通過雇傭一個(gè)人來讀取測試的輸出,然后驗(yàn)證代碼是否能夠正常的工作哎壳,那么這是一種可能導(dǎo)致項(xiàng)目失敗的做法毅待。而且一致性回歸的一個(gè)重要特征就是能夠讓測試自己檢查自身是否通過了驗(yàn)證,人類對這些重復(fù)性的手工行為也是非常不擅長归榕。
    • A-TRIP 徹底的(Thorough)

      好的單元測試應(yīng)該是徹底的尸红,它們測試了所有可能會(huì)出現(xiàn)問題的情況。一個(gè)極端是每行代碼刹泄、代碼可能每一個(gè)分支外里、每一個(gè)可能拋出的異常等等,都作為測試對象特石。另一個(gè)極端是僅僅測試最可能的情形——邊界條件盅蝗、殘缺和畸形的數(shù)據(jù)等等。事實(shí)上這是一個(gè)項(xiàng)目層面的決策問題姆蘸。

      另外請注意:Bug往往集中的出現(xiàn)在代碼的某塊區(qū)域中墩莫,而不是均勻的分布在代碼的每塊區(qū)域中的芙委。對于這種現(xiàn)象,業(yè)內(nèi)引出了一個(gè)著名的戰(zhàn)斗口號“不要修修補(bǔ)補(bǔ)贼穆,完全重寫题山!”。一般情況下故痊,完全拋棄一塊Bug很多的代碼塊顶瞳,并進(jìn)行重寫會(huì)令開銷更小,痛苦更少愕秫。

      總之慨菱,單元測試越多,代碼問題越少戴甩。

    • A-TRIP 可重復(fù)(Repeatable)

      每一個(gè)測試必須可以重復(fù)的符喝,多次執(zhí)行,并且結(jié)果只能有一個(gè)甜孤。這樣說明协饲,測試的目標(biāo)只有一個(gè),就是測試應(yīng)該能夠以任意的的順序一次又一次的執(zhí)行缴川,并且產(chǎn)生相同的結(jié)果茉稠。意味著,測試不能依賴不受控制的任何外部因素把夸。這個(gè)話題引出了“測試替身”的概念而线,必要的時(shí)候,需要用測試替身來隔離所有的外界因素恋日。

      如果每次測試執(zhí)行不能產(chǎn)生相同的結(jié)果膀篮,那么真相只有一個(gè):代碼中有真正的Bug。

    • A-TRIP 獨(dú)立的(Independent)

      測試應(yīng)該是簡潔而且精煉的岂膳,這意味著每個(gè)測試都應(yīng)該有強(qiáng)的針對性誓竿,并且獨(dú)立于其它測試和環(huán)境谈截。請記住,這些測試嫂丙,可能在同一時(shí)間點(diǎn)诽表,被多個(gè)開發(fā)人員運(yùn)行竿奏。那么在編寫測試的時(shí)候绿语,確保一次只測試了一樣?xùn)|西吕粹。

      獨(dú)立的匹耕,意味著你可以在任何時(shí)間以任何順序運(yùn)行任何測試。每一個(gè)測試都應(yīng)該是一個(gè)孤島既鞠。

    • A-TRIP 專業(yè)的(Professional)

      測試代碼需要是專業(yè)的损趋。意味著,在多次編寫測試的時(shí)候桐玻,需要注意抽取相同的代碼邏輯镊靴,進(jìn)行封裝設(shè)計(jì)。這樣的做法是可行的踊谋,而且需要得到鼓勵(lì)轿衔。

      測試代碼害驹,是真實(shí)的代碼。在必要的時(shí)候摘刑,需要?jiǎng)?chuàng)建一個(gè)框架進(jìn)行測試枷恕。測試的代碼應(yīng)該和產(chǎn)品的代碼量大體相當(dāng)。所以測試代碼需要保持專業(yè)胡控,有良好的設(shè)計(jì)昼激。

    ?

  • 生產(chǎn)力的因素

    這里我們討論生產(chǎn)力的問題橙困。

    當(dāng)單元測試越來越多的時(shí)候,團(tuán)隊(duì)的測試覆蓋率會(huì)快速的提高夏跷,不用再花費(fèi)時(shí)間修復(fù)過去的錯(cuò)誤,待修復(fù)缺陷的總數(shù)在下降猫态。測試開始清晰可見的影響團(tuán)隊(duì)工作的質(zhì)量。但是當(dāng)測試覆蓋率不斷提高的時(shí)候匆光,我們是否要追求100%的測試覆蓋率呢?

    事實(shí)上周崭,那些確實(shí)的測試,不會(huì)給團(tuán)隊(duì)帶來更多價(jià)值摸航,花費(fèi)更多精力來編寫測試不會(huì)帶來額外的收益。很多測試未覆蓋到的代碼读串,在項(xiàng)目中事實(shí)上也沒有用到。何必測試那些空的方法呢胀茵?同時(shí),100%的覆蓋率并不能確保沒有缺陷——它只能保證你所有的代碼都執(zhí)行了脱拼,不論程序的行為是否滿足要求熄浓,與其追求代碼覆蓋率赌蔑,不如將重點(diǎn)關(guān)注在確保寫出有意義的測試跷乐。

    當(dāng)團(tuán)隊(duì)已經(jīng)達(dá)到穩(wěn)定水平——曲線的平坦部分顯示出額外投資的收益遞減。測試越多浅侨,額外測試的價(jià)值越少。第一個(gè)測試最有可能是針對代碼最重要的區(qū)域证膨,因此帶來高價(jià)值與高風(fēng)險(xiǎn)如输。當(dāng)我們?yōu)閹缀跛惺虑榫帉憸y試后,那些仍然沒有測試覆蓋的地方椎例,很可能是最不重要和最不可能破壞的挨决。

    接下來分析一個(gè)測試因素影響的圖:

    編排.png

    事實(shí)上,大多數(shù)代碼將測試作為質(zhì)量工具订歪,沿著曲線停滯了盖高。從這里看,我們需要找出影響程序員生產(chǎn)力的因素。本質(zhì)上谦秧,測試代碼的重復(fù)和多余的復(fù)雜性會(huì)降低生產(chǎn)力,抵消測試帶來的正面影響焕窝。最直接的兩個(gè)影響生產(chǎn)力的因素:反饋環(huán)長度調(diào)試垃沦。這兩者是在鍵盤上消耗程序員時(shí)間的罪魁禍?zhǔn)住H绻阱e(cuò)誤發(fā)生后迅速學(xué)習(xí)卧惜,那么花在調(diào)試上的時(shí)間是可以大幅避免的返工——同時(shí),反饋環(huán)越長,花在調(diào)試上的時(shí)間越多退腥。

    等待對變更進(jìn)行確認(rèn)和驗(yàn)證澜术,在很大程度上牽扯到測試執(zhí)行的速度兰英,這個(gè)是上述強(qiáng)調(diào)的反饋環(huán)長度和調(diào)試時(shí)間的根本原因之一寨闹。另外三個(gè)根本原因會(huì)影響程序員的調(diào)試量净赴。

    1. 測試的可讀性:缺乏可讀性自然降低分析的熟讀割以,并且鼓勵(lì)程序員打開調(diào)試器严沥,因?yàn)殚喿x代碼不會(huì)讓你明白嗜桌。同時(shí)因?yàn)楹茈y看出錯(cuò)誤的所在方灾,還會(huì)引入更多的缺陷坪郭。
    2. 測試結(jié)果的準(zhǔn)確度:準(zhǔn)確度是一個(gè)基本要求个从。
    3. 可依賴性和可靠性:可靠并且重復(fù)的方式運(yùn)行測試,提供結(jié)果是另一個(gè)基本要求歪沃。

    ?

  • 設(shè)計(jì)潛力的曲線

    假設(shè)先寫了最重要的測試——針對最常見和基本的場景嗦锐,以及軟件架構(gòu)中的關(guān)鍵部位。那么測試質(zhì)量很高沪曙,我們可以講重復(fù)的代碼都重構(gòu)掉奕污,并且保持測試精益和可維護(hù)。那么我們想象一下液走,積累了如此高的測試覆蓋率以后碳默,唯一沒測試到的地方,只能是那些最不重要和最不可能破壞的缘眶,項(xiàng)目沒有運(yùn)行到的地方了嘱根。平心而論,那么地方也是沒有什么價(jià)值的地方巷懈,那么该抒,之前的做法傾向于收益遞減——已經(jīng)不能再從編寫測試這樣的事情中獲取價(jià)值了。

    這是由于不做的事情而造成的質(zhì)量穩(wěn)態(tài)顶燕。之所以這么說凑保,是因?yàn)橄胍竭_(dá)更高的生產(chǎn)力,我們需要換個(gè)思路去考慮測試涌攻。為了找回丟掉的潛力欧引,我們需要從編寫測試中找到完全不同的價(jià)值——價(jià)值來自于創(chuàng)新及設(shè)計(jì)導(dǎo)向,而并非防止回歸缺陷的保護(hù)及驗(yàn)證導(dǎo)向恳谎。

    總而言之维咸,為了充分和完全的發(fā)揮測試的潛力,我們需要:

    1. 像生產(chǎn)代碼一樣對待你測試代碼——大膽重構(gòu)惠爽、創(chuàng)建和維護(hù)高質(zhì)量測試
    2. 開始將測試作為一種設(shè)計(jì)工具癌蓖,指導(dǎo)代碼針對實(shí)際用途進(jìn)行設(shè)計(jì)。

    第一種方法婚肆,是我們在這篇講義中討論的重點(diǎn)租副。多數(shù)程序員在編寫測試的時(shí)候會(huì)不知所措,無法顧及高質(zhì)量较性,或者降低編寫用僧、維護(hù)结胀、運(yùn)行測試的成本。

    第二種方法责循,是討論利用測試作為設(shè)計(jì)的方面糟港,我們的目的是對這種動(dòng)態(tài)和工作方式有個(gè)全面的了解,在接下來的[探討]中我們繼續(xù)分析這個(gè)話題院仿。
    ?

1.2 [探討]正確地認(rèn)識單元測試

  • 練習(xí):一個(gè)簡單的單元測試示例

    我們從一個(gè)簡單的例子開始設(shè)計(jì)測試秸抚,它是一個(gè)獨(dú)立的方法,用來查找list中的最大值歹垫。

    int getLargestElement(int[] list){
      // TODO: find largest element from list and return it.
    }
    

    比如剥汤,給定一個(gè)數(shù)組 { 1, 50排惨, 81吭敢, 100 },這個(gè)方法應(yīng)該返回100暮芭,這樣就構(gòu)成了一個(gè)很合理測試鹿驼。那么,我們還能想出一些別的測試么辕宏?就這樣的方法畜晰,在繼續(xù)閱讀之前,請認(rèn)真的思考一分鐘匾效,記下來所有能想到的測試。

    在繼續(xù)閱讀之前恤磷,請靜靜的思考一會(huì)兒……

    想到了多少測試呢面哼?請將想到的測試都在紙上寫出來。格式如下:

    • 50, 60, 7, 58, 98 --> 98
    • 100, 90, 25 --> 100
    • ……

    然后我們編寫一個(gè)基本的符合要求的函數(shù)扫步,來繼續(xù)進(jìn)行測試魔策。

    public int getLargestElement(int[] list) {
      int temp = Integer.MIN_VALUE;
      for (int i = 0; i < list.length; i++) {
        if (temp < list[i]) {
          temp = list[i];
        }
      }
      return temp;
    }
    

    然后請考慮上述代碼是否有問題,可以用什么樣的例子來進(jìn)行測試河胎。

    ?

  • 分析:為什么不寫單元測試

    請思考當(dāng)前在組織或者項(xiàng)目中闯袒,如何寫單元測試,是否有不寫單元測試的習(xí)慣和借口游岳,這些分別是什么政敢?

    ?

  • 分析:單元測試的結(jié)構(gòu)與內(nèi)容

    當(dāng)我們確定要寫單元測試的時(shí)候,請認(rèn)真分析胚迫,一個(gè)單元測試包含什么樣的內(nèi)容喷户,為什么?

    ?

  • 分析:單元測試的必要性

    請分析單元測試必要性访锻,嘗試得出單元測試所帶來的好處褪尝。

    單元測試的主要目的闹获,就是驗(yàn)證應(yīng)用程序是否可以按照預(yù)期的方式正常運(yùn)行,以及盡早的發(fā)現(xiàn)錯(cuò)誤河哑。盡管功能測試也可以做到這一點(diǎn)避诽,但是單元測試更加強(qiáng)大,并且用戶更加豐富璃谨,它能做的不僅僅是驗(yàn)證應(yīng)用程序的正常運(yùn)行沙庐,單元測試還可以做到更多。

    • 帶來更高的測試覆蓋率

      功能測試大約可以覆蓋到70%的應(yīng)用程序代碼睬罗,如果希望進(jìn)行的更加深入一點(diǎn)轨功,提供更高的測試覆蓋率,那么我們需要編寫單元測試了容达。單元測試可以很容易的模擬錯(cuò)誤條件古涧,這一點(diǎn)在功能測試中卻很難辦到,有些情況下甚至是不可能辦到的花盐。單元測試不僅提供了測試羡滑,還提供了更多的其它用途,在最后一部分我們將會(huì)繼續(xù)介紹算芯。

    • 提高團(tuán)隊(duì)效率

      在一個(gè)項(xiàng)目中柒昏,經(jīng)過單元測試通過的代碼,可以稱為高質(zhì)量的代碼熙揍。這些代碼無需等待到其它所有的組件都完成以后再提交职祷,而是可以隨時(shí)提交,提高的團(tuán)隊(duì)的效率届囚。如果不進(jìn)行單元測試有梆,那么測試行為大多數(shù)要等到所有的組件都完成以后,整個(gè)應(yīng)用程序可以運(yùn)行以后意系,才能進(jìn)行泥耀,嚴(yán)重影響了團(tuán)隊(duì)效率。

    • 自信的重構(gòu)和改進(jìn)實(shí)現(xiàn)

      在沒有進(jìn)行單元測試的代碼中蛔添,重構(gòu)是有著巨大風(fēng)險(xiǎn)的行為痰催。因?yàn)槟憧偸强赡軙?huì)損壞一些東西。而單元測試提供了一個(gè)安全網(wǎng)迎瞧,可以為重構(gòu)的行為提供信心夸溶。同時(shí)在良好的單元測試基礎(chǔ)上,對代碼進(jìn)行改進(jìn)實(shí)現(xiàn)凶硅,對一些修改代碼蜘醋,增加新的特性或者功能的行為,有單元測試作為保障咏尝,可以防止在改進(jìn)的基礎(chǔ)上压语,引入新的Bug啸罢。

    • 將預(yù)期的行為文檔化

      在一些代碼的文檔中,示例的威力是眾所周知的胎食。當(dāng)完成一個(gè)生產(chǎn)代碼的時(shí)候扰才,往往要生成或者編寫對應(yīng)的API文檔。而如果在這些代碼中進(jìn)行了完整的單元測試厕怜,則這些單元測試就是最好的實(shí)例衩匣。它們展示了如何使用這些API,也正是因?yàn)槿绱酥嗪剑鼈兙褪峭昝赖拈_發(fā)者文檔琅捏,同時(shí)因?yàn)閱卧獪y試必須與工作代碼保持同步,所以比起其它形式的文檔递雀,單元測試必須始終是最新的柄延,最有效的。

    ?

1.3 用 JUnit 進(jìn)行單元測試

JUnit誕生于1997年缀程,Erich Gamma 和 Kent Beck 針對 Java 創(chuàng)建了一個(gè)簡單但是有效的單元測試框架搜吧,隨后迅速的成為 Java 中開發(fā)單元測試的事實(shí)上的標(biāo)準(zhǔn)框架,被稱為 xUnit 的相關(guān)測試框架杨凑,正在逐漸成為任何語言的標(biāo)準(zhǔn)框架滤奈。

以我們的角度,JUnit用來“確保方法接受預(yù)期范圍內(nèi)的輸入撩满,并且為每一次測試輸入返回預(yù)期的值”蜒程。在這一節(jié)里,我們從零開始介紹如何為一個(gè)簡單的類創(chuàng)建單元測試伺帘。我們首先編寫一個(gè)測試昭躺,以及運(yùn)行該測試的最小框架,以便能夠理解單元測試是如何處理的曼追。然后我們在通過 JUnit 展示正確的工具可以如何使生活變得更加簡單窍仰。

本文中使用 JUnit 4 最新版進(jìn)行單元測試的示例與講解汉规。

JUnit 4 用到了許多 Java 5 中的特性礼殊,如注解。JUnit 4 需要使用 Java 5 或者更高的版本针史。

  • 用 JUnit 構(gòu)建單元測試

    這里我們開始構(gòu)建單元測試晶伦。

    首先我們使用之前一節(jié)的【探討】中使用過的類,作為被測試的對象啄枕。創(chuàng)建一個(gè)類婚陪,叫做HelloWorld,該類中有一個(gè)方法频祝,可以從輸入的一個(gè)整型數(shù)組中泌参,找到最大的值脆淹,并且返回該值。

    代碼如下:

    public class HelloWorld {
    
       public int getLargestElement(int[] list) {
           int temp = Integer.MIN_VALUE;
           for (int i = 0; i < list.length; i++) {
               if (temp < list[i]) {
                   temp = list[i];
               }
           }
           return temp;
       }
    }
    

    雖然我們針對該類沽一,沒有列出文檔盖溺,但是 HelloWorld 中的 int getLargestElement(int[])方法的意圖顯然是接受一個(gè)整型的數(shù)組,并且以 int 的類型铣缠,返回該數(shù)組中最大的值烘嘱。編譯器能夠告訴我們,它通過了編譯蝗蛙,但是我們也應(yīng)該確保它在運(yùn)行期間可以正常的工作蝇庭。

    單元測試的核心原則是“任何沒有經(jīng)過自動(dòng)測試的程序功能都可以當(dāng)做它不存在”。getLargestElement 方法代表了 HelloWorld 類的一個(gè)核心功能捡硅,我們擁有了一些實(shí)現(xiàn)該功能的代碼哮内,現(xiàn)在缺少的只是一個(gè)證明實(shí)現(xiàn)能夠正常工作的自動(dòng)測試。

    這個(gè)時(shí)候病曾,進(jìn)行任何測試看起來都會(huì)有些困難牍蜂,畢竟我們甚至沒有可以輸入一個(gè)數(shù)組的值的用戶界面。除非我們使用在【探討】中使用的類進(jìn)行測試泰涂。

    示例代碼:

    public class HelloWorldTest {
        public static void main(String[] args) {
            HelloWorld hello = new HelloWorld();
            int[] listToTest = {-10, -20, -100, -90};
            int result = hello.getLargestElement(listToTest);
            if (result != -10) {
                System.out.println("獲取最大值錯(cuò)誤鲫竞,期望的結(jié)果是 100;實(shí)際錯(cuò)誤的結(jié)果: " + result);
            } else {
                System.out.println("獲取最大值正確逼蒙,通過測試从绘。");
            }
        }
    }
    

    輸出結(jié)果如下:

    獲取最大值正確,通過測試是牢。
    
    Process finished with exit code 0
    

    第一個(gè) HelloWorldTest 類非常簡單僵井。它創(chuàng)建了 HelloWorld 的一個(gè)實(shí)例,傳遞給它一個(gè)數(shù)組驳棱,并且檢查運(yùn)行的結(jié)果批什。如果運(yùn)行結(jié)果與我們預(yù)期的不一致,那么我們就在標(biāo)準(zhǔn)輸出設(shè)備上輸出一條消息社搅。

    現(xiàn)在我們編譯并且運(yùn)行這個(gè)程序驻债,那么測試將會(huì)正常通過,同時(shí)一切看上去都非常順利形葬『夏牛可是事實(shí)上并非都是如此圓滿,如果我們修改部分測試笙以,再次運(yùn)行淌实,可能會(huì)遇到不通過測試的情況,甚至代碼異常。

    接下來我們修改代碼如下:

    public class HelloWorldTest {
        public static void main(String[] args) {
            HelloWorld hello = new HelloWorld();
            int[] listToTest = null;
            int result = hello.getLargestElement(listToTest);
            if (result != -10) {
                System.out.println("獲取最大值錯(cuò)誤拆祈,期望的結(jié)果是 100恨闪;實(shí)際錯(cuò)誤的結(jié)果: " + result);
            } else {
                System.out.println("獲取最大值正確,通過測試放坏。");
            }
        }
    }
    

    當(dāng)我們再次執(zhí)行代碼的時(shí)候凛剥,代碼運(yùn)行就會(huì)報(bào)錯(cuò)。運(yùn)行結(jié)果如下:

    Exception in thread "main" java.lang.NullPointerException
    at HelloWorld.getLargestElement(HelloWorld.java:11)
    at HelloWorldTest.main(HelloWorldTest.java:13)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)
    
    Process finished with exit code 1
    

    按照第一節(jié)中的描述的優(yōu)秀的單元測試轻姿,上述代碼毫無疑問犁珠,稱不上優(yōu)秀的單元測試,因?yàn)闇y試連運(yùn)行都無法運(yùn)行互亮。令人高興的是犁享,JUnit 團(tuán)隊(duì)解決了上述麻煩。JUnit 框架支持自我檢測豹休,并逐個(gè)報(bào)告每個(gè)測試的所有錯(cuò)誤和結(jié)果炊昆。接下來我們來進(jìn)一步了解 JUnit 。

    JUnit 是一個(gè)單元測試框架威根,在設(shè)計(jì)之初凤巨,JUnit 團(tuán)隊(duì)已經(jīng)為框架定義了3個(gè)不相關(guān)的目標(biāo):

    • 框架必須幫助我們編寫有用的測試
    • 框架必須幫助我們創(chuàng)建具有長久價(jià)值的測試
    • 框架必須幫助我們通過復(fù)用代碼來降低編寫測試的成本

    首先安裝 JUnit 。這里我們使用原始的方式添加 JAR 文件到 ClassPath 中洛搀。

    下載地址:https://github.com/junit-team/junit4/wiki/Download-and-Install敢茁,下載如下兩個(gè) JAR 包,放到項(xiàng)目的依賴的路徑中留美。

    • junit.jar
    • hamcrest-core.jar

    在 IDEA 的項(xiàng)目中彰檬,添加一個(gè)文件夾 lib,將上述兩個(gè)文件添加到 lib 中谎砾。

    然后 File | Project Structure | Modules逢倍,打開 Modules 對話框,選擇右邊的 Dependencies 的選項(xiàng)卡景图,點(diǎn)擊右邊的 + 號较雕,選擇 “1 JARs or directories”并找到剛剛添加的兩個(gè) JRA 文件,并確定挚币。

    然后新建 Java Class亮蒋,代碼如下:

    public class HelloWorldTests {
    
        @Test
        public void test01GetLargestElement(){
            HelloWorld hello = new HelloWorld();
            int[] listToTest = {10, 20, 100, 90};
            int result = hello.getLargestElement(listToTest);
            Assert.assertEquals("獲取最大值錯(cuò)誤! ", 100, result);
        }
    
        @Test
        public void test02GetLargestElement(){
            HelloWorld hello = new HelloWorld();
            int[] listToTest = {-10, 20, -100, 90};
            int result = hello.getLargestElement(listToTest);
            Assert.assertEquals("獲取最大值錯(cuò)誤! ", 90, result);
        }
    }
    

    如上的操作,我們便定義了一個(gè)單元測試忘晤,使用 JUnit 編寫了測試宛蚓。主要的要點(diǎn)如下:

    1. 針對每個(gè)測試的對象類激捏,單獨(dú)編寫測試類设塔,測試方法,避免副作用
    2. 定義一個(gè)測試類
    3. 使用 JUnit 的注解方式提供的方法: @Test
    4. 使用 JUnit 提供的方法進(jìn)行斷言:Assert.assertEquals(String msg, long expected, long actual)
    5. 創(chuàng)建一個(gè)測試方法的要求:該方法必須是公共的,不帶任何參數(shù)闰蛔,返回值類型為void痕钢,同時(shí)必須使用@Test注解
  • JUnit 的各種斷言

    為了進(jìn)行驗(yàn)測試驗(yàn)證,我們使用了由 JUnit 的 Assert 類提供的 assert 方法序六。正如我們在上面的例子中使用的那樣任连,我們在測試類中靜態(tài)的導(dǎo)入這些方法瘟檩,同時(shí)還有更多的方法以供我們使用萎河,如下我們列出一些流行的 assert 方法。

    方法 Method 檢查條件
    assertEquals(msg, a, b) a == b荸型,msg可選繁涂,用來解釋失敗的原因
    assertNotEquals(msg, a, b) a != b拱她,msg可選,用來解釋失敗的原因
    assertTrue(msg, x ) x 是真扔罪,msg可選秉沼,用來解釋失敗的原因
    assertFalse(msg, x) x 是假,msg可選矿酵,用來解釋失敗的原因
    assertSame(msg, a, b) a 不是 b唬复,msg可選,用來解釋失敗的原因
    assertNull(msg, x) x 是null全肮,msg可選敞咧,用來解釋失敗的原因
    assertNotNull(msg, x) x 不是null,msg可選辜腺,用來解釋失敗的原因
    assertThat(msg, actual, matcher) 用匹配器進(jìn)行斷言妄均,高級應(yīng)用*,不再此文檔討論

    一般來說哪自,一個(gè)測試方法包括了多個(gè)斷言丰包。當(dāng)其中一個(gè)斷言失敗的時(shí)候,整個(gè)測試方法將會(huì)被終止——從而導(dǎo)致該方法中剩下的斷言將會(huì)無法執(zhí)行了壤巷。此時(shí)邑彪,不能有別的想法,只能先修復(fù)當(dāng)前失敗的斷言胧华,以此類推寄症,不斷地修復(fù)當(dāng)前失敗的斷言,通過一個(gè)個(gè)測試矩动,慢慢前行有巧。

  • JUnit 的框架

    到目前為止,我們只是介紹了斷言本身悲没,很顯然我們不能只是簡單的把斷言方法寫完篮迎,就希望測試可以運(yùn)行起來。我們需要一個(gè)框架來輔助完成這些,那么我們就要做多一些工作了甜橱。很幸運(yùn)的是逊笆,我們不用多做太多。

    在 JUnit 4 提供了@Before@After岂傲,在每個(gè)測試函數(shù)調(diào)用之前/后都會(huì)調(diào)用难裆。

    • @Before: Method annotated with @Before executes before every test. 每個(gè)測試方法開始前執(zhí)行的方法
    • @After: Method annotated with @After executes after every test. 每個(gè)測試方法執(zhí)行后再執(zhí)行的方法

    如果在測試之前有些工作我們只想做一次,用不著每個(gè)函數(shù)之前都做一次镊掖。比如讀一個(gè)很大的文件乃戈。那就用下面兩個(gè)來標(biāo)注:
    @BeforeClass: 測試類初始化的時(shí)候,執(zhí)行的方法
    @AfterClass: 測試類銷毀的時(shí)候亩进,執(zhí)行的方法

    注意:

    1. @Before/@After 可以執(zhí)行多次; @BeforeClass/@AfterClass 只能執(zhí)行一次
    2. 如果我們預(yù)計(jì)有Exception偏化,那就給@Test加參數(shù):@Test(expected = XXXException.class)
    3. 如果出現(xiàn)死循環(huán)怎么辦?這時(shí)timeout參數(shù)就有用了:@Test(timeout = 1000)
    4. 如果我們暫時(shí)不用測試一個(gè)用例镐侯,我們不需要?jiǎng)h除或都注釋掉侦讨。只要改成:@Ignore ,你也可以說明一下原因@Ignore("something happens")

    示例代碼:下面的代碼代表了單元測試用例的基本框架

    public class JUnitDemoTest {
        @Before
        public void setUp(){
            //TODO: 測試預(yù)置條件苟翻,測試安裝
        }
        @After
        public void tearDown(){
            //TODO: 測試清理韵卤,測試卸載
        }
        @Test
        public void test01(){
            //TODO: test01 腳本
        }
        @Test
        public void test02(){
            //TODO: test02 腳本
        }
        @Test
        public void test03(){
            //TODO: test03 腳本
        }
    }
    

    單元測試框架的過程如下:

    測試過程.png

    JUnit 需要注意的事項(xiàng):

    1. 每個(gè) @Test 都是一個(gè)測試用例,一個(gè)類可以寫多個(gè) @Test
    2. 每個(gè) @Test 執(zhí)行之前 都會(huì)執(zhí)行 @Before崇猫,執(zhí)行之后都會(huì)運(yùn)行 @After
    3. 每個(gè) @Test沈条,@After@Before 都必須是 public void, 參數(shù)為空
    4. @After / @Before 也可以是多個(gè)诅炉,并且有執(zhí)行順序蜡歹。在每個(gè) @Test 前后執(zhí)行多次。
      • @Before 多個(gè)名字長度一致涕烧,z -> a, 長度不一致月而,會(huì)先執(zhí)行名字短的。
      • @After / @Test 多個(gè)名字長度一致议纯,a -> z, 長度不一致父款,會(huì)后執(zhí)行名字短的。
    5. @AfterClass / @BeforeClass 也可以是多個(gè)瞻凤,并且有執(zhí)行順序憨攒。只會(huì)在測試類的實(shí)例化前后各執(zhí)行一次。
      • @BeforeClass 多個(gè)名字長度一致阀参,z -> a, 長度不一致肝集,會(huì)先執(zhí)行名字短的。
      • @AfterClass 多個(gè)名字長度一致蛛壳,a -> z, 長度不一致杏瞻,會(huì)后執(zhí)行名字短的所刀。
    6. @AfterClass / @BeforeClass 都必須是 public static void, 參數(shù)為空
    7. 測試結(jié)果有 通過、不通過和錯(cuò)誤 三種伐憾。
  • JUnit 的測試運(yùn)行

    這一小節(jié),我們來介紹一下 JUnit 4 中的新的測試運(yùn)行器(Test Runner)赫模。如果我們剛開始編寫測試树肃,那么我們需要盡可能快捷的運(yùn)行這些測試,這樣我們才能夠?qū)y試融合到開發(fā)循環(huán)中去瀑罗。

    編碼 → 運(yùn)行 → 測試 → 編碼……

    其中胸嘴,JUnit 就可以讓我們構(gòu)建和運(yùn)行測試。我們可以按照組合測試Suite 以及參數(shù)化測試分別來運(yùn)行測試斩祭。

    • 組合測試Suite

      測試集 (Suite 或者 test suite)一組測試劣像。測試集是一種把多個(gè)相關(guān)測試歸入一組的便捷測試方式〈菝担可以在一個(gè)測試集中耳奕,定義需要打包測試的類,并一次性運(yùn)行所有包含的測試诬像;也可以分別定義多個(gè)測試集屋群,然后在一個(gè)主測試集中運(yùn)行多個(gè)相關(guān)的測試集,打包相關(guān)的測試的類坏挠,并一次性運(yùn)行所有包含的測試芍躏。

      示例代碼如下:

      @RunWith(value = Suite.class)
      @Suite.SuiteClasses(value = HelloWorldTests.class)
      public class HelloWorldTestRunner {
      }
      

      ?

    • 參數(shù)化測試

      參數(shù)化測試(Parameterized)是測試運(yùn)行器允許使用不同的參數(shù)多次運(yùn)行同一個(gè)測試。參數(shù)化測試的代碼如下:

      @RunWith(value = Parameterized.class)
      public class ParameterizedHelloWorldTests {
      
          @Parameterized.Parameters
          public static Collection getTestParameters() {
              int[] listToTest1 = {10, 80, 100, -96};
              int[] listToTest2 = {-10, -80, -100, -6};
              int[] listToTest3 = {10, -80, -100, -96};
              int[] listToTest4 = {10, -80, 100, -96};
              int[] listToTest5 = {10, 80, -100, -96};
      
              return Arrays.asList(new Object[][]{
                      {100, listToTest1},
                      {-6, listToTest2},
                      {10, listToTest3},
                      {100, listToTest4},
                      {80, listToTest5}});
          }
      
          @Parameterized.Parameter
          public int expected;
      
          @Parameterized.Parameter(value = 1)
          public int[] listToTest;
      
          @Test
          public void testGetLargestElementByParameters() {
              Assert.assertEquals("獲取最大元素錯(cuò)誤降狠!", expected, new HelloWorld().getLargestElement(listToTest));
          }
      }
      

      對于參數(shù)化測試的運(yùn)行器來運(yùn)行測試類对竣,那么必須滿足以下要求:

      1. 測試類必須使用@RunWith(value = Parameterized.class)注解
      2. 必須聲明測試中所使用的實(shí)例變量
      3. 提供一個(gè)用@Parameterized.Parameters的注解方法,這里用的是getTestParameters()榜配,同時(shí)此方法的簽名必須是public static Collection
      4. 為測試指定構(gòu)造方法否纬,或者一個(gè)個(gè)全局變量的成員進(jìn)行賦值
      5. 所有的測試方法以@Test注解,實(shí)例化被測試的程序蛋褥,同時(shí)在斷言中使用我們提供的全局變量作為參數(shù)

      ?

1.4 [探討]按業(yè)務(wù)價(jià)值導(dǎo)向進(jìn)行單元測試設(shè)計(jì)

  • 練習(xí):測試的結(jié)果是否正確

    如果測試代碼能夠運(yùn)行正確烦味,我們要怎么才能知道它是正確的呢?

    如何應(yīng)對測試數(shù)據(jù)量比較大的時(shí)候壁拉,我們的測試代碼如何編寫谬俄?

    ?

  • 練習(xí):測試的邊界條件

    尋找邊界條件是單元測試中最有價(jià)值的工作之一,一般來說Bug出現(xiàn)在邊界上的概率比較大弃理。那么我們都需要考慮什么樣的邊界條件呢溃论?

    ?

  • 練習(xí):強(qiáng)制產(chǎn)生錯(cuò)誤條件

    關(guān)于產(chǎn)生錯(cuò)誤的條件,請列出一個(gè)詳細(xì)的清單來痘昌。

    ?

  • 分析:測試作為設(shè)計(jì)工具

    第一節(jié)【專題】中钥勋,我們有討論設(shè)計(jì)潛力的曲線炬转,其中第二條方案強(qiáng)調(diào)了測試作為設(shè)計(jì)的工具。那么我們想就兩個(gè)方面來討論這個(gè)測試設(shè)計(jì)的問題算灸。

    1. TDD扼劈,測試驅(qū)動(dòng)開發(fā)
    2. BDD,行為驅(qū)動(dòng)開發(fā)

    ?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末菲驴,一起剝皮案震驚了整個(gè)濱河市荐吵,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌赊瞬,老刑警劉巖先煎,帶你破解...
    沈念sama閱讀 221,635評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異巧涧,居然都是意外死亡薯蝎,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評論 3 399
  • 文/潘曉璐 我一進(jìn)店門谤绳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來占锯,“玉大人,你說我怎么就攤上這事缩筛⊙萄耄” “怎么了?”我有些...
    開封第一講書人閱讀 168,083評論 0 360
  • 文/不壞的土叔 我叫張陵歪脏,是天一觀的道長疑俭。 經(jīng)常有香客問我,道長婿失,這世上最難降的妖魔是什么钞艇? 我笑而不...
    開封第一講書人閱讀 59,640評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮豪硅,結(jié)果婚禮上哩照,老公的妹妹穿的比我還像新娘。我一直安慰自己懒浮,他們只是感情好飘弧,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,640評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著砚著,像睡著了一般次伶。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上稽穆,一...
    開封第一講書人閱讀 52,262評論 1 308
  • 那天冠王,我揣著相機(jī)與錄音,去河邊找鬼舌镶。 笑死柱彻,一個(gè)胖子當(dāng)著我的面吹牛豪娜,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播哟楷,決...
    沈念sama閱讀 40,833評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼瘤载,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了卖擅?” 一聲冷哼從身側(cè)響起鸣奔,我...
    開封第一講書人閱讀 39,736評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎磨镶,沒想到半個(gè)月后溃蔫,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體健提,經(jīng)...
    沈念sama閱讀 46,280評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡琳猫,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,369評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了私痹。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片脐嫂。...
    茶點(diǎn)故事閱讀 40,503評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖紊遵,靈堂內(nèi)的尸體忽然破棺而出账千,到底是詐尸還是另有隱情,我是刑警寧澤暗膜,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布匀奏,位于F島的核電站,受9級特大地震影響学搜,放射性物質(zhì)發(fā)生泄漏娃善。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,870評論 3 333
  • 文/蒙蒙 一瑞佩、第九天 我趴在偏房一處隱蔽的房頂上張望聚磺。 院中可真熱鬧,春花似錦炬丸、人聲如沸瘫寝。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽焕阿。三九已至,卻和暖如春首启,著一層夾襖步出監(jiān)牢的瞬間捣鲸,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評論 1 272
  • 我被黑心中介騙來泰國打工闽坡, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留栽惶,地道東北人愁溜。 一個(gè)月前我還...
    沈念sama閱讀 48,909評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像外厂,于是被迫代替她去往敵國和親冕象。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,512評論 2 359

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理汁蝶,服務(wù)發(fā)現(xiàn)渐扮,斷路器,智...
    卡卡羅2017閱讀 134,701評論 18 139
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法掖棉,類相關(guān)的語法墓律,內(nèi)部類的語法,繼承相關(guān)的語法幔亥,異常的語法耻讽,線程的語...
    子非魚_t_閱讀 31,663評論 18 399
  • 2. 單元測試進(jìn)階——尋求優(yōu)秀 2.1 使用測試替身 在現(xiàn)代開發(fā)者測試的上下文中,除了允許在某些依賴缺失的情況下編...
    厲鉚兄閱讀 743評論 0 2
  • 再過兩個(gè)小時(shí)就要22歲了帕棉,其實(shí)是96年5月份的针肥,認(rèn)真算來21周歲還不到。人嘛香伴,在這個(gè)時(shí)候總想顯得自己更年輕一點(diǎn)慰枕。《...
    顧西涼zhq閱讀 250評論 0 0
  • 文@江寒園 在前幾篇有關(guān)電影敘事結(jié)構(gòu)的文章中稍稍提及過電影當(dāng)中的“主題—并置”敘事即纲,而“主題—并置”僅僅作為“并置...
    江寒園閱讀 9,893評論 7 30