過去幾節課裡 我們花了些時間 談論過不同的測試方法 也談論過單元測試和整合測試的異同 我們也講解過如何使用RSpec 真正將你想測試的程式碼分開 也因為作業三和其他部份的關係 我們也研究過BDD 當中,我們基本上就是使用Cucumber把使用者的流程 轉變成整合測試和合格測試 (acceptance test) 你已經看到不同層次的測試 所以,這節課的目標基本上只是簡單的總結一下而已 就是說,我們站遠點綜觀看看 並將這些事情一起來看 基本上,這節課的內容將會涵蓋 課本上三到四個章節 我在這邊只是稍微提到而已 好了,有一個問題是常常出現的 我想你們也都會有這個問題 因為你們也有做作業 問題就是:「多少測試才夠呢?」 難過的是 如果你們在業界問這個問題 答案是非常簡單的 「關於這個問題,我們有產品的結案日期, 所以在結案日期前能做的測試 即足以。」 也就是有多少時間就做多少 這其實有點顛倒了 顯然不是很好 其實你們可以做的更好,對吧? 這邊有些靜態的測量 像是程式裡有多少程式碼 多少行測試用的程式碼等 另外,在業界並不罕見的是 針對測試完整的程式而言 測試用的程式碼的行數 往往是遠遠超過本來程式碼的行數的 甚至,倍增的量也是常見的 我想,即使甚至是 研究程式碼,甚至是習作等 1.5倍也非不合理 也就是說,測試用的程式碼 有應用程式碼的1.5倍 在很多實作的系統裡 就是說是真正需要測試的 測試程式碼對應用程式碼的比率,甚至比這個更高 所以也許可以用另一個較好的方式來問這個問題 就是,與其問「需要多少測試呢?」 不如問「我現在做的測試有多完整?」 「有多徹底?」 這個學期的後半部 Sen教授將會 對正規方法 (formal methods) 作一個簡略的介紹 並談論一下測試和除錯方法的最新進展 但是,對於這點,現在我們還是可以略提一二的 就是說,基於你們已經知道的,我們還是可以略提 一些有關測試涵蓋率 (test coverage) 的基本概念 我也許會說 事實上我們也一直說 正規方法在大的系統上基本上根本是沒用的 不過,我個人的觀點是 這個說法以前的確很對,但現在已經不太對了 我想,有許多特殊的場合裡 特別在測試和除錯方面 正規測試已經獲得了很大的進展 在這方面 Koushik Sen 是個領導先驅 所以,之後你們都有機會可以了解更多 不過現在我講的,是一些可以說是很實際的東西 就是測試涵蓋率的測量 (coverage measurement) 因為這就是實際情況 換句話說,假如有人要評估你 就是說在真正的工作時有人要評估你,這就是實際情況 那麼,有什麼基礎的東西呢? 讓我給你一個很簡單的類別 來講解有什麼不同的方法來量測 我們的測試可涵蓋多少程式範圍 這邊有些不同的程度差異 以不同的字彙來說的話 這不像是所有的程式部門都通用 但是有一套較通常使用的字彙 是我們的課本裡面用的 就是,我們談到 S0 意思是你呼叫每個方法一次 就是說,你呼叫 foo,然後呼叫 bar,就完成了 這就是 S0 coverage,不是怎麼的全盤 比較嚴謹的 S1 基本上就是,我們呼叫了每一個方法 而且是只要可以呼叫某個方法的地方都去呼叫那個方法 這什麼意思? 舉例來說 只呼叫 bar 不夠 你要先確定你一定會 在這裏呼叫 bar 至少一次 除此以外,還要確定 任何可以呼叫 bar 的外部函數都要呼叫 bar 一次 至於 SimpleCov 所測量的 C0 (你們當中已經設定好 SimpleCov 且實際做過的人啊) 基本上就是表示你執行所有的程式碼 在你的程式碼全部都做過一次 但這邊要小心一下 條件運算只被算一次而已 所以假如發生條件判斷例如執行了 "if" 只要程式碼分支有執行到 你已經執行了 "if" 陳述句 所以 C0 仍然只是涵蓋比較表層的範圍 但是我們將會看到 對 C0 涵蓋率的正確理解,其實是這樣的: 假如 C0 程度的涵蓋率你也測出了不好的涵蓋率 你的測試涵蓋率就真的是非常非常的差了 因為 你如果連這種簡單的表層測試也達不到 你的測試大概很有問題了 C1 則是更進一步 可以這麼說 必須把所有分支雙方向測試 所以當我們在執行 "if" 陳述句 就必須確保 "if x" 這個部分執行到 "if not x"這個部分也至少執行一次,才符合 C1 如果要再進一步,可以再加入 decision coverage 意思是:我們如果想要... 假如我們發現 "if" 陳述句 裡面包含不只一種陳述句 我們必須確保每個陳述句 都可以雙向評估 換句話說 如果 "if" 不執行 就要確保 fail 至少一次 因為 y 至少一次是 false , 又因為 z 是 false 任何陳述句 都是獨立的依條件改變結果 都必須能夠雙向測試 然後 有一件事很多人都希望做到 但沒有共識說這麼做到底有沒有意義 就是每個路徑都走過 顯然,這是有困難度的 因為所有條件都考慮會造成量呈指數倍數的增加 況且,通常都很難去評估 究竟是不是每個路徑都已經測試 針對這些困難的正規方法,是有的 它們可以讓你們知道洞在哪 但是最根本的是 在大部分的商業軟體公司裡 對於 C2 有多少價值的觀念 還不太一致 就是說,C2 比起 C0 或 C1 究竟有多少價值,還是沒有一個共識 所以,我認為,這個課程的用意是 讓你們了解 怎樣使用測試涵蓋率的資訊 SimpleCov 利用 Ruby 中的一些功能 讓你們可以做 C0 測試 也有很好的報表 給我們的資訊 大抵上而言是逐檔案,逐程式行的資訊 你可以看看你的涵蓋範圍 我覺得 這是個很好的開始 我們已經看了不同種類的測試後 往回退一步宏觀的看 我們實際看過的不同的測試 究竟有那幾種? 如果使用不同的測試 各自的缺點又是什麼? 我們已經看過單一類別和方法的程度 我們用 RSpec 大量使用了 mocking 和 stubbing 測試 比如,當我們模型中測試方法 這是單元測試例子 我們也完成其他非常像功能或模組測試 裡面有多個模組 所以如果我們有了 controller 得規格 我們看到就模擬 POST 的行為 但要記住 送出的 POST 要通過 routing subsystem 才到 controller controller 完成後會產出一個 view 事實上還有其他部分 會和 controller 一起運作 使得 controller 規格能通過 這是裡面的某個部分 我們在這邊有多個方法用到不只一個類別 但我們仍要注意 這只在系統中的一小部分 我們仍廣泛的用 mocking 以及 stubbing 把我們想要測試的行為獨立出來 在 Cucumber 層級的整合或系統測試 他們測試了程式的全部路徑 可能也觸及了相當多不同的模組 最小化使用 mocks 和 stubs 因為整合測試其中一個目標 是能確切的測試個區塊之間的相互影響 所以如果你不想用 stub 或控制這些相互影響 你想要讓系統執行它就會執行 如果這是在開發時 那麼要如何會比較這些不同的測試? 我們看看這邊有幾個不同的軸 其中之一是他們需要多長時間運行 RSpec 和 Cucumber 看似有較高的啟動時間 但如您所見 如果加上更多的 RSpec 測試 並在後臺執行自動測試 一旦 RSpec 開始 會執行 specs 相當快 而 Cucumber 需要很長時間 因為 Cucumber 觸發了整個應用程式 這學期末 我們將會看到讓 Cucumber 更慢 因為整個瀏覽器內部都要觸發 就像是傀儡操控 Firefox 所以可以測試 Javascript 程式 實際上我們做的 我們能和朋友們在 SourceLabs 就在雲端裡操作 真令人興奮 所以執行的快與慢 假如單元測試中發生了錯誤 通常很容易發生 要找出和追蹤該錯誤的來源 正是因為獨立測試 你已經 stub 所以無所謂 你只專注在感興趣的行為 如果已經做得很好但是測試時 有些錯誤的地方又發生錯誤 相反地 如果您用 Cucumber 差不多10個步驟 每一步都會觸擊到應用程式的一部分 這就會花很多時間才能找到那個 bug 所以需要去衡量 能夠涵蓋的錯誤範圍 如果你寫一個好的套件用來作單元測試和功能測試 您可以有很高的覆蓋範圍 您可以產生 SimpleCov 報告 也可以在檔案中的任何一行沒有側試過的程式碼 做會涵蓋到這行的測試 要找出如何改善您的覆蓋範圍 例如在 C0 能更輕鬆地完成與單元測試 而 Cucumber 測試會觸擊大量的程式碼 但你的方法就顯得很少 所以如果目標是提昇涵蓋範圍 就用工具來協助單元的層級 這樣可以更專心去了解程式那邊是不是還沒側過 然後可以寫相當針對性的測試針對這些部分 就像把這些部分放在一起做單元測試 因為這些分離出來測試適合用 mocks 來隔開你不在意的部分 定義上這意味著不是在測試介面 而像是在軟體裡有智慧一樣 很多有趣的錯誤 發生在各部分間的介面 (以下尚未處理) 並在類中或在方法內不進行排序的 那些是有點簡單的 bug,追查 與在另一個極端 你越集成測試走向極端 你應該少依賴類比考試 這一確切原因 現在我們看到的如果你在測試類似 也就是說,在面向服務的體系結構 你要在其上與遠端網站進行交互 你最終仍 不必做了相當大的嘲弄、 鉗 這樣,你不要依賴互聯網 為了使您的測試通過 但是,總體上說 你想盡可能多的類比考試,您可以刪除 而讓系統運行的方式,它將運行在現實生活中 所以,好消息是你 * 是 * 測試介面 * 但 * 當事情出錯在一個介面 因為您的解析度不是那麼好 可能需要很長時間弄清楚它是什麼 那麼,什麼是某種程度的高階位從折 是你真的不想依賴 太重上任何一種測試 他們為不同的目的和具體取決於服務上 你想行使您更多的介面 或你想提高你的細細微性覆蓋率 這會影響你如何發展你的測試套件 和你就會把它演進隨您的軟體 因此,我們使用一組特定的測試中的術語 它是術語到,通過和大 最常用的 Rails 社區 但有一些變化 [和] 你可能會聽到的一些其他術語 如果你去找一份工作的某個地方 你聽到有關變異測試 我們還沒有用過 這是一個有趣的想法,我認為,發明的 維與駕駛艙,有,排序的 軟體測試的通用書 這個想法是: 假設我向我的代碼提交蓄意的 bug 這不會迫使一些測試失敗嗎? 因為,如果我改變,你知道,"如果 x"到"如果不 x" 並沒有測試失敗,然後要麼我的思念一些報導 或我的應用程式是非常奇怪和不知何故不確定性 模糊測試,其中 Koushik 森可能更詳細地談 基本上,這就是"10,000 猴子在打字機 在您的代碼中扔隨機輸入" 有趣的是, 我們一直在做這些測試 基本上是精心編制來測試應用程式 它設計的方法 和這些,你知道,模糊測試 有關測試應用程式的方式是它 * 不是 *,可使用 所以,如果會發生什麼你扔巨大表單提交 如果你把控制字元放在您的表單中,將會發生什麼? 如果您反復提交同樣的事情,會發生什麼? Koushik 有一個統計資料, 微軟認定他們的 bug 的 20 % 使用一些變異的模糊化測試 和,約 25 % 常見的 Unix 命令列程式的 可以向崩潰 [當] 把侵略性模糊測試通過 定義使用覆蓋範圍是我們還沒有完成的事情 但它是另一個有趣的概念 我的程式在任何一點的設想是, 有一個地方,我的定義 — — 或賦值給一些變數 — — 然後還有下游的地方 大概我的去向消費的價值 — — 有人要使用此值 我已覆蓋每一對嗎? 換句話說,做有測試在每一對 定義一個變數和某個地方使用它 在我的測試套件的某些部分執行 它有時被稱為杜覆蓋範圍 其他條件,我認為不廣泛地不再使用 與白牌或與 glassbox 黑匣子黑匣子 大致上黑匣子測試是一個從寫入 事物的外部規範的角度來看 [例如:]"這是一個雜湊表 當我把放在一個鍵時應回到一個值 如果刪除了關鍵值不存在" 這是一個黑匣子測試,因為它並沒有說 任何有關該雜湊表如何實施的 和它不會嘗試狠抓落實 相應的白牌測試可能是: "我知道些什麼雜湊函數 和我要去刻意製造 我的測試用例中的雜湊鍵 這造成了大量的散列碰撞 以確保我正在測試的功能,部分" 現在,C0 測試覆蓋工具,像 SimpleCov 將顯示,如果你有的只是黑匣子測試 您可能會發現, 碰撞覆蓋代碼不是很多時候被撞傷 ,可能會提示您,然後說: "好的如果我真的要加強,— — 第一,如果我想要提高這些測試的覆蓋範圍 現在要寫白牌或 glassbox 測試 我要向內看看,請參閱執行什麼呢 並找到具體的方法 試圖打破邪惡的方式實施" 所以,我認為,測試是一種生活方式,正確嗎? 我們已經遠離的階段 "我們將建立整個事情,然後,我們將考驗" 和我們已經進入的階段 "我們測試我們看一下" 測試是真的更像是一種發展工具 像這麼多的開發工具 它的效力取決於 不管你雅致的方式使用上 所以,你會說:"好吧,讓我們看看 — — 我踢了輪胎 你知道,我解雇了流覽器,我試過幾件事情 (鮮花手)它工作起來 !部署它 !" 這顯然不是你想要的多一點騎士 通過的路上,我們發現的事情之一 與剛剛起步的此線上課程 當 60000 人參加課程 和那些人的 0.1%有問題 你就可以得到 60 電子郵件 必然結果是: 當您的網站使用的很多人 你沒有找到一些愚蠢的 bug 但是,可以通過測試來找 可以非常迅速生成 * 多 * 的痛 另一方面,你不想被教條和說 "呃,直到我們有 100%的覆蓋率和每個測試是綠色 我們絕對會不船舶" 這也是不健康 和測試品質 不一定相符的發言 除非你可以說 關於你的測試品質 只是因為你已經執行的每一條線 並不意味著你測試過的有趣的案件 所以,某處之間,你會說 "嗯,我們可以使用覆蓋工具來標識 undertested 或差測試代碼的部分 我們將使用它們作為指引 要排序的説明改善我們的總體信心水準" 但請記住,敏捷開發是擁抱變化 它的處理 更改的部分是事情會改變,將導致 你沒有預見到的 bug 和正確的反應就是: 能自在的測試工具 [這樣] 您可以快速找到這些 bug 寫一個轉載了這個 bug 的測試 然後進行測試綠色 然後你就會真的改變它 這意味著,您真的修復 bug 的方式是 如果您創建了一個正確的失敗的測試 重現這個 bug 然後回到了並修復該代碼 使這些測試通過 同樣的你不想說 "嗯,單元測試給你更好的覆蓋範圍 他們是更徹底和詳細 所以讓我們專注,我們所有的能量" 而不是 "哦,重點集成測試 因為他們更切合實際,對不對? 它們反映了客戶說他們想要什麼 因此,如果通過集成測試 通過定義我們滿足客戶需求" 同樣,這兩個極端都有點不健康 因為其中的每一個可以找出問題 這將由其他忽略了 所以,有好的結合 就是有點全部是所有關于 我想要留給你們的最後一件事是,我認為 方面的測試,是"與拓展署 所謂傳統的調試 — — 即,我們都有點做的方式 儘管我們說我們不要" 和我們都試圖右好轉嗎? 我們所有的在裝訂線 我們中的一些正仰望星空 努力提高我們的實踐 但現在住這 3 或 4 年我自己 和 — — 要老實 — 3 年前我沒做拓展署 我現在就做,因為我發現它是更好 這裡是我蒸餾的所以,我覺得很管用 很抱歉,顏色是有點怪異 但在左邊的列的表的 [它] 說"常規調試" 右側說"拓展署" 所以我用來編寫代碼的方法是什麼? 也許你們中的一些仍然這樣做 我寫了一大堆的行 也許我們只剩下幾十行代碼 我是 * 確保 * 他們正確 — — 我的意思是,我 * am * 一個好的程式師,正確嗎? 這並不難 我運行它 — — 它不起作用 好的調試器點燃 — — 將 printf 的開始 如果就已經使用 TDD 將我做什麼而是? 嗯,我會寫 * 幾 * 行代碼,第一次寫了一個測試 所以儘快把測試從紅色變為綠色 我知道我寫了代碼的工作 — — 或者至少,我原本想的行為的部件 行為的那些部分工作,因為我有一個測試 好的回傳統的調試: 我正在我的程式試圖找到的 bug 我開始把 printf 的無處不在 可以列印出來的東西值 方式是很有趣 當你想閱讀這些 日誌輸出的 500 線出來的 你會得到一個 Rails 應用程式中 試圖找到 * 你 * printf 的 你知道,"我知道我要做 — — 我要把放在 75 星號之前和之後 這將使可讀"(笑聲) 誰不要 — — 好吧,提高你的手,如果你不這樣做 ! 謝謝你的誠實。(笑聲)還行。 或 — — 或者我可以做其他事情,我可以說: 列印變數的值而不是 為什麼不寫檢查它的測試 在這樣的期望應該 我立即知道在明亮的紅色字母 如果不滿足這一期望 好的我對常規調試側: 我打破出殺手鐧: 我拉出 Ruby 調試器 我設置調試中斷點,和我現在開始 * 調整 * 說 "哦,讓我們看看,我要讓過去的 if 語句 所以要設置的那個東西 哦,我要調用該方法,所以我需要 to…" 不 ! 我 * 能 * 相反 — — 如果我要去做,不管怎麼說 — — 讓我們在檔中設置一些類比考試和存根,只是做 若要控制的代碼路徑,讓它就這樣的方式 現在,"好吧,肯定我已經固定它 ! 我給出的調試器,運行它所有再次 !" 並且,當然,9 次滿分,你並沒有解決它 或你的部分原因是固定的它,但你並沒有完全修復, 現在我再做這些手動都有 * 或 * 我已經有一堆的測試 和我可以自動他們重新 可以,如果有些人不和 "哦,我未修復整件事 沒問題,我只是去回來 !" 所以,底線是, 你知道,你 * 能 * 做它的左側 但在這兩種情況中使用相同的技術 唯一不同的是,在一個案例中你正在手動 這是無聊且容易出錯 在其他情況下你做一些更多的工作 但您可以使它自動和可重複 有,你知道,一些高信心 當您更改您的代碼中的東西 你不打破過去工作的東西 基本上,這是提高工作效率 所以你正在做的事情都一樣 但有一種"三角"額外工作 您正在使用你的努力在多高的杠杆 這樣說是我認為的種 TDD 是一件好事 這是真的,這不需要新的技能 它僅僅需要 [你] 重構您現有的技能 也嘗試過當我 — — 再次,誠實口供,正確嗎? — — 當我開始做這就像 "好吧,我會在滑軌上的課程教學 我真的應該集中測試 所以我回到我寫了一些代碼 那是 * 工作 * — — 你知道,那是體面的代碼 — — 然後我就開始努力為它編寫測試 它是 * 那麼痛苦 * 因為是可測試的方式並不編寫代碼 有各種各樣的交互 比如有嵌套的條件陳述式 如果你想要找出特定語句 並將它測試 — — 到觸發器測試 — — 只是該聲明 你會在您測試設置的東西量 發生 — — 請記住,當談到類比火車殘骸 — — 您必須設置所有此基礎結構 只是為了得到 * 一個 * 代碼行 和你這麼做了,你走 "Gawd,測試是真的不值得 ! 我寫了 20 條線的安裝 這樣我可以測試兩行在函數中我 !" 真正帶告訴你 — — 我現在意識到 — — 是你的函數是 * 壞 * 它是一個嚴重的書面的函數 它不是一個可測試函數 它有太多的移動部件 相關性的 * 可 * 被打破 我的功能中有無接縫 這使我單獨測試不同的行為 一旦你開始做測試第一次開發 因為你要的小塊寫你的測試 它使解決這個問題 所以,這一直是我的頓悟