简体   繁体   中英

How to write data-driven tests using Hspec?

I'm learning Haskell, and being a good developer, writing unit tests as I go. I implemented various sorting algorithms, and corresponding tests. However, I feel that separate tests are redundant, because the input and output are not varying, only the algorithms used to sort the input are. Is there a way to create data-driven tests or data tables as possible in various other unit testing frameworks?

module RecursionSpec (main, spec) where

import Test.Hspec
import Recursion

main :: IO ()
main = hspec spec

spec :: Spec
spec = do
  let input = [3, 1, 5, 2, 4]
      output = [1, 2, 3, 4, 5]
  describe "bubblesort" $ do
    it ("sorts " ++ show input) $ do
      bubblesort input `shouldBe` output

  describe "mergesort" $ do
    it ("sorts " ++ show input) $ do
      mergesort input `shouldBe` output

  describe "quicksort" $ do
      it ("sorts " ++ show input) $ do
        quicksort input `shouldBe` output

Also, I get the following warning that I'd like to understand and eliminate.

warning: [-Wtype-defaults]
    • Defaulting the following constraints to type ‘Integer’
        (Show a0)
          arising from a use of ‘show’ at test/RecursionSpec.hs:14:21-30
        (Eq a0)
          arising from a use of ‘shouldBe’ at test/RecursionSpec.hs:15:7-40
        (Ord a0)
          arising from a use of ‘bubblesort’ at test/RecursionSpec.hs:15:7-22
        (Num a0)
          arising from the literal ‘1’ at test/RecursionSpec.hs:12:17
        (Num a0)
          arising from the literal ‘3’ at test/RecursionSpec.hs:11:16
    • In the second argument of ‘(++)’, namely ‘show input’
      In the first argument of ‘it’, namely ‘("sorts " ++ show input)’
      In the expression: it ("sorts " ++ show input)

You can define a higher-order function like:

describeSort :: Ord a => String -> ([a] -> [a]) -> [a] -> [a] -> SpecWith b
describeSort sortName sorter input output =
    describe sortName $ do
        it ("sorts " ++ show input) $ do
        sorter input `shouldBe` output

It's not data-driven, but it basically removes the boilerplate in this instance (I can't verify the syntax is exactly right, don't have a HSpec installation to hand).

Then you can define your tests as:

spec :: Spec
spec = do
    let input  = [3, 1, 5, 2, 4]
        output = [1, 2, 3, 4, 5]

    describeSort "bubblesort" bubblesort input output
    describeSort "mergesort"  mergeSort  input output
    describeSort "quicksort"  quickSort  input output

A more data-driven (property testing) testing framework specifically for Haskell is QuickCheck . It allows you to define "properties" obeyed by functions, and can then generate data to test these. For example a quick test of a sorting function could be written as:

quickCheck (\xl -> bubblesort xl == sort xl)

Where sort is the Data.List version and bubblesort is your implementation under test. QuickCheck will then generate 100 lists that fit the constraints (must be lists of Ord values), and report any errors encountered.


You can probably fix that warning by explicitly stating the type of your input and output s:

let input  = [3, 1, 5, 2, 4] :: [Integer]
    output = [1, 2, 3, 4, 5] :: [Integer]

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM