Как отобразить причину неудачного свойства теста с помощью быстрой проверки?

Как лучше всего отображать причины неудачной проверки свойства при проверке с помощью QuickCheck?

Рассмотрим, например:

prop a b = res /= []
   where
      (res, reason) = checkCode a b

Тогда сеанс может выглядеть так:

> quickCheck prop
Falsifiable, after 48 tests:
42
23

Но для отладки было бы очень удобно показать причину сбоя в составе фальсифицируемого отчета quickCheck.

Я взломал это так:

prop a b = if res /= [] then traceShow reason False else True
   where
      (res, reason) = checkCode a b

Есть ли лучший/приятный или более быстрый способ сделать это?


person maxschlepzig    schedule 23.01.2011    source источник


Ответы (4)


Я предполагаю, что ваша переменная «причина» содержит какие-то специфичные для теста данные о том, что пошло не так. Вместо этого вы можете вернуть «Результат», который содержит как условия успеха/неудачи/недействительности, так и строку, объясняющую, что пошло не так. Свойства, возвращающие результаты, обрабатываются QuickCheck точно так же, как и свойства, возвращающие логическое значение.

(править) Вот так:

module QtTest where 

import Test.QuickCheck
import Test.QuickCheck.Property as P


-- Always return success
prop_one :: Integer -> P.Result
prop_one _ = MkResult (Just True) True "always succeeds" False [] []


-- Always return failure
prop_two :: Integer -> P.Result
prop_two n = MkResult (Just False) True ("always fails: n = " ++ show n) False [] []

Обратите внимание, что это тип «Результат», определенный в Test.QuickCheck.Property, который вам нужен.

Есть также некоторые комбинаторы, определенные в Test.QuickCheck.Property, которые помогают вам составить результат, а не вызывать конструктор напрямую, например

prop_three :: Integer -> Property
prop_three n = printTestCase ("always fails: n = " ++ show n) False

Я думаю, было бы лучше использовать их.

person Paul Johnson    schedule 23.01.2011
comment
Можете ли вы привести простой пример, как точно вернуть результат, чтобы в случае сбоя отображалась переменная причина (предположим, что это некоторая строка или отображаемое значение)? - person maxschlepzig; 25.01.2011
comment
Спасибо за обновления. Я был слишком зациклен на cse.chalmers.se/~rjmh/QuickCheck/manual .html и не смотрел актуальную и полную документацию по модулям hackage.haskell.org/packages/archive/QuickCheck/2.4.0.1/doc/ — кажется, что printTestCase — это недавнее добавление — quickCheck 2.1 не включает Это. - person maxschlepzig; 27.01.2011

Поскольку QuickCheck дает вам входные данные для функции, а тестируемый код чистый (так и есть, верно?), вы можете просто передать эти входные данные функции и получить результат. Это более гибко, потому что с этими входными данными вы также можете многократно тестировать исходную функцию с настройками, пока она не станет правильной.

person Edward Z. Yang    schedule 23.01.2011
comment
Ну, суть вопроса в удобстве. Успешное тестирование заключается в максимально возможной автоматизации. Необходимость открывать сеанс ghci и пересчитывать потенциально дорогостоящую функцию не имеет смысла. Я имею в виду, что QuickCheck уже предоставляет collect, classify, чтобы иметь возможность обогатить вывод непровалившихся тестов свойств. Такое обогащение является необязательным и совершенно не снижает гибкости. - person maxschlepzig; 23.01.2011
comment
Я имею в виду, что для этого есть и практическая причина: свойства QuickCheck могут быть сколь угодно сложными, поэтому не всегда очевидно, каков результат любого данного теста: вам нужно будет преобразовать свой код в форму, которая говорит: «Эй, вот промежуточные значения меня интересуют! Предполагая, что вы готовы сделать это переписывание, нет никаких причин, по которым вы не могли бы предоставить эту возможность, хотя в IIRC QuickCheck нет ничего для этой цели. - person Edward Z. Yang; 23.01.2011

Это работает так же, как ответ Пола Джонсона, но более лаконичен и устойчив к изменениям в MkResult:

import Test.QuickCheck.Property (succeeded, failed, reason)

prop a b =
  if res /= []
    then succeeded
    else failed { reason = reason }
   where
      (res, reason) = checkCode a b
person Roman Cheplyaka    schedule 29.05.2018
comment
Спасибо, это очень просто и удобно. Позволяет мне точно увидеть, что пошло не так, когда у меня есть большая настройка, прежде чем тестировать несколько свойств. - person unhammer; 11.12.2020

Это мое решение (я использую counterexample вместо printTestCase, так как более поздний вариант устарел):

(<?>) :: (Testable p) => p -> String -> Property
(<?>) = flip (Test.QuickCheck.counterexample . ("Extra Info: " ++))
infixl 2 <?>

Использование:

main :: IO ()
main = hspec $ do
  describe "math" $ do
    prop "sum-of-square-le-square-of-sum" $ do
      \(x :: Int) (y :: Int) ->
        x * x + y * y <= (x + y) * (x + y) <?> show (x * x, y * y, x + y)

Поэтому, когда тестовый пример терпит неудачу, вы можете увидеть что-то вроде:

   *** Failed! Falsifiable, Falsifiable (after 2 tests):
   1
   -1
   Extra Info: (1,1,0)

Вы также можете использовать <?> вместе с .&&., .||., === и ==> и т. д.:

  describe "math" $ do
    prop "sum-of-square-le-square-of-sum" $ do
      \(x :: Int) (y :: Int) ->
        x * x + y * y <= (x + y) * (x + y) <?> show (x * x, y * y, x + y) .||. (1==0) <?> "haha"
person luochen1990    schedule 28.11.2018