4

我是 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 种情况(和)中的每一种重复函数定义Const,以测试该函数是否返回了它应该返回的内容。在我看来,这是太多的重复:VariablePolymorphic

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

(对于 的所有情况,依此类推Kind

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

什么是正确的做法?

  1. 使用属性来测试这个函数的所有方面,并可能在测试中复制它的定义
  2. 仅将属性用于应该返回的“属性”,但不要重复定义并仅提供一些单元测试
4

2 回答 2

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
于 2013-09-08T18:29:02.103 回答
0

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

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

于 2013-09-08T13:42:39.883 回答