Michael Pankov
Michael Pankov

Reputation: 3701

What is better approach to testing pure functions?

I'm new to Haskell. I'm testing a simple function with Test.Framework:

import Test.Framework (defaultMain, testGroup)
import Test.Framework.Providers.HUnit
import Test.Framework.Providers.QuickCheck2 (testProperty)

import Test.QuickCheck
import Test.HUnit

data Kind = Variable
          | Const
          | Polymorphic
    deriving (Show, Eq, Ord)

calculate :: Int -> Kind -> Float

calculate quantity Variable =
    (**2) . fromIntegral $ quantity

calculate _ Const =
    10

calculate quantity Polymorphic =
    if quantity <= 10 then
        10
    else
        (**2) . fromIntegral $ quantity

prop_ValuePositive quantity kind =
    calculate quantity kind
 >= 0.0

test_ValueVariable1 =
    calculate 1 Variable
    @?= (**2) 1

test_ValueVariable2 =
    calculate 10 Variable
    @?= (**2) 10

test_ValueConst1 =
    calculate 1 Const
    @?= 10

test_ValueConst2 =
    calculate 10 Const
    @?= 10

test_ValuePolymorphic1 =
    calculate 1 Polymorphic
    @?= 10

test_ValuePolymorphic2 =
    calculate 11 Polymorphic
    @?= (**2) 11

instance Test.QuickCheck.Arbitrary Kind where
    arbitrary = Test.QuickCheck.oneof(
        [return Variable,
         return Const,
         return Polymorphic])

main = defaultMain tests

tests = [
    testGroup "Value" [
        testProperty "Value is positive" prop_ValuePositive,
        testCase "Value is calculated right for Variable"
            test_ValueVariable1,
        testCase "Value is calculated right for Variable"
            test_ValueVariable2,
        testCase "Value is calculated right for Const"
            test_ValueConst1,
        testCase "Value is calculated right for Const"
            test_ValueConst2,
        testCase "Value is calculated right for Polymorphic"
            test_ValuePolymorphic1,
        testCase "Value is calculated right for Polymorphic"
            test_ValuePolymorphic2
        ]
    ]

What bothers me is that it's recommended to test pure functions with QuickCheck properties and impure functions with HUnit test cases. But that way, I would have to just repeat the function definition for each of 3 cases (Const, Variable and Polymorphic) in properties to test that the function returns what it's supposed to. That is too much duplication in my opinion:

prop_ValueVariable quantity Variable =
    calculate quantity Variable
 == ((**2) . fromIntegral $ quantity)

(and so on for all the cases of Kind)

In contrast, in the current code I test only one "obvious" property of function and provide some "sample points" for what the function should return, without actually duplicating the definition (in spirit of unit testing).

What is right approach?

  1. Use properties for testing of all aspects of this function and possibly duplicate its definition in tests
  2. Use properties only for, well, "properties" of what should be returned, but don't duplicate the definition and provide just some unit tests

Upvotes: 4

Views: 574

Answers (2)

Simon Hengel
Simon Hengel

Reputation: 421

That property based testing is for pure code and unit tests for impure code is a useful guideline, but not an absolute truth. Unit tests can be useful for pure code, too. I usually start with a unit test, e.g.

describe "parseMarkdown" $ do
  it "parses links" $ do
    parseMarkdown "[foo](http://foo.com/)" `shouldBe` Link "http://foo.com" "foo"

and then later abstract it to a property

  it "parses *arbitrary* links" $
    property $ \link@(Link url name) ->
      parseMarkdown "[" ++ name ++ "](" ++ url ++ ")" `shouldBe` link

But sometimes I just stick with the unit test because either (a) there is no good property or (b) a property does not increase the test coverage.

On the other hand properties can be useful for impure code, too. You e.g. may want to test your database abstraction with properties

describe "loadUser" $ do
  it "retrieves saved users from the database" $ do
    property $ \user -> do
      saveUser user >>= loadUser `shouldReturn` user

Upvotes: 3

Alexey Romanov
Alexey Romanov

Reputation: 170839

No, of course you are not supposed to duplicate the definition in this way. What would be the point? You may as well simplify the test to prop_trivial q k = calculate q k == calculate q k. The only case when I'd consider it is when you plan to change the way function is calculated in the future and want to check that it still returns the same result.

But if your unit tests are created by just putting values into the function definition and seeing what comes out, they are also not particularly useful either, for the same reason.

Upvotes: 0

Related Questions