Cette fois je met des tests en place avec HUnit .
Pour cela, je dois d’abord modulariser mon code. J’ai donc déplacé le code
de la dernière fois, sans la fonction main
, dans un fichier GameOfLife
.
Puis j’ai ajouté la déclaration du module.
module GameOfLife
( randomCells
, createGeneration
, formatGeneration
) where
import System.Random
import Data.List
type Cell = Int
type Generation = [[ Cell ]]
randomCells :: Int -> StdGen -> [ Cell ]
randomCells size gen = take size $ randomRs ( 0 , 1 ) gen
createGeneration :: Int -> [ Cell ] -> Generation
createGeneration _ [] = []
createGeneration width cells = line : ( createGeneration width rest )
where ( line , rest ) = splitAt width cells
formatGeneration :: Generation -> String
formatGeneration generation =
let rows = intercalate " \n " ( map ( concatMap show ) generation )
in map replaceChar rows
replaceChar :: Char -> Char
replaceChar '1' = '@'
replaceChar '0' = ' '
replaceChar c = c
Une déclaration de module, c’est ça:
module GameOfLife
( randomCells
, createGeneration
, formatGeneration
) where
J’ai donc un module GameOfLife
qui exporte, pour l’instant, trois fonctions.
Au fait, le code est sur Github .
Je vais créer la fonction cellNextState
, je la rajoute donc dans les exports
du module:
module GameOfLife
( randomCells
, createGeneration
, formatGeneration
, cellNextState
) where
Et j’en crée une version qui ne fonctionne pas ;)
cellNextState :: Cell -> [ Cell ] -> Cell
cellNextState cell neighborhood = undefined
C’est parti pour mon premier test en Haskell. Je crée un fichier
GameOfLife_Test.hs
:
module GameOfLife_Test where
import GameOfLife ( cellNextState )
import Test.HUnit
testCellNextState3 = TestCase $ assertEqual
"Gets live cell when neighborhood'sum is 3" 1 ( cellNextState 0 [ 1 , 1 , 1 , 0 ])
main = runTestTT testCellNextState3
C’est du bon vieux test unitaire à l’ancienne. Je mentirais en disant que
je trouve la syntaxe sexy.
$ runhaskell GameOfLife_Test.hs
### Error:
Prelude.undefined
Cases: 1 Tried: 1 Errors: 1 Failures: 0
Counts {cases = 1, tried = 1, errors = 1, failures = 0}
Bon, si maintenant ma fonction renvoie 1, le test devrait passer.
cellNextState cell neighborhood = 1
$ runhaskell GameOfLife_Test.hs
Cases: 1 Tried: 1 Errors: 0 Failures: 0
Counts {cases = 1, tried = 1, errors = 0, failures = 0}
J’aimerais bien avoir une sortie en couleur. Si il y a moyen, je n’ai pas
encore trouvé…
Quoiqu’il en soit, je peux tester mon code Haskell, et ça c’est cool. Je vais
donc en finir avec cellNextState
en faisant quelques tests de plus:
module GameOfLife_Test where
import GameOfLife ( cellNextState )
import Test.HUnit
testCellNextState3 = TestCase $ assertEqual
"Gets 1 when neighborhood's sum is 3"
1 ( cellNextState 0 [ 1 , 1 , 1 , 0 ])
testCellNextState4AndAlive = TestCase $ assertEqual
"Gets 1 when neighborhood's sum is 4 and cell is alive"
1 ( cellNextState 1 [ 1 , 1 , 1 , 0 , 1 ])
testCellNextState4AndDead = TestCase $ assertEqual
"Gets 0 when neighborhood's sum is 4 and cell is dead"
0 ( cellNextState 0 [ 1 , 1 , 1 , 0 , 1 ])
testCellNextState6 = TestCase $ assertEqual
"Gets 0 when neighborhood's sum is 6"
0 ( cellNextState 1 [ 1 , 1 , 1 , 0 , 1 , 1 , 1 ])
main = runTestTT $ TestList [ testCellNextState3 ,
testCellNextState4AndAlive ,
testCellNextState4AndDead ,
testCellNextState6 ]
$ runhaskell GameOfLife_Test.hs
### Failure in: 2
Gets 0 when neighborhood's sum is 4 and cell is dead
expected: 0
but got: 1
### Failure in: 3
Gets 0 when neighborhood's sum is 6
expected: 0
but got: 1
Cases: 4 Tried: 4 Errors: 0 Failures: 2
Counts {cases = 4, tried = 4, errors = 0, failures = 2}
cellNextState :: Cell -> [ Cell ] -> Cell
cellNextState cell neighborhood
| total == 4 = cell
| total == 3 = 1
| otherwise = 0
where total = sum neighborhood
$ runhaskell GameOfLife_Test.hs
Cases: 4 Tried: 4 Errors: 0 Failures: 0
Counts {cases = 4, tried = 4, errors = 0, failures = 0}