【问题标题】:What is better approach to testing pure functions?测试纯函数的更好方法是什么?
【发布时间】:2013-09-09 02:46:00
【问题描述】:

我是 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. 仅将属性用于应该返回的“属性”,但不要重复定义并仅提供一些单元测试

【问题讨论】:

    标签: unit-testing testing haskell


    【解决方案1】:

    基于属性的测试适用于纯代码,而单元测试适用于不纯代码是一个有用的指导方针,但不是绝对真理。单元测试对于纯代码也很有用。我通常从单元测试开始,例如

    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
    

    【讨论】:

    • 定义测试的语法是什么?找不到describe 函数的描述(原文如此!)
    【解决方案2】:

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

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

    【讨论】:

    • 我并没有完全“计划”改变它的计算方式,但是当你重构代码并且某种错误潜入时,测试的目的不是破坏吗?我也不太明白你第二段的意思。那么哪些测试有用呢?
    猜你喜欢
    • 2012-05-02
    • 1970-01-01
    • 1970-01-01
    • 2013-06-09
    • 1970-01-01
    • 1970-01-01
    • 2014-05-03
    • 2015-10-23
    • 1970-01-01
    相关资源
    最近更新 更多