1

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)
sjakobi
  • 3,546
  • 1
  • 25
  • 43
Abhijit Sarkar
  • 21,927
  • 20
  • 110
  • 219

1 Answers1

3

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 outputs:

let input  = [3, 1, 5, 2, 4] :: [Integer]
    output = [1, 2, 3, 4, 5] :: [Integer]
hnefatl
  • 5,860
  • 2
  • 27
  • 49
  • Upvoted, but writing more code to implement what a unit testing framework should provide out of the box is not what I'm looking for. And specifying the type does get rid of the warning, but I'd like to understand why it appears in the first place. It's quite evident that `input` is `[Integer]` - what problem is the otherwise smart compiler having with that? – Abhijit Sarkar Dec 25 '17 at 10:30
  • You can look at `QuickCheck` to provide better data-driven stuff, it can generate random test inputs. It's what I use, personally. For the error, it's likely that the values could have type `Integer` or `Int`. – hnefatl Dec 25 '17 at 10:33
  • 1
    Thank you, I'll look at quickcheck, although it seems to be property-driven testing, a different approach. And `[Int]` works too for `input` and `output` - I'd forgotten about [Int vs Integer](https://stackoverflow.com/questions/3429291/haskell-int-and-integer). – Abhijit Sarkar Dec 25 '17 at 10:39