Testing and Style Solutions
Question: Writing Black Box Tests
Write 3 black box tests for the following functions. Note that you only have the function name and type signature. So you don’t know how the function works and operates, meaning that your tests will focus on the overall functionality rather than internal calculation.
absoluteValue :: (Num a, Ord a) => a -> a
Answer:
-- >> absoluteValue 10
-- 10
-- >> absoluteValue -192
-- -192
-- >> absoluteValue -1
-- 1
isNotNegative :: (Num a, Ord a) => a -> Boolean
Answer:
-- >> isNotNegative 10
-- True
-- >> isNotNegative -192
-- False
-- >> isNotNegative -1
-- False
data Triangle = Equilateral | Isosceles | Scalene
-- Given three side lengths, determines whether the triangle is Equilateral, Isosceles or Scalene
typeOfTriangle :: (Num a) => a -> a -> a -> Triangle
Answer:
-- >> typeOfTriangle 1 1 1
-- Equilateral
-- >> typeOfTriangle 2 2 3
-- Isosceles
-- >> typeOfTriangle 1 2 3
-- Scalene
-- Sum the elements of the list that are odd
sumOdd :: [Int] -> Int
Answer:
-- >> sumOdd [2,4,8]
-- 0
-- >> sumOdd [2,3,4,5]
-- 8
-- >> sumOdd [1,0,1]
-- 2
data Month
= January
| February
| March
| April
| May
| June
| July
| August
| September
| October
| November
| December
deriving (Show, Eq, Enum)
data Day = Integer
type Date = (Month, Day)
-- Ignoring leap years, return the date of the next day
nextDate :: Date -> Date
Answer:
-- >> nextDate (Novemeber, 22)
-- (November, 23)
-- >> nextDate (February, 28)
-- (March, 1)
-- >> nextDate (August, 1)
-- (August, 2)
Note: Your answers will almost definitely differ from ours. However, you should be able to see similarities in the types of inputs tested.
Question: White Box Tests
Now that you have written your black box tests, let’s look at the code and write some white box tests. Some of the functions are broken: use white box tests to find these errors.
absoluteValue :: (Num a) => a -> a
absoluteValue a
| a < 0 = -a
| a >= 0 = a
Answer: Function is correct!
isNotNegative :: (Num a) => a -> Boolean
isNotNegative a
| a > 0 = True
| otherwise = False
Answer: Function breaks when the input is 0.
data Triangle = Equilateral | Isosceles | Scalene
typeOfTriangle :: (Num a) => a -> a -> a -> Triangle
typeOfTriangle a b c
| a == b && b == c = Equilateral
| a == b || b == c = Isosceles
| s > a && s > b && s > c = Scalene
| otherwise = error "Not a Triangle"
where
s = (a+b+c)/2
Answer: Function doesn’t consider all isosceles cases, e.g. 2,3,2
sumOdd :: [int] -> int
sumOdd l = case l of
[] -> 0
x:xs -> x * (x `mod` 2) + (sumOdd xs)
Answer: Functions breaks with negative numbers as
-i `mod` 2 = -i
data Month = January | February | March | April | May | June | July | August | September | October | November | December
deriving (Show, Eq, Enum)
type Day = Int
type Date = (Month, Day)
-- Ignoring leap years, return the date of the next day
nextDate :: Date -> Date
nextDate (month, day)
| month == February = increment (month, day) 28
| elem month monthsWith31Days = increment (month, day) 31
| otherwise = increment (month, day) 30
where
monthsWith31Days = [January, March, May, July, August, October, December]
increment :: Date -> Int -> Date
increment (m,d) n
| d >= n = (succ m , (d+1) `mod` n)
| otherwise = (m, d+1)
Answer: The function doesn’t work when given the input
(December, 31)
.
Question: Code Style
Below are lots of example haskell functions. Many of them have terrible style, for a variety of reasons. For each function, fix them to make them beautifully readable!
-- map applies a function to each element of the list
map :: (a -> b) -> [a] -> [b]
map _ [] = []
map function (firstElement:remainingList) =
function firstElement : map function remainingList
Answer: Names are too long. Outputs aren’t aligned.
-- map applies a function to each element of the list map :: (a -> b) -> [a] -> [b] map _ [] = [] map f (x:xs) = f x : map f xs
-- bmi calc bmi :: Double -> Double -> String bmi x y | x / y^2 <= 15 = "Severely Underweight" | x / y^2 <= 18.5 = "Underweight" | x / y^2 <= 25 = "Normal" | x / y^2 <= 30 = "Overeight" | otherwise = "Obese"
Answer: Comment isn’t informative. Repeated code that should move to a where clause. Bad names for inputs as you can’t tell them apart.
-- bmi takes in your height and weight and returns your weight category bmi :: Double -> Double -> Double bmi height weight | result <= 15 = "Severely Underweight" | result <= 18.5 = "Underweight" | result <= 25 = "Normal" | result <= 30 = "Overweight" | otherwise = "Obese" where result = x / y^2
answers :: [Maybe Int] answers =[ Just 42 , Just 7 , Nothing ]
Answer: Aligned is not comma-leading and no explanatory comment.
-- answers is a list of student marks. Nothing designates that the students didn't sit the test. answers :: [Maybe Int] answers = [ Just 42 , Just 7 , Nothing ]
-- meanFuncValue calculates the mean (i.e. average) of a list after applying a function to each element of the list meanFuncValue :: [Int] -> (Int -> Float) -> Float meanFuncValue ls f = helper ls f (length ls) where helper :: [Int] -> (Int -> Float) -> Int -> Float helper ls f len = case ls of [] -> 0 x:xs -> ((f x) / (fromIntegral len)) + helper xs f len
Answer: This function should use prelude functions and better helper functions to make it far simpler. Additionally, the type signature is needlessly specific and should be generalised with polymorphism.
-- meanFuncValue calculates the mean (i.e average) of a list after applying a function to each element of the list meanFuncValue :: (Fractional b) => [a] -> (a -> b) -> b meanFuncValue ls f = sum (map f ls) / fromIntegral(length(ls)) where map :: (a -> b) -> [a] -> [b] map f ls = case ls of [] -> [] x:xs -> f x : map f xs
-- count finds how many repetitions of a specific element there are in a list count :: (Eq a) => [a] -> a -> Double count n ls | head n == ls = 1 + count (tail n) ls | otherwise = count (tail n) ls
Answer: Names are terrible, mixing conventions and standards. Also the function type is too general - all the outputs are
Int
notDouble
.-- count finds how many repetitions of a specific element there are in a list count :: (Eq a) => [a] -> a -> Int count ls elem | head ls == elem = 1 + count (tail ls) elem | otherwise = count (tail ls) elem