《Effective Unit Testing》 讀書筆記 6 可讀性 code smell

本書第二部分中主要介紹了可讀性/可維護(hù)性/可靠性三個(gè)方面的code smell或者說(shuō)反模式(anti-pattern)绩社。
我們首先看可讀性的code smell. 再次解釋一下所謂可讀性炸宵,就是代碼的意圖和行為能通過(guò)閱讀代碼就能得到準(zhǔn)確而清晰的表達(dá)猾愿。

  • 原始的斷言 (Primitive assertions)
    意思是assert所做的判斷抽象層級(jí)過(guò)于低隘马,是在測(cè)試代碼的實(shí)現(xiàn)。如書中下面的例子:
@Test
public void outputHasLineNumbers() {
    String content = "1st match on #1\nand\n2nd match on #3";
    String out = grep.grep("match", "test.txt", content);
    assertTrue(out.indexOf("test.txt:1 1st match") != -1);
    assertTrue(out.indexOf("test.txt:3 2nd match") != -1);
}

其中的indexOf 和 magic number -1 都過(guò)于和java的String類的實(shí)現(xiàn)相關(guān)了。
修改后的版本用到了org.junit.JUnitMatchers#containsString()方法,對(duì)比之前犀斋,是不是可讀性好了很多呢?

@Test
public void outputHasLineNumbers() {
    String content = "1st match on #1\nand\n2nd match on #3";
    String out = grep.grep("match", "test.txt", content);
    assertThat(out, containsString("test.txt:1 1st match"));
    assertThat(out, containsString("test.txt:3 2nd match"));
}

我們應(yīng)該追求測(cè)試代碼的本質(zhì)情连,也就是正確的行為叽粹,而不是去測(cè)試代碼的實(shí)現(xiàn)細(xì)節(jié)。

  • 超斷言(Hyperassertions)
    這是指斷言的內(nèi)容過(guò)于具體和瑣碎却舀,或者過(guò)于脆弱虫几,非本質(zhì)的改變就會(huì)影響測(cè)試結(jié)果⊥彀危或者說(shuō)我們所測(cè)試的輸出過(guò)于龐大辆脸,并且采用過(guò)于精細(xì)的比較去判斷。
    文中舉得一個(gè)例子是測(cè)試一段Log代碼輸出螃诅,簡(jiǎn)單的判斷l(xiāng)og要和預(yù)定的一套String完全一樣啡氢。這樣的問(wèn)題在于如果稍微改一下log的格式,比如時(shí)間顯示格式(假設(shè)測(cè)試的不是時(shí)間格式對(duì)不對(duì))州刽,測(cè)試就會(huì)通不過(guò),而且不看輸出細(xì)節(jié)還不知道到底是哪里輸出錯(cuò)了(理想情況是希望通過(guò)方法名直接知道什么錯(cuò)誤而不需要看output)浪箭。
    不過(guò)我個(gè)人覺(jué)得穗椅,這種超斷言在有些特殊情況下也是可以用的,比如一些UI測(cè)試直接用了截屏比較圖片的方式奶栖,可以認(rèn)為是很脆弱的超斷言匹表,因?yàn)楹?jiǎn)單的CSS改動(dòng)就會(huì)導(dǎo)致測(cè)試通不過(guò),但如果每個(gè)UI對(duì)象去測(cè)試的維護(hù)成本更高或者根本難以寫出這種測(cè)試的話宣鄙,圖片比較方式也是可以接受的甚至是唯一現(xiàn)實(shí)可行的方案袍镀。
    這在我們系統(tǒng)中有個(gè)例子,就是Order Interface的測(cè)試冻晤,為了簡(jiǎn)單起見苇羡,直接拿接口的xml輸出去和一個(gè)認(rèn)為正確的文本比較是否完全一致,雖然寫起來(lái)簡(jiǎn)單鼻弧,也導(dǎo)致了測(cè)試的脆弱性设江,其中利弊需要權(quán)衡。

  • 逐比特位斷言 (Bitwise assertions)
    這個(gè)其實(shí)也不用單獨(dú)寫出來(lái)攘轩,是一種特殊的原始斷言叉存,給個(gè)例子看看就明白了。

public class PlatformTest {
  @Test
  public void platformBitLength() {
    assertTrue(Platform.IS_32_BIT ^ Platform.IS_64_BIT);
  }
}

應(yīng)該改成下面這樣

public class PlatformTest {
 @Test
  public void platformBitLength() {
   assertTrue("Not 32 or 64-bit platform?",
     Platform.IS_32_BIT || Platform.IS_64_BIT);
   assertFalse("Can’t be 32 and 64-bit at the same time.",
     Platform.IS_32_BIT && Platform.IS_64_BIT);
 }
}
  • 附加的細(xì)節(jié)(Incidental details)
    這個(gè)是說(shuō)單段的測(cè)試代碼太長(zhǎng)了度帮,所有東西寫在一個(gè)方法里歼捏,抽象層次混雜,無(wú)關(guān)的細(xì)節(jié)太多, 讓人一眼看不出代碼到底想要干啥瞳秽,從而可讀性不好瓣履。解決辦法是抽方法出來(lái),讓測(cè)試方法保持在同一個(gè)抽象級(jí)別上寂诱。具體就不說(shuō)了拂苹。

  • 分裂的人格 (Split personality)
    簡(jiǎn)單說(shuō)來(lái)就是同一個(gè)測(cè)試方法里斷言了幾個(gè)不太相關(guān)的東西,可以說(shuō)是不同的人格(personality)或者不同的興趣點(diǎn)(interest)痰洒。當(dāng)然瓢棒,如何劃分所謂不想關(guān)的事情需要具體情況具體分析,也可能不同人理解不同丘喻。根據(jù)不同情況脯宿,我們采取的措施可以是簡(jiǎn)單拆成不同的測(cè)試方法。如果拆方法還不夠泉粉,那我們自然還可以拆成不同的測(cè)試類(相同部分可以提取抽象測(cè)試基類)连霉。

  • 分裂的邏輯 (Split logic)
    這說(shuō)的是測(cè)試的代碼過(guò)于長(zhǎng)導(dǎo)致看了后面的忘了前面的,或者邏輯被分隔在不同的文件中嗡靡,看了這個(gè)文件忘了那個(gè)文件跺撼。
    書中的例子如下

public class TestRuby {
  private Ruby runtime;
  @Before
  public void setUp() throws Exception {
    runtime = Ruby.newInstance();
  }
  @Test
  public void testVarAndMet() throws Exception {
    runtime.getLoadService().init(new ArrayList());
    eval("load 'test/testVariableAndMethod.rb'");
    assertEquals("Hello World", eval("puts($a)"));
    assertEquals("dlroW olleH", eval("puts $b"));
    assertEquals("Hello World",
    eval("puts $d.reverse, $c, $e.reverse"));
    assertEquals("135 20 3",
    eval("puts $f, \" \", $g, \" \", $h"));
  }
}

上面這段代碼的最大問(wèn)題就在于 eval("load 'test/testVariableAndMethod.rb'"); 這句話。這個(gè)測(cè)試從外部加載了另一個(gè)ruby文件讨彼,讀者不兩個(gè)文件對(duì)比著看歉井,根本不知道在測(cè)試什么。
解決辦法是哈误,如果testVariableAndMethod.rb這個(gè)文件內(nèi)容不多的話哩至,直接把文件內(nèi)容插在測(cè)試代碼里(inline)。如果一定要分開放的話蜜自,要和測(cè)試放在同一個(gè)目錄下面菩貌,要能通過(guò)相對(duì)路徑來(lái)訪問(wèn)。

  • 魔法數(shù)(Magic number)
    這個(gè)很好理解重荠,一般來(lái)說(shuō)解決方法是抽取常量箭阶。不過(guò)書中還寫了一些特殊方式來(lái)解決,比如用獨(dú)特的方法名來(lái)表明入?yún)⒌暮x戈鲁。見下面這個(gè)例子
  public class BowlingGameTest {
  @Test
  public void perfectGame() throws Exception {
    roll(pins(10), times(12));
    assertThat(game.score(), is(equalTo(300)));
  }
  private int pins(int n) { return n; }
  private int times(int n) { return n; }
}
  • 過(guò)長(zhǎng)的初始化 (Setup sermon)
    就是說(shuō)寫了一大堆的代碼用于初始化測(cè)試所需的fixture尾膊,是一種特殊情況的Incidental details。解決方法無(wú)非抽方法荞彼,變量取好名字冈敛,保持同一方法中的抽象層級(jí)一致性。

  • 過(guò)保護(hù)的測(cè)試(Overprotective tests)
    測(cè)試一些非核心的并不是你測(cè)試代碼需要測(cè)試的內(nèi)容鸣皂。

@Test
public void count() {
  Data data = project.getData();
  assertNotNull(data);
  assertEquals(4, data.count());
}

上面代碼中的第一個(gè)null斷言不需要寫抓谴,因?yàn)檫@并不是這個(gè)測(cè)試方法所要測(cè)試的邏輯暮蹂。雖然說(shuō)隱式的假設(shè)data不為空不是完全正確,但是顯式去判斷這種無(wú)關(guān)緊要的東西會(huì)影響可讀性癌压。另外就算不寫這句話仰泻,如果data為null了測(cè)試同樣會(huì)拋錯(cuò)導(dǎo)致測(cè)試失敗,所以也不會(huì)影響測(cè)試結(jié)果滩届。

可讀性的code smell到此介紹結(jié)束集侯,下一章將介紹可維護(hù)性。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末帜消,一起剝皮案震驚了整個(gè)濱河市棠枉,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌泡挺,老刑警劉巖辈讶,帶你破解...
    沈念sama閱讀 216,496評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異娄猫,居然都是意外死亡贱除,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門媳溺,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)月幌,“玉大人,你說(shuō)我怎么就攤上這事悬蔽〕短桑” “怎么了?”我有些...
    開封第一講書人閱讀 162,632評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵屯阀,是天一觀的道長(zhǎng)缅帘。 經(jīng)常有香客問(wèn)我轴术,道長(zhǎng)难衰,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,180評(píng)論 1 292
  • 正文 為了忘掉前任逗栽,我火速辦了婚禮盖袭,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘彼宠。我一直安慰自己鳄虱,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評(píng)論 6 388
  • 文/花漫 我一把揭開白布凭峡。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪爪瓜。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,165評(píng)論 1 299
  • 那天系宫,我揣著相機(jī)與錄音,去河邊找鬼建车。 笑死扩借,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的缤至。 我是一名探鬼主播潮罪,決...
    沈念sama閱讀 40,052評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼领斥!你這毒婦竟也來(lái)了嫉到?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,910評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤戒突,失蹤者是張志新(化名)和其女友劉穎屯碴,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體膊存,經(jīng)...
    沈念sama閱讀 45,324評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡导而,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了隔崎。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片今艺。...
    茶點(diǎn)故事閱讀 39,711評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖爵卒,靈堂內(nèi)的尸體忽然破棺而出虚缎,到底是詐尸還是另有隱情,我是刑警寧澤钓株,帶...
    沈念sama閱讀 35,424評(píng)論 5 343
  • 正文 年R本政府宣布实牡,位于F島的核電站,受9級(jí)特大地震影響轴合,放射性物質(zhì)發(fā)生泄漏创坞。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評(píng)論 3 326
  • 文/蒙蒙 一受葛、第九天 我趴在偏房一處隱蔽的房頂上張望题涨。 院中可真熱鬧,春花似錦总滩、人聲如沸纲堵。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)席函。三九已至,卻和暖如春冈涧,著一層夾襖步出監(jiān)牢的瞬間茂附,已是汗流浹背蝌以。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留何之,地道東北人跟畅。 一個(gè)月前我還...
    沈念sama閱讀 47,722評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像溶推,于是被迫代替她去往敵國(guó)和親徊件。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評(píng)論 2 353

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