2013-06-19 9 views
2

Этот вопрос берет начало, где Haskell QuickCheck best practices (especially when testing type classes) остановился.Определение набора тестов для класса

У меня есть класс и множество реализаций этого класса. Что-то вроде этого:

import Test.QuickCheck 
import Control.Applicative 
import Test.Framework 
import Test.Framework.Providers.QuickCheck2 

class C c where 
    f :: c -> Int 

data A = A Int deriving Show 

instance C A where 
    f (A a) = 2*a 

data B = B Int deriving Show 

instance C B where 
    f (B b) = 2*b 

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

prop_f_is_even :: C c => c -> Property 
prop_f_is_even x = property $ even (f x) 

Я хочу проверить это свойство для каждой из реализаций. Я могу сделать что-то подобное. (Я использую Test.Framework.)

instance Arbitrary A where 
    arbitrary = A <$> arbitrary 

instance Arbitrary B where 
    arbitrary = B <$> arbitrary 

test :: Test 
test = testGroup "Whole buncha tests" 
    [ 
    testProperty "prop_f_is_even - A" (prop_f_is_even :: A -> Property), 
    testProperty "prop_f_is_even - B" (prop_f_is_even :: B -> Property) 
    -- continue on for all combinations of properties and implementations 
    ] 

Но в моем случае, у меня есть десятки свойств, чтобы проверить, и десяток или около того классов, так что подход к ошибкам, и хлопот. (частая ошибка я сделать это, чтобы вырезать и вставить тесты, но забудьте изменить имя типа, так что я в конечном итоге тестирует два раза для этого свойства, не испытывая B.)

У меня есть решение, которое Я опубликую ниже, если кто-то найдет это полезным.

ответ

3

Это мое решение.

cProperties :: C t => String -> [(String, t -> Property)] 
cProperties s = 
    [ 
    ("prop_f_is_even: " ++ s, prop_f_is_even) 
    -- plus any other tests that instances of C should satisfy 
    ] 

makeTests :: (Arbitrary t, Show t) => [(String, t -> Property)] -> [Test] 
makeTests ts = map (\(s,t) -> testProperty s t) ts 

aProperties :: [(String, A -> Property)] 
aProperties = cProperties "A" 

bProperties :: [(String, B -> Property)] 
bProperties = cProperties "B" 

easierTest :: Test 
easierTest = 
    testGroup "tests" (makeTests aProperties ++ makeTests bProperties) 

При таком подходе, если я хочу добавить еще одно свойство, что все экземпляры C должны удовлетворять, я просто добавить его в cProperties. И если я создам другой экземпляр C, назовите его D, тогда я определяю dProperties аналогично aProperties и bProperties, а затем обновляем easierTest.


EDIT: Одним из недостатков этого подхода заключается в том, что все тесты в cProperties должны иметь тип подписи t -> Property. Я сам не нашел это препятствием, потому что в тех случаях, когда я применяю этот метод, у меня уже - по несвязанным причинам - определен тип, который охватывает все данные для теста.

Другим недостатком является то, что в GHCI, я больше не могу типа, например:

quickCheck prop_f_is_even 

Теперь я должен ввести что-то вроде этого:

quickCheck (prop_f_is_even :: A -> Property) 
Смежные вопросы