haskell-notes

19.2 Тестирование с помощью QuickCheck

Мы проверили три случая, ещё три случая, ещё три случая, ожидаемый результат сходится с тем, что

возвращает нам интерпретатор, но можем ли мы быть уверены в том, что алгоритм действительно работает?

280 | Глава 19: Ориентируемся по карте

Для Haskell была разработана специальная библиотека тестирования QuickCheck, которая упрощает про-

цесс проверки программ. Мы можем сформулировать свойства, которые обязательно должны выполняться,

а QuickCheck сгенерирует случайный набор данных и проверит наши свойства на них.

Например в нашей задаче путь из A в B должен совпадать с перевёрнутым путём из B в A. Также все станции

в маршруте должны быть соседними. Давайте проверим эти свойства. Для этого нам нужно сформулировать

их в виде предикатов:

module Test where

import Control.Applicative

import Metro

prop1 :: Station -> Station -> Bool

prop1 a b = connect a b == (fmap reverse $ connect b a)

prop2 :: Station -> Station -> Bool

prop2 a b = maybe True (all (uncurry near) . pairs) $ connect a b

pairs :: [a] -> [(a, a)]

pairs xs = zip xs (drop 1 xs)

near :: Station -> Station -> Bool

near a b = a ‘elem‘ (fst distMetroMap b)

Установим QuickCheck:

cabal install QuickCheck

Теперь нам нужно подсказать QuickCheck как генерировать случайные значения типа Station. QuickCheck

тестирует функции, которые принимают значения из класса Arbitrary и возвращают Bool. Класс Arbitrary

отвечает за генерацию случайных значений.

Основной метод arbitrary возвращает генератор случайных значений:

class Arbitrary a where

arbitrary :: Gen a

Мы воспользуемся тем, что этот класс уже определён для многих стандартных типов. Кроме того класс

Gen явялется монадой. Мы сгенерируем случайное целое число и отобразим его в одну из станций. Сделать

это можно разными способами, мы начнём из одной станции и будем случайно блуждать по карте:

import Test.QuickCheck

instance Arbitrary Station where

arbitrary = ($ s0) . foldr (. ) id . fmap select ints

where ints = vector =<< choose (0, 100)

s0 = St Blue De

select :: Int -> Station -> Station

select i s = as !! mod i (length as)

where as = fst distMetroMap s

Мы воспользовались двумя функциями из бибилотеки QuickCheck. Это vector и choose. Первая строит

список случайных чисел заданной длины, а вторая выбирает случайное число из заданного диапазона. Теперь

мы можем протетстировать наши предикаты с помощью функции quickCheck:

*Test Prelude> quickCheck prop1

+++ OK, passed 100 tests.

*Test Prelude> quickCheck prop2

+++ OK, passed 100 tests.

*Test Prelude>

Свойства прошли тестирование на выборке из 100 комбинаций аргументов. Если нам интересно, мы

можем с помощью функции verboseCheck посмотреть на каких именно значениях проводилось тестирование:

Тестирование с помощью QuickCheck | 281

*Test Prelude> verboseCheck prop2

Passed:

St Black Kosmodrom

St Red UlBylichova

Passed:

St Black UlBylichova

St Orange Sever

Passed:

St Red Sirius

St Blue Krest

Если бы свойство не выполнилось, QuickCheck сообщил бы нам об этом и показал бы те элементы, для

которых свойство не выполнилось. Давайте составим такое свойство искусственно. Например, проверим,

находятся ли все станции на одной линии:

fakeProp :: Station -> Station -> Bool

fakeProp (St a _) (St b _) = a == b

Посмотрим, что на это скажет QuickCheck:

*Test Prelude> quickCheck fakeProp

*** Failed! Falsifiable (after 1 test):

St Green Sirius

St Blue Rodnik

По умолчанию QuickCheck проверит свойство сто раз. Для изменения этих настроек, мы можем восполь-

зоваться функцией quickCheckWith, дополнительным параметром она принимает значение типа Arg, которое

содержит параметры тестирования. Например протестируем первое свойство 500 раз:

*Test> quickCheckWith (stdArgs{ maxSuccess = 500 }) prop1

+++ OK, passed 500 tests.

Мы воспользовались стандартными настройками (stdArgs) и изменили один параметр.

Формирование тестовой выборки

Предположим, что мы уверены в правильной работе алгоритма для голубой и чёрной ветки метро, но

сомневаемся в остальных. Как раз для этого случая в QuickCheck предусмотрена функция a==> b. Это функ-

ция обозначает условную проверку, свойство b будет протестировано только в том случае, если свойство a

окажется верным. Иначе тестовые данные будут отброшены.

notBlueAndBlack a b = cond a && cond b ==> prop1 a b

where cond (St a _) = a /= Blue && a /= Black

Далее тестируем как обычно:

*Test> quickCheck notBlueAndBlack

+++ OK, passed 100 tests.

Также с помощью функции forAll мы можем подсказать QuickCheck на каких данных тестировать свой-

ство.

forAll :: (Show a, Testable prop) => Gen a -> (a -> prop) -> Property

Эта функция принимает генератор случайных значений и свойство, которое зависит от тех значений,

которые создаются этим генератором. К примеру, пусть нас интересуют только все возможные пути между

четырьмя станциями: (St Blue De), (St Red Lao), (St Green Til) и (St Orange Sever). Воспользуемся

функцией elements :: [a] -> Gen a, она как раз принимает список значений, и возвращает генератор,

который случайным образом выбирает любое значение из этого списка.

Страницы: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162