۱۴ - ۷تمرینهای فصل
حالا وقتش رسیده که تستهای خودتون رو بنویسین. برای بیشترِ تمرینهای کتاب میشه تست نوشت، اما اینکه از hspec
استفاده کنین یا QuickCheck
بستگی به این داره که میخواین چه چیزی رو تست کنین. ما خواستیم یه کم کار رو براتون ساده کنیم، به همین خاطر تو این تمرینها گفتیم از کدوم استفاده کنین. ولی طبق معمول، شما رو تشویق میکنیم واسه خودتون آزمایش کنین.
تأییدِ اعداد به لغات
تمرینِ "اعداد به لغات" در فصلِ توابعِ بازگشتی خاطرتون هست؟ الان برای توابعی که اونجا نوشتین تست مینویسین.
module WordNumberTest where
import Test.Hspec
import WordNumber
(digitToWord, digits, wordNumber)
main :: IO ()
main = hspec $ do
describe "digitToWords" $ do
it "returns zero for 0" $ do
digitToWord 0 `shouldBe` "zero"
it "returns one for 1" $ do
print "???"
describe "digits" $ do
it "returns [1] for 1" $ do
digits 1 `shouldBe` [1]
it "returns [1, 0, 0] for 100" $ do
print "???"
describe "wordNumber" $ do
it "one-zero-zero given 100" $ do
wordNumber 100
`shouldBe` "one-zero-zero"
it "nine-zero-zero-one for 9001" $ do
print "???"
بجای اون چاپهای علامت سؤال، تستهای صحیح رو بنویسین. اگه تستهای دیگه هم به ذهنتون میرسن اضافه کنین.
استفاده از QuickCheck
مشخصههای چندتا حساب ِ ساده رو تست کنین:
۱.
-- برای تابع زیر
half x = x / 2
-- باید برقرار باشه (property) این مشخصه
halfIdentity = (*2) . half
۲.
import Data.List (sort)
-- رو بهش اعمال sort برای هر لیستی که
-- میکنین، این مشخصه باید برقرار باشه
listOrdered :: (Ord a) => [a] -> Bool
listOrdered xs =
snd $ foldr go (Nothing, True) xs
where go _ status@(_, False) = status
go y (Nothing, t) = (Just y, t)
go y (Just x, t) = (Just y, x >= y)
۳.
حالا جابجاییپذیری و شرکتپذیری ِ جمع رو تست میکنیم:
plusAssociative x y z =
x + (y + z) == (x + y) + z
plusCommutative x y =
x + y == y + x
۴.
حالا همون کار رو برای ضرب انجام بدین.
۵.
در یکی از فصلهای اول کتاب، قوانینی برای روابطِ بینِ quot
و rem
، و div
و mod
نشون دادیم. براشون تستِ QuickCheck
بنویسین.
(quot x y)*y + (rem x y) == x
(div x y)*y + (mod x y) == x
۶.
آیا (^)
شرکتپذیری داره؟ جابجاییپذیری چطور؟ ببینین QuickCheck
چی میگه.
۷.
تست کنین که دوبار معکوس کردنِ یه لیست، معادلِ همانی برای لیسته:
reverse . reverse == id
۸.
یه مشخصه برای تعریفِ ($)
بنویسین.
f $ a = f a
f . g = \x -> f (g x)
۹.
ببینین آیا این دوتا تابعها برابرند یا نه:
foldr (:) == (++)
foldr (++) [] == concat
۱۰.
همم... واقعاً اینطوره؟
f n xs = length (take n xs) == n
۱۱.
بالاخره یه تمرین جذاب. اگه یادتون باشه یه بار ازتون خواستیم که تابعهای read
و show
رو با هم ترکیب کنین تا یه "رفتوبرگشت" رو کامل کنین. خوب حالا میتونین تست کنین ببینین کار میکنه یا نه:
f x = (read (show x)) == x
شکست
ببینید چرا این مشخصه شکست میخوره.
-- برای تابع زیر
square x = x * x
-- چرا این مشخصه برقرار نیست؟
-- رو بررسی کنین sqrt تایپ
squareIdentity = square . sqrt
اگه با ممیز شناور و میزان دقتش آشنا نیستین، یه کم راجع بهش تحقیق کنین.
تکرارشوندگی
تکرارشوندگی، خاصیت ِ بعضی از توابعه که خروجیِ اونها بعد از اولین اعمال دیگه تغییری نمیکنه. اگه یکبار تابع رو اعمال کنین، یه جواب برمیگردونه، و اعمال ِ مجددِ همون تابع به جواب، دیگه هیچ وقت تغییرش نمیده. مثلاً مرتب کردنِ یه لیست: یکبار که مرتب ِش کنین، بعد اعمال ِ همون تابعِ مرتبکننده روی لیستِ مرتبشده، دیگه تأثیری نداره.
به کمکِ QuickCheck
و توابعِ کمکیِ زیر، تکرارشوندگی ِ توابعِ زیر رو نشون بدین:
twice f = f . f
fourTimes = twice . twice
۱.
f x =
(capitalizeWord x
== twice capitalizeWord x)
&&
(capitalizeWord x
== fourTimes capitalizeWord x)
۲.
f' x =
(sort x
== twice sort x)
&&
(sort x
== fourTimes sort x)
برای نوعدادهها ژنراتور تصادفی درست کنین
در طول فصل، نحوهی ساختنِ ژنراتورهای Gen
رو برای انواعِ نوعدادهها نشون دادیم. انقدر مطمئن هستیم که ازشون لذت بردین، که میخوایم خودتون برای چندتا نوعداده ِ جدید ژنراتور بسازین:
۱.
با احتمالِ مساوی برای هر کدوم:
data Fool =
Fulse
| Frue
deriving (Eq, Show)
۲.
۲/۳ احتمال برای Fulse
، ۱/۳ احتمال برای Frue
.
data Fool =
Fulse
| Frue
deriving (Eq, Show)
تستِ داربازی
برای این تمرین، باید برگردین به پروژهی داربازی از فصل قبل و براش تست بنویسین. به خاطر سبکِ تعاملی ِ بازی، انواع تستهایی که در این نقطه میتونین براش بنویسین محدودن. با این حال میتونین توابعش رو تست کنین. روی تستِ این تابعها متمرکز بشین:
fillInCharacter :: Puzzle -> Char -> Puzzle
fillInCharacter (Puzzle word
filledInSoFar s) c =
Puzzle word newFilledInSoFar (c : s)
where zipper guessed wordChar guessChar =
if wordChar == guess
then Just wordChar
else guessChar
newFilledInSoFar =
let zd = (zipper c)
in zipWith zd word filledInSoFar
و:
handleGuess :: Puzzle -> Char -> IO Puzzle
handleGuess puzzle guess = do
putStrLn $ "Your guess was: " ++ [guess]
case (charInWord puzzle guess
, alreadyGuess puzzle guess) of
(_, True) -> do
putStrLn "You already guess that\
\ character, pick \
\ something else!"
return puzzle
(True, _) -> do
putStrLn "This character was in the\
\ word, filling in the word\
\ accordingly"
return puzzle
(False, _) -> do
putStrLn "This character wasn't in\
\ the word, try again."
return (fillInCharacter puzzle guess)
کاری که قراره انجام بدن رو به خاطر بیارین و با تست مطمئن بشین که اون کار رو انجام میدن.
تأییدِ رمزنگارها
به عنوان تمرین آخر، برای رمزنگارهای سزار و ویژنر، مشخصههای QuickCheck
ای بنویسین که مساوی بودنِ یه متن رو بعد از رمزنگاری و رمزشکنی بررسی کنن.