什么是测试纯函数的更好方法?

时间:2013-09-08 11:52:41

标签: unit-testing testing haskell

我是Haskell的新手。我正在使用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
        ]
    ]

让我感到困扰的是,建议使用QuickCheck属性测试纯函数,并使用HUnit测试用例测试函数。但是那样,我必须在属性中重复3个案例(ConstVariablePolymorphic)中每个案例的函数定义,以测试函数返回它应该返回的内容。在我看来,这太重复了:

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

(等Kind)的所有案例

相反,在当前的代码中,我只测试函数的一个“明显”属性,并为函数返回的内容提供一些“样本点”,而不是实际复制定义(单元测试的精神)。

什么是正确的方法?

  1. 使用属性测试此函数的所有方面,并可能在测试中复制其定义
  2. 仅将属性用于应返回内容的“属性”,但不要复制定义并仅提供一些单元测试

2 个答案:

答案 0 :(得分:3)

基于属性的测试用于纯代码,不纯代码的单元测试是一个有用的指导,但不是绝对的事实。单元测试也可用于纯代码。我通常从单元测试开始,例如

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

然后将其抽象为属性

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

但有时我只是坚持单元测试,因为(a)没有好的属性或(b)属性不会增加测试覆盖率。

另一方面,属性对于不纯的代码也很有用。你例如可能想用属性

测试数据库抽象
describe "loadUser" $ do
  it "retrieves saved users from the database" $ do
    property $ \user -> do
      saveUser user >>= loadUser `shouldReturn` user

答案 1 :(得分:0)

不,当然你不应该以这种方式复制定义。重点是什么?您也可以将测试简化为prop_trivial q k = calculate q k == calculate q k。我考虑的唯一情况是你计划在未来计算改变函数的方式,并想检查它是否仍然返回相同的结果。

但是,如果您的单元测试是通过将值放入函数定义并查看结果来创建的,那么它们也不是特别有用,出于同样的原因。