【问题标题】:Is there a utility for easily writing parametrized tests in Haskell?是否有一个实用程序可以在 Haskell 中轻松编写参数化测试?
【发布时间】:2019-01-11 14:38:12
【问题描述】:

一方面,我们有检查一个特定示例的自动化测试。另一方面,我们有基于属性的测试,例如 QuickCheck,我们提供属性,而框架提供示例,但我们可能必须解释如何生成示例。

在这两个极端之间的某个地方存在编写单个测试或属性并在多个用户提供的示例上运行它的可能性。 Haskell 测试环境中有哪些实用程序可以帮助编写此类参数化测试?

作为一个具体的例子,下面是在 Python 的pytest 中的实现方式。我想检查len 函数是否为各种输入提供了正确的输出。这可以通过编写一个测试来检查某些输入的长度是否符合测试人员的预期,并使用大量输入示例和相应的预期结果对测试进行参数化。

from pytest import mark

param = mark.parametrize

@param('input, expected',
       ((''  ,    0),
        ('a' ,    1),
        ('b' ,    1),
        ('ab',    2),
        ('xx',    3), # deliberate mistake
        ('xyz',   3),
        ('aaabc', 5)
       ))
def test_len(input, expected):
    assert len(input) == expected

产生这样的输出:

len_test.py::test_len[-0] PASSED                      [ 14%]
len_test.py::test_len[a-1] PASSED                     [ 28%]
len_test.py::test_len[b-1] PASSED                     [ 42%]
len_test.py::test_len[ab-2] PASSED                    [ 57%]
len_test.py::test_len[xx-3] FAILED                    [ 71%]  (appears red)
len_test.py::test_len[xyz-3] PASSED                   [ 85%]
len_test.py::test_len[aaabc-5] PASSED                 [100%]

========================= FAILURES ==========================
______________________ test_len[xx-3] _______________________
len_test.py:15: in test_len
    assert len(input) == expected
E   AssertionError: assert 2 == 3
E    +  where 2 = len('xx')
============ 1 failed, 6 passed in 0.04 seconds =============

Haskell 中有类似的东西吗?

【问题讨论】:

    标签: haskell testing


    【解决方案1】:

    这里是how I do it with HUnit

    adjustToBusinessHoursReturnsCorrectResult :: [Test]
    adjustToBusinessHoursReturnsCorrectResult = do
      (dt, expected) <-
        [
          (zt (2017, 10, 2) (6, 59,  4) 0, zt (2017, 10, 2) (9,  0,  0) 0),
          (zt (2017, 10, 2) (9, 42, 41) 0, zt (2017, 10, 2) (9, 42, 41) 0),
          (zt (2017, 10, 2) (19, 1, 32) 0, zt (2017, 10, 3) (9,  0,  0) 0)
        ]
      let actual = adjustToBusinessHours dt
      return $ ZT expected ~=? ZT actual
    

    我只是使用 do 符号和 list monad 来创建 HUnit 可以执行的测试列表 ([Test])。

    通常,我inline them,所以它们看起来像这样:

    main :: IO ()
    main = defaultMain $ hUnitTestToTests $ TestList [
      "adjustToBusinessHours returns correct result" ~: do
        (dt, expected) <-
          [
            (zt (2017, 10, 2) (6, 59,  4) 0, zt (2017, 10, 2) (9,  0,  0) 0),
            (zt (2017, 10, 2) (9, 42, 41) 0, zt (2017, 10, 2) (9, 42, 41) 0),
            (zt (2017, 10, 2) (19, 1, 32) 0, zt (2017, 10, 3) (9,  0,  0) 0)
          ]
        let actual = adjustToBusinessHours dt
        return $ ZT expected ~=? ZT actual
    
      ,
      "Composed adjust returns correct result" ~: do
        (dt, expected) <-
          [
            (zt (2017, 1, 31) ( 7, 45, 55)   2 , zt (2017, 2, 28) ( 7,  0,  0) 0),
            (zt (2017, 2,  6) (10,  3,  2)   1 , zt (2017, 3,  6) ( 9,  3,  2) 0),
            (zt (2017, 2,  9) ( 4, 20,  0)   0 , zt (2017, 3,  9) ( 9,  0,  0) 0),
            (zt (2017, 2, 12) (16,  2, 11)   0 , zt (2017, 3, 10) (16,  2, 11) 0),
            (zt (2017, 3, 14) (13, 48, 29) (-1), zt (2017, 4, 13) (14, 48, 29) 0)
          ]
        let adjustments =
              reverse [adjustToNextMonth, adjustToBusinessHours, adjustToDutchBankDay, adjustToUtc]
        let adjust = appEndo $ mconcat $ Endo <$> adjustments
    
        let actual = adjust dt
    
        return $ ZT expected ~=? ZT actual
      ]
    

    我确信还有其他方法可以实现这一目标,但我喜欢这种方法,因为它不需要任何额外的依赖项;它只是利用了语言的功能。

    【讨论】:

      【解决方案2】:

      请记住,QuickCheck 属性(通常)只是一个函数。我们可以使用 QuickCheck 为其提供参数,但我们也可以自己轻松地提供它们。

      prop_foo :: Foo -> Bar -> Bool
      
      prop1 = prop_foo (Foo 6) (Bar 9)
      prop2 = prop_foo (Foo 9) (Bar 2)
      ...
      

      此外,QuickCheck 允许提供自定义生成器,因此您可以控制测试数据的随机分布,但我很确定您也可以使用它来生成完全的数据-随机方式。

      然后是 HUnit,我很确定您可以在其中 fmap 对测试数据进行测试子例程以生成一组测试用例。事实上,我有时会用它来测试解析器[这不太适合随机测试]。我编写了一个函数,它接受一个输入字符串和一个预期的解析树,然后构造一个以输入字符串命名的 HUnit 测试用例,它在该字符串上运行解析器并断言输出与给定的期望匹配。

      真的,我想有几种方法可以做到这一点。

      (您知道 SmallCheck 吗?它的作用与 QuickCheck 基本相同,但生成输入系统地而不是随机生成。因此它会尝试所有可能的输入给定尺寸,然后移到下一个尺寸。可能不适合您的用例,但值得了解。)

      【讨论】:

      • 是的,我知道 SmallCheck,但这在这种情况下没有帮助,因为关键是在某些情况下(例如您提到的解析器)它确实更容易和更清晰有一些明确的例子。我相信清晰编写的测试是文档的重要组成部分,而且很多时候一组好的示例比属性更清楚地记录了一些东西。这并不是贬低基于属性的测试:这是两个互补的工具。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-09-15
      • 2020-11-15
      • 2016-07-08
      • 2010-09-26
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多