Reputation: 24593
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)
Upvotes: 1
Views: 664
Reputation: 6037
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]
Upvotes: 3