2012-03-13 3 views
14

필자는 테스트하고자하는 고차 함수를 가지고 있으며 테스트하고자하는 프로퍼티 중 하나는 전달되는 함수로 수행됩니다. 그림의 목적으로 여기에 인위적인 예제가 있습니다 :QuickCheck를 사용하여 고차 함수를 테스트하려면 어떻게해야합니까?

gen :: a -> ([a] -> [a]) -> ([a] -> Bool) -> a 

아이디어는 대략 예제 생성기입니다. 하나의 a으로 시작하여 [a]의 싱글 톤 목록을 만든 다음 술어가 중지하라고 지시 할 때까지 [a]의 새 목록을 만듭니다. 호출은 다음과 같습니다 여기

init :: a 
next :: [a] -> [a] 
stop :: [a] -> Bool 

이 속성을의

gen init next stop 

내가 테스트하고 싶습니다 :

gen init next stop에 대한 호출에서 gen 약속은 통과 결코 빈 목록은 next입니다.

QuickCheck을 사용하여이 속성을 테스트 할 수 있습니까? 그렇다면 어떻게 할 수 있습니까? 당신이 gen의 구현을 준 경우 도움이 될 동안

+5

다음과 같은 관련 속성이 있습니다. "비어 있지 않은 입력의 경우 '다음'은 비어 있지 않은 출력을 생성합니다." 언급 한 속성 대신이 속성을 테스트하거나 테스트 할 수도 있습니다. –

+1

@ JohnL 사실! 하지만 그것은'gen'이 아닌'next'의 속성이고'next'는 first-order이므로 테스트하는 법을 압니다. –

답변

10

, 나는 그것이 이런 식 추측입니다 :

gen :: a -> ([a] -> [a]) -> ([a] -> Bool) -> a 
gen init next stop = loop [init] 
    where 
    loop xs | stop xs = head xs 
      | otherwise = loop (next xs) 

테스트 할 속성은 next이 빈 목록을 제공하지 않습니다 것입니다 . 이것을 테스트하는 데 걸림돌이되는 것은 내부 루프 불변 값이 gen 안에 있는지 확인하려는 것이므로 외부에서 사용할 수 있어야합니다. 우리가이 정보를 반환 gen을 수정할 수 :

genWitness :: a -> ([a] -> [a]) -> ([a] -> Bool) -> (a,[[a]]) 
genWitness init next stop = loop [init] 
    where 
    loop xs | stop xs = (head xs,[xs]) 
      | otherwise = second (xs:) (loop (next xs)) 

우리는 Control.Arrow에서 second를 사용합니다. 원래 gen 쉽게 게으른 평가 genWitness:

gen' :: a -> ([a] -> [a]) -> ([a] -> Bool) -> a 
gen' init next stop = fst (genWitness init next stop) 

감사의 용어로 정의되어이 많은 오버 헤드 우리를 제공하지 않습니다. 속성으로 돌아 가기! QuickCheck에서 생성 된 기능을 표시하려면 모듈 Test.QuickCheck.Function을 사용합니다. 여기에 꼭 필요한 것은 아니지만 속성을 단조로 변경하는 것이 좋습니다. 을 단위 목록으로 만드는 대신 Int의 목록을 사용합니다.

prop_gen :: Int -> (Fun [Int] [Int]) -> (Fun [Int] Bool) -> Bool 
prop_gen init (Fun _ next) (Fun _ stop) = 
    let trace = snd (genWitness init next stop) 
    in all (not . null) trace 

우리는이 QuickCheck으로 실행 해 봅시다 : 우리가 지금 에게 재산 명시하자 ...

ghci> quickCheck prop_gen 

뭔가 루프 것을 네 물론 다음 목록에 gen 루프 stop 경우 next은 결코 True입니다!

prop_gen_prefix :: Int -> (Fun [Int] [Int]) -> (Fun [Int] Bool) -> Int -> Bool 
prop_gen_prefix init (Fun _ next) (Fun _ stop) prefix_length = 
    let trace = snd (genWitness init next stop) 
    in all (not . null) (take prefix_length trace) 

우리는 지금 빨리 AA 카운터 - 예를 얻을 : 두 번째 함수는 인수 next입니다

385 
{_->[]} 
{_->False} 
2 

을하고 있다면 우리는 대신에 대신 입력 추적 의 유한 접두사 살펴 봅시다 빈 목록, 을 반환하면 gen의 루프는 next에 빈 목록을 제공합니다.

이 질문에 대한 답변과 QuickCheck를 사용하여 고차원 함수를 테스트하는 방법에 대한 약간의 통찰력을 얻으시기 바랍니다.

+2

'gen'을'gen :: Monad m => a -> ([a] -> m [a]) -> ([a] -> m Bool) -> ma'로 바꾸면, 당신의 목격 코드를'next' (그리고'stop') 안에 넣고, 그런 로깅으로 구현을 신경 쓸 필요는 없습니다. – rampion

+0

와우. 나는'gen' 주위에 어떤 종류의 래퍼 (wrapper)를 넣기를 기대했지만, 나는이 여분의 쓰레기로 내 좋은 깨끗한'gen'을 망칠 필요가 있는지 확신하지 못한다. 나는 그걸 들여다 볼 것입니다. –

+0

@NormanRamsey : @ rampion의 제안은 모나드를 사용하여 작성한 다음 작성자 모나드를 사용하여 추적을 수행하면 너무 많은 쓰레기가 필요하지 않습니다. 그러면 '다음'은 입력을 작가 모나드에 써야합니다. – danr

4

이 점을 악용하는 것은 좋지 않지만 QuickCheck 이 예외를 throw하는 경우 기능을 수행하지 못합니다. 그래서, 테스트하기 위해서, 그냥 빈 경우에 대한 예외를 throw하는 함수를 제공하십시오. danr의 답변을 적응하기 :

import Test.QuickCheck 
import Test.QuickCheck.Function 
import Control.DeepSeq 

prop_gen :: Int -> (Fun [Int] [Int]) -> (Fun [Int] Bool) -> Bool 
prop_gen x (Fun _ next) (Fun _ stop) = gen x next' stop `deepseq` True 
    where next' [] = undefined 
     next' xs = next xs 

이 기술은 gen을 수정할 필요가 없습니다.

+0

이 문제는 하나의 문제가 있습니다 :'stop' 함수가 결코'True'를 반환하지 않으면, 그것은 영원히 반복 될 것입니다. QuickCheck에서 제공하는 함수 대신에, 사용자는 어떤 시점에서'True'를 리턴하도록 보장 된'stop'을 생성해야합니다. 'const True'와 같은 함수가 없으므로, 이것이 보장하기 어려울 수 있습니다. –

관련 문제