<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                # 第十一章:測試和質量保障 構建真實系統意味著我們要關心系統的質量控制,健壯性和正確性。有了正確的質量保障機制,良好編寫的代碼才能像一架精確的機器一樣,所有模塊都完成它們預期的任務,并且不會有模棱兩可的邊界情況。最后我們得到的將是不言自明,正確無疑的代碼——這樣的代碼往往能激發自信。 Haskell 有幾個工具用來構建這樣精確的系統。最明顯的一個,也是語言本身就內置的,是具有強大表達力的類型系統。它使得一些復雜的不變量(invariants)得到了靜態保證——絕無可能寫出違反這些約束條件的代碼。另外,純度和多態也促進了模塊化,易重構,易測試的代碼風格。這種類型的代碼通常不會出錯。 測試在保證代碼的正確性上起到了關鍵作用。Haskell 主要的測試機制是傳統的單元測試(通過 HUnit 庫)和由它衍生而來的更強機制:使用 Haskell 開源測試框架 QuickCheck 進行的基于類型的“性質”測試。基于性質的測試是一種層次較高的方法,它抽象出一些函數應該普遍滿足的不變量,真正的測試數據由測試庫為程序員產生。通過這種方法,我們可以用成百上千的測試來檢驗代碼,從而發現一些用其他方法無法發現的微妙的邊角情形(corner cases),而這對于手寫來說是不可能的。 在這章里,我們將會學習如何使用 QuickCheck 來建立不變量,然后重新審視之前章節開發的美觀打印器,并用 QuickCheck 對它進行測試。我們也會學習如何用 GHC 的內置代碼覆蓋工具 HPC 來指導測試過程。 ## QuickCheck: 基于類型的測試 為了大概了解基于性質的測試是如何工作的,我們從一個簡單的情形開始:你寫了一個排序算法,需要測試它的行為。 首先我們載入 QuickCheck 庫和其它依賴模塊: ~~~ -- file: ch11/QC-basics.hs import Test.QuickCheck import Data.List ~~~ 然后是我們想要測試的函數——一個自定義的排序過程: ~~~ -- file: ch11/QC-basics.hs qsort :: Ord a => [a] -> [a] qsort [] = [] qsort (x:xs) = qsort lhs ++ [x] ++ qsort rhs where lhs = filter (< x) xs rhs = filter (>= x) xs ~~~ 這是一個經典的 Haskell 排序實現:它可能不夠高效(因為不是原地排序),但它至少展示了函數式編程的優雅。現在,我們來檢查這個函數是否符合一個好排序算法應該符合的基本規則。很多純函數式代碼都有的一個很有用的不變量是*冪等*(idempotency)——應用一個函數兩次和一次效果應該相同。對于我們的排序過程,一個穩定的排序算法,這當然應該滿足,否則就真的出大錯了!這個不變量可以簡單地表示為如下性質: ~~~ -- file: ch11/QC-basics.hs prop_idempotent xs = qsort (qsort xs) == qsort xs ~~~ 依照 QuickCheck 的慣例,我們給測試性質加上 prop_ 前綴以和普通代碼區分。冪等性質可以簡單地用一個 Haskell 函數表示:對于任何已排序輸入,再次應用 qsort 結果必須相同。我們可以手動寫幾個例子來確保沒什么問題: [譯注,運行之前需要確保自己安裝了 QuickCheck 包,譯者使用的版本是2.8.1。] ~~~ ghci> prop_idempotent [] True ghci> prop_idempotent [1,1,1,1] True ghci> prop_idempotent [1..100] True ghci> prop_idempotent [1,5,2,1,2,0,9] True ~~~ 看起來不錯。但是,用手寫輸入數據非常無趣,并且違反了一個高效函數式程序員的道德法則:讓機器干活!為了使這個過程自動化,QuickCheck 內置了一組數據生成器用來生成 Haskell 所有的基本數據類型。QuickCheck 使用 Arbitrary 類型類來給(偽)隨機數據生成過程提供了一個統一接口,類型系統會具體決定使用哪個生成器。QuickCheck 通常會把數據生成過程隱藏起來,但我們可以手動運行生成器來看看 QuickCheck 生成的數據呈什么分布。例如,隨機生成一組布爾值: [譯注:本例子根據最新版本的 QuickCheck 庫做了改動。] ~~~ Prelude Test.QuickCheck.Gen Test.QuickCheck.Arbitrary> sample' arbitrary :: IO [Bool] [False,False,False,True,False,False,True,True,True,True,True] ~~~ QuickCheck 用這種方法產生測試數據,然后通過 quickCheck 函數把數據傳給我們要測試的性質。性質本身的類型決定了它使用哪個數據生成器。quickCheck 確保對于所有產生的測試數據,性質仍然成立。由于冪等測試對于列表元素類型是多態的,我們需要選擇一個特定的類型來產生測試數據,我們把它作為一個類型約束寫在性質上。運行測試的時候,只需調用 quickCheck 函數,并指定我們性質函數的類型即可(否則的話,列表值將會是沒什么意思的 () 類型): ~~~ *Main Test.QuickCheck> :type quickCheck quickCheck :: Testable prop => prop -> IO () *Main Test.QuickCheck> quickCheck (prop_idempotent :: [Integer] -> Bool) +++ OK, passed 100 tests. ~~~ 對于產生的100個不同列表,我們的性質都成立——太棒了!編寫測試的時候,查看為每個測試生成的實際數據常常會很有用。我們可以把 quickCheck 替換為它的兄弟函數 verboseCheck 來查看每個測試的(完整)輸出。現在,來看看我們的函數還可能滿足什么更復雜的性質。 ## 性質測試 好的庫通常都會包含一組彼此正交而又關聯的基本函數。我們可以使用 QuickCheck 來指定我們代碼中函數之間的關系,從而通過一組通過有用性質相互關聯的函數來提供一個好的庫接口。從這個角度來說,QuickCheck 扮演了 API “lint” 工具的角色:它確保我們的庫 API 能說的通。 列表排序函數的一些有趣性質把它和其它列表操作關聯起來。例如:已排序列表的第一個元素應該是輸入列表的最小元素。我們可以使用 List 庫的 minimum 函數來指出這個性質: ~~~ -- file: ch11/QC-basics.hs import Data.List prop_minimum xs = head (qsort xs) == minimum xs ~~~ 測試的時候出錯了: ~~~ *Main Test.QuickCheck> quickCheck (prop_minimum :: [Integer] -> Bool) *** Failed! Exception: 'Prelude.head: empty list' (after 1 test): [] ~~~ 當對一個空列表排序時性質不滿足了:對于空列表而言,head 和 minimum 沒有定義,正如它們的定義所示: ~~~ -- file: ch11/minimum.hs head :: [a] -> a head (x:_) = x head [] = error "Prelude.head: empty list" minimum :: (Ord a) => [a] -> a minimum [] = error "Prelude.minimum: empty list" minimum xs = foldl1 min xs ~~~ 因此這個性質只在非空列表上滿足。幸運的是,QuickCheck 內置了一套完整的性質編寫語言,使我們可以更精確地表述我們的不變量,排除那些我們不予考慮的值。對于空列表這個例子,我們可以這么說:*如果*列表非空,*那么*被排序列表的第一個元素是最小值。這是通過 (==>) 函數來實現的,它在測試性質之前將無效數據排除在外: ~~~ -- file: ch11/QC-basics.hs prop_minimum' xs = not (null xs) ==> head (qsort xs) == minimum xs ~~~ 結果非常清楚。通過把空列表排除在外,我們可以確定指定性質是成立的。 ~~~ *Main Test.QuickCheck> quickCheck (prop_minimum' :: [Integer] -> Property) +++ OK, passed 100 tests. ~~~ 注意到我們把性質的類型從 Bool 改成了更一般的 Property 類型(property 函數會在測試之前過濾出非空列表,而不僅是簡單地返回一個布爾常量了)。 再加上其它一些應該滿足的不變量,我們就可以完成排序函數的基本性質集了:輸出應該有序(每個元素應該小于等于它的后繼元素);輸出是輸入的排列(我們通過列表差異函數 (\\) 來檢測);被排序列表的最后一個元素應該是最大值;對于兩個不同列表的最小值,如果我們把兩個列表拼接并排序,這個值應該是第一個元素。這些性質可以表述如下: ~~~ -- file: ch11/QC-basics.hs prop_ordered xs = ordered (qsort xs) where ordered [] = True ordered [x] = True ordered (x:y:xs) = x <= y && ordered (y:xs) prop_permutation xs = permutation xs (qsort xs) where permutation xs ys = null (xs \\ ys) && null (ys \\ xs) prop_maximum xs = not (null xs) ==> last (qsort xs) == maximum xs prop_append xs ys = not (null xs) ==> not (null ys) ==> head (qsort (xs ++ ys)) == min (minimum xs) (minimum ys) ~~~ ## 利用模型進行測試 另一種增加代碼可信度的技術是利用模型實現進行測試。我們可以把我們的列表排序函數跟標準列表庫中的排序實現進行對比。如果它們行為相同,我們會有更多信心我們的代碼的正確的。 ~~~ -- file: ch11/QC-basics.hs prop_sort_model xs = sort xs == qsort xs ~~~ 這種基于模型的測試非常強大。開發人員經常會有一些正確但低效的參考實現或原型。他們可以保留這部分代碼來確保優化之后的生產代碼仍具有相同行為。通過構建大量這樣的測試并定期運行(例如每次提交),我們可以很容易地確保代碼仍然正確。大型的 Haskell 項目通常包含了跟項目本身大小可比的性質測試集,每次代碼改變都會進行成千上萬項不變量測試,保證了代碼行為跟預期一致。 ## 測試案例學習:美觀打印器 測試單個函數的自然性質是開發大型 Haskell 系統的基石。我們現在來看一個更復雜的案例:為第五章開發的美觀打印器編寫測試集。 ## 生成測試數據 美觀打印器是圍繞 Doc 而建的,它是一個代數數據類型,表示格式良好的文檔。 ~~~ -- file: ch11/Prettify2.hs data Doc = Empty | Char Char | Text String | Line | Concat Doc Doc | Union Doc Doc deriving (Show,Eq) ~~~ 這個庫本身是由一組函數構成的,這些函數負責構建和變換 Doc 類型的值,最后再把它們轉換成字符串。 QuickCheck 鼓勵這樣一種測試方式:開發人員指定一些不變量,它們對于任何代碼接受的輸入都成立。為了測試美觀打印庫,我們首先需要一個輸入數據源。我們可以利用 QuickCheck 通過 Arbitrary 類型類提供的一套用來生成隨機數據的組合子集。Arbitrary 類型類提供了 arbitrary 函數來給每種類型生成數據,我們可以利用它來給自定義數據類型寫數據生成器。 ~~~ -- file: ch11/Arbitrary.hs import Test.QuickCheck.Arbitrary import Test.QuickCheck.Gen class Arbitrary a where arbitrary :: Gen a ~~~ 有一點需要注意,函數的類型簽名表明生成器運行在 Gen 環境中。它是一個簡單的狀態傳遞 monad,用來隱藏貫穿于代碼中的隨機數字生成器的狀態。稍后的章節會更加細致地研究 monads,現在只要知道,由于 Gen 被定義為一個 monad,我們可以使用 do 語法來定義新生成器來訪問隱式的隨機數字源。Arbitrary 類型類提供了一組可以生成隨機值的函數,我們可以把它們組合起來構建出我們所關心的類型的數據結構,以便給我們的自定義類型寫生成器。一些關鍵函數的類型如下: ~~~ -- file: ch11/Arbitrary.hs elements :: [a] -> Gen a choose :: Random a => (a, a) -> Gen a oneof :: [Gen a] -> Gen a ~~~ elements 函數接受一個列表,返回這個列表的隨機值生成器。我們稍后再用 choose 和 oneof。有了 elements,我們就可以開始給一些簡單的數據類型寫生成器了。例如,如果我們給三元邏輯定義了一個新數據類型: ~~~ -- file: ch11/Arbitrary.hs data Ternary = Yes | No | Unknown deriving (Eq,Show) ~~~ 我們可以給 Ternary 類型實現 Arbitrary 實例:只要實現 arbitrary 即可,它從所有可能的 Ternary 類型值中隨機選出一些來: ~~~ -- file: ch11/Arbitrary.hs instance Arbitrary Ternary where arbitrary = elements [Yes, No, Unknown] ~~~ 另一種生成數據的方案是生成 Haskell 基本類型數據,然后把它們映射成我們感興趣的類型。在寫 Ternary 實例的時候,我們可以用 choose 生成0到2的整數值,然后把它們映射為 Ternary 值。 ~~~ -- file: ch11/Arbitrary2.hs instance Arbitrary Ternary where arbitrary = do n <- choose (0, 2) :: Gen Int return $ case n of 0 -> Yes 1 -> No _ -> Unknown ~~~ 對于簡單的*和*類型,這種方法非常奏效,因為整數可以很好地映射到數據類型的構造器上。對于*積*類型(如結構體和元組),我們首先得把積的不同部分分別生成(對于嵌套類型遞歸地生成),然后再把他們組合起來。例如,生成隨機序對: ~~~ -- file: ch11/Arbitrary.hs instance (Arbitrary a, Arbitrary b) => Arbitrary (a, b) where arbitrary = do x <- arbitrary y <- arbitrary return (x, y) ~~~ 現在我們寫個生成器來生成 Doc 類型所有不同的變種。我們把問題分解,首先先隨機生成一個構造器,然后根據結果再隨機生成參數。最復雜的是 union 和 concatenation 這兩種情形。 [譯注,作者在此處解釋并實現了 Char 的 Arbitrary 實例。但由于最新 QuickCheck 已經包含此實例,故此處略去相關內容。] 現在我們可以開始給 Doc 寫實例了。只要枚舉構造器,再把參數填進去即可。我們用一個隨機整數來表示生成哪種形式的 Doc,然后再根據結果分派。生成 concat 和 union 的 Doc 值時,我們只需要遞歸調用 arbitrary 即可,類型推導會決定使用哪個 Arbitrary 實例: ~~~ -- file: ch11/QC.hs instance Arbitrary Doc where arbitrary = do n <- choose (1,6) :: Gen Int case n of 1 -> return Empty 2 -> do x <- arbitrary return (Char x) 3 -> do x <- arbitrary return (Text x) 4 -> return Line 5 -> do x <- arbitrary y <- arbitrary return (Concat x y) 6 -> do x <- arbitrary y <- arbitrary return (Union x y) ~~~ 看起來很直觀。我們可以用 oneof 函數來化簡它。我們之前見到過 oneof 的類型,它從列表中選擇一個生成器(我們也可以用 monadic 組合子 liftM 來避免命名中間結果): ~~~ -- file: ch11/QC.hs instance Arbitrary Doc where arbitrary = oneof [ return Empty , liftM Char arbitrary , liftM Text arbitrary , return Line , liftM2 Concat arbitrary arbitrary , liftM2 Union arbitrary arbitrary ] ~~~ 后者更簡潔。我們可以試著生成一些隨機文檔,確保沒什么問題。 ~~~ *QC Test.QuickCheck> sample' (arbitrary::Gen Doc) [Text "",Concat (Char '\157') Line,Char '\NAK',Concat (Text "A\b") Empty, Union Empty (Text "4\146~\210"),Line,Union Line Line, Concat Empty (Text "|m \DC4-\DLE*3\DC3\186"),Char '-', Union (Union Line (Text "T\141\167\&3\233\163\&5\STX\164\145zI")) (Char '~'),Line] ~~~ 從輸出的結果里,我們既看到了簡單,基本的文檔,也看到了相對復雜的嵌套文檔。每次測試時我們都會隨機生成成百上千的隨機文檔,他們應該可以很好地覆蓋各種情形。現在我們可以開始給我們的文檔函數寫一些通用性質了。 ## 測試文檔構建 文檔有兩個基本函數:一個是空文檔常量 Empty,另一個是拼接函數。它們的類型是: ~~~ -- file: ch11/Prettify2.hs empty :: Doc (<>) :: Doc -> Doc -> Doc ~~~ 兩個函數合起來有一個不錯的性質:將空列表拼接在(無論是左拼接還是右拼接)另一個列表上,這個列表保持不變。我們可以將這個不變量表述為如下性質: ~~~ -- file: ch11/QC.hs prop_empty_id x = empty <> x == x && x <> empty == x ~~~ 運行測試,確保性質成立: ~~~ *QC Test.QuickCheck> quickCheck prop_empty_id +++ OK, passed 100 tests. ~~~ 可以把 quickCheck 替換成 verboseCheck 來看看實際測試時用的是哪些文檔。從輸出可以看到,簡單和復雜的情形都覆蓋到了。如果需要的話,我們還可以進一步優化數據生成器來控制不同類型數據的比例。 其它 API 函數也很簡單,可以用性質來完全描述它們的行為。這樣做使得我們可以對函數的行為維護一個外部的,可檢查的描述以確保之后的修改不會破壞這些基本不變量: ~~~ -- file: ch11/QC.hs prop_char c = char c == Char c prop_text s = text s == if null s then Empty else Text s prop_line = line == Line prop_double d = double d == text (show d) ~~~ 這些性質足以測試基本的文檔結構了。測試庫的剩余部分還要更多工作。 ## 以列表為模型 高階函數是可復用編程的基本膠水,我們的美觀打印庫也不例外——我們自定義了 fold 函數,用來在內部實現文檔拼接和在文檔塊之間加分隔符。fold 函數接受一個文檔列表,并借助一個合并方程(combining function)把它們粘合在一起。 ~~~ -- file: ch11/Prettify2.hs fold :: (Doc -> Doc -> Doc) -> [Doc] -> Doc fold f = foldr f empty ~~~ 我們可以很容易地給某個特定 fold 實例寫測試。例如,橫向拼接(Horizontal concatenation)就可以簡單地利用列表中的參考實現來測試。 ~~~ -- file: ch11/QC.hs prop_hcat xs = hcat xs == glue xs where glue [] = empty glue (d:ds) = d <> glue ds ~~~ punctuate 也類似,插入標點類似于列表的 interspersion 操作(intersperse 這個函數來自于 Data.List,它把一個元素插在列表元素之間): ~~~ -- file: ch11/QC.hs prop_punctuate s xs = punctuate s xs == intersperse s xs ~~~ 看起來不錯,運行起來卻出了問題: ~~~ *QC Test.QuickCheck> quickCheck prop_punctuate *** Failed! Falsifiable (after 5 tests and 1 shrink): Empty [Text "",Text "E"] ~~~ 美觀打印庫優化了冗余的空文檔,然而模型實現卻沒有,所以我們得讓模型匹配實際情況。首先,我們可以把分隔符插入文檔,然后再用一個循環去掉當中的 Empty 文檔,就像這樣: ~~~ -- file: ch11/QC.hs prop_punctuate' s xs = punctuate s xs == combine (intersperse s xs) where combine [] = [] combine [x] = [x] combine (x:Empty:ys) = x : combine ys combine (Empty:y:ys) = y : combine ys combine (x:y:ys) = x `Concat` y : combine ys ~~~ 在 **ghci** 里運行,確保結果是正確的。測試框架發現代碼中的錯誤讓人感到欣慰——因為這正是我們追求的。 ~~~ *QC Test.QuickCheck> quickCheck prop_punctuate' +++ OK, passed 100 tests. ~~~ ## 完成測試框架 [譯注:為了匹配最新版本的 QuickCheck,本節在原文基礎上做了較大改動。讀者可自行參考原文,對比閱讀。] 我們可以把這些測試單獨放在一個文件中,然后用 QuickCheck 的驅動函數運行它們。這樣的函數有很多,包括一些復雜的并行驅動函數。我們在這里使用 quickCheckWithResult 函數。我們只需提供一些測試參數,然后列出我們想要測試的函數即可: ~~~ -- file: ch11/Run.hs module Main where import QC import Test.QuickCheck anal :: Args anal = Args { replay = Nothing , maxSuccess = 1000 , maxDiscardRatio = 1 , maxSize = 1000 , chatty = True } minimal :: Args minimal = Args { replay = Nothing , maxSuccess = 200 , maxDiscardRatio = 1 , maxSize = 200 , chatty = True } runTests :: Args -> IO () runTests args = do f prop_empty_id "empty_id ok?" f prop_char "char ok?" f prop_text "text ok?" f prop_line "line ok?" f prop_double "double ok?" f prop_hcat "hcat ok?" f prop_punctuate' "punctuate ok?" where f prop str = do putStrLn str quickCheckWithResult args prop return () main :: IO () main = do putStrLn "Choose test depth" putStrLn "1. Anal" putStrLn "2. Minimal" depth <- readLn if depth == 1 then runTests anal else runTests minimal ~~~ [譯注:此代碼出處為原文下Charlie Harvey的評論。] 我們把這些代碼放在一個單獨的腳本中,聲明的實例和性質也有自己單獨的文件,它們庫的源文件完全分開。這在庫項目中非常常見,通常在這些項目中測試都會和庫本身分開,測試通過模塊系統載入庫。 這時候可以編譯并運行測試腳本了: ~~~ $ ghc --make Run.hs [1 of 3] Compiling Prettify2 ( Prettify2.hs, Prettify2.o ) [2 of 3] Compiling QC ( QC.hs, QC.o ) [3 of 3] Compiling Main ( Run.hs, Run.o ) Linking Run ... $ ./Run Choose test depth 1. Anal 2. Minimal 2 empty_id ok? +++ OK, passed 200 tests. char ok? +++ OK, passed 200 tests. text ok? +++ OK, passed 200 tests. line ok? +++ OK, passed 1 tests. double ok? +++ OK, passed 200 tests. hcat ok? +++ OK, passed 200 tests. punctuate ok? +++ OK, passed 200 tests. ~~~ 一共產生了1201個測試,很不錯。增加測試深度很容易,但為了了解代碼究竟被測試的怎樣,我們應該使用內置的代碼覆蓋率工具 HPC,它可以精確地告訴我們發生了什么。 ## 用 HPC 衡量測試覆蓋率 HPC(Haskell Program Coverage) 是一個編譯器擴展,用來觀察程序運行時哪一部分的代碼被真正執行了。這在測試時非常有用,它讓我們精確地觀察哪些函數,分支以及表達式被求值了。我們可以輕易得到被測試代碼的百分比。HPC 的內置工具可以產生關于程序覆蓋率的圖表,方便我們找到測試集的缺陷。 在編譯測試代碼時,我們只需在命令行加上 -fhpc 選項,即可得到測試覆蓋率數據。 ~~~ $ ghc -fhpc Run.hs --make ~~~ 正常運行測試: ~~~ $ ./Run ~~~ 測試運行時,程序運行的細節被寫入當前目錄下的 .tix 和 .mix 文件。之后,命令行工具 hpc 用這些文件來展示各種統計數據,解釋發生了什么。最基本的交互是通過文字。首先,我們可以在 hpc 命令中加上 report 選項來得到一個測試覆蓋率的摘要。我們會把測試程序排除在外(使用 --exclude 選項),這樣就能把注意力集中在美觀打印庫上了。在命令行中輸入以下命令: ~~~ $ hpc report Run --exclude=Main --exclude=QC 93% expressions used (30/32) 100% boolean coverage (0/0) 100% guards (0/0) 100% 'if' conditions (0/0) 100% qualifiers (0/0) 100% alternatives used (8/8) 100% local declarations used (0/0) 66% top-level declarations used (10/15) ~~~ [譯注:報告結果可能因人而異。] 在最后一行我們看到,測試時有66%的頂層定義被求值。對于第一次嘗試來說,已經是很不錯的結果了。隨著被測試函數的增加,這個數字還會提升。對于快速了解結果來說文字版本的結果還不錯,但為了真正了解發生了什么,最好還是看看被標記后的結果(marked up output)。用 markup 選項可以生成: ~~~ $hpc markup Run --exclude=Main --exclude=QC ~~~ 它會對每一個 Haskell 源文件產生一個 html 文件,再加上一些索引文件。在瀏覽器中打開 hpc_index.html,我們可以看到一些非常漂亮的代碼覆蓋率圖表: ![../_images/hpc-round1.png](https://box.kancloud.cn/2015-09-04_55e90c4210c1b.png) 還不錯。打開 Prettify2.hs.html 可以看到程序的源代碼,其中未被測試的代碼用黃色粗體標記,被執行的代碼用粗體標記。 ![../_images/markup.png](https://box.kancloud.cn/2015-09-04_55e90c421c22a.png) 我們沒測 Monoid 實例,還有一些復雜函數也沒測。HPC 不會說謊。我們來給 Monoid 類型類實例加個測試,這個類型類支持拼接元素和返回空元素: ~~~ -- file: ch11/QC.hs prop_mempty_id x = mempty `mappend` x == x && x `mappend` mempty == (x :: Doc) ~~~ 在 **ghci** 里檢查確保正確: ~~~ *QC Test.QuickCheck> quickCheck prop_mempty_id +++ OK, passed 100 tests. ~~~ 我們現在可以重新編譯并運行測試了。確保舊的 .tix 被刪除,否則當 HPC 試圖合并兩次測試數據時會報錯: ~~~ $ ghc -fhpc Run.hs --make -fforce-recomp [1 of 3] Compiling Prettify2 ( Prettify2.hs, Prettify2.o ) [2 of 3] Compiling QC ( QC.hs, QC.o ) [3 of 3] Compiling Main ( Run.hs, Run.o ) Linking Run ... $ ./Run in module 'Main' Hpc failure: module mismatch with .tix/.mix file hash number (perhaps remove Run.tix file?) $rm Run.tix $./Run Choose test depth 1. Anal 2. Minimal 2 empty_id ok? +++ OK, passed 200 tests. char ok? +++ OK, passed 200 tests. text ok? +++ OK, passed 200 tests. line ok? +++ OK, passed 1 tests. double ok? +++ OK, passed 200 tests. hcat ok? +++ OK, passed 200 tests. punctuate ok? +++ OK, passed 200 tests. prop_mempty_id ok? +++ OK, passed 200 tests. ~~~ 測試用例又多了兩百個,我們的代碼覆蓋率也提高到了80%: ![../_images/hpc-round2.png](https://box.kancloud.cn/2015-09-04_55e90c422725f.png) HPC 確保我們在測試時誠實,因為任何沒有被覆蓋到的代碼都會被標記出來。特別地,它確保程序員考慮到各種錯誤情形,狀況不明朗的復雜分支,以及各式各樣的代碼。有了 QuickCheck 這樣全面的測試生成系統,測試變得非常有意義,也成了 Haskell 開發的核心。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看