[Haskell-cafe] HUnit and table-driven tests
wonderzombie at gmail.com
Tue Aug 7 20:18:50 CEST 2012
On Tue, Aug 7, 2012 at 12:51 AM, Dean Herington
<heringtonlacey at mindspring.com> wrote:
> At 4:30 PM -0700 8/5/12, Matthew wrote:
>> On Sun, Aug 5, 2012 at 12:32 AM, Henk-Jan van Tuyl <hjgtuyl at chello.nl>
>>> On Sun, 05 Aug 2012 03:21:39 +0200, Matthew <wonderzombie at gmail.com>
>>>> I've got a function which takes in two chars, describing a playing
>>>> card and a suit. An example would be 4C or TH for a 4 of Clubs or a
>>>> Ten of Hearts. I need to be able to compare the ranks of a card (e.g.
>>>> a King is 13), so a Card is a tuple of rank and suit. The function
>>>> which parses a Card is type String -> Maybe Card.
>>>> I'm writing unit tests for this using HUnit, and ideally I'd go with a
>>>> table-driven approach, where each test case is a tuple of the input
>>>> and the expected output. (Possibly I could expand this to a triple, or
>>>> simply a list, to allow for an error message for each test case.) Then
>>>> all the test function has to do is run through each case and assert as
>>>> necessary. Example: [("TH", Just (Hearts, 10)), ("XH", Nothing)].
>>> A simple solution:
>>>> parseCard :: String -> Maybe Card
>>>> parseCard string = <your function to test>
>>>> test :: Bool
>>>> test = all testEqual [("TH", Just (Hearts, 10)), ("XH", Nothing)]
>>>> testEqual (input, output) = parseCard input == output
>>> For a description of 'all', see:
>> Thanks for the response. The one problem I have with this is that it
>> will not be at all obvious which test case (or cases!) failed.
>> That said, maybe I could do something similar, with a Writer? A passed
>> test writes "", but a failed one writes a test-specific failure
>> message. Then the test itself uses this string as the assert message.
> Let HUnit tell you about the failing test cases. Here's one way to do it.
> import Test.HUnit
> import Data.Char (isDigit)
> data Suit = Spades | Hearts | Diamonds | Clubs
> deriving (Show, Read, Eq, Ord)
> type Rank = Int -- 2 .. 14 (jack=11, queen=12, king=13, ace=14)
> type Card = (Suit, Rank)
> parseCard :: String -> Maybe Card
> parseCard [rankChar, suitChar] = do suit <- suitFrom suitChar; rank <-
> rankFrom rankChar; return (suit, rank)
> parseCard _ = Nothing
> suitFrom char = lookup char [('S', Spades), ('H', Hearts), ('D', Diamonds),
> ('C', Clubs)]
> rankFrom dig | isDigit dig = let v = read [dig] in if v >= 2 then Just v
> else Nothing
> rankFrom char = lookup char [('T', 10), ('J', 11), ('Q', 12), ('K', 13),
> ('A', 14)]
> makeTest :: (String, Maybe Card) -> Test
> makeTest (string, result) = string ~: result ~=? parseCard string
> tests = [("TH", Just (Hearts, 10)), ("XH", Nothing)]
> main = (runTestTT . TestList . map makeTest) tests
...it seems so obvious now. This is *exactly* what I was looking for;
clearly I was over-thinking this.
More information about the Haskell-Cafe