Chapter 23: running the tests, and the Journal
Chapter 23 ends two ways: fifteen tests that cover the record, the class and the functions, and a game you can actually play. This lesson plays the first ending, and then looks inside the class those tests already exercised, through the Journal. To play it in the IDE: Help > Lessons > Writing code…, or ek9 -ide -lesson book-game-journal.
Start from
The code the lesson starts from: copy it into a file of your own and follow the steps.
Guess.ek9
#!ek9 defines module book.guess defines record Score who as String: String() attempts as Integer: Integer() default Score() as pure Score() as pure -> name as String tries as Integer who :=? String(name) attempts :=? Integer(tries) operator $ as pure <- rtn as String: `${who} (${attempts})` operator <=> as pure -> other as Score <- rtn as Integer: attempts <=> other.attempts operator #? as pure <- rtn as Integer: #?attempts default operator ? defines class //Holds one game: the secret, how many tries are allowed, and how it is going. Game secret as Integer: Integer() maxTries as Integer: Integer() tries as Integer: 0 solved as Boolean: false default private Game() as pure Game() as pure -> chosen as Integer allowed as Integer secret :=? Integer(chosen) maxTries :=? Integer(allowed) //Take one guess. Count it, say which way to go, and note a win. guess() -> number as Integer <- rtn as String: String() tries += 1 rtn: hint(secret, number) if number == secret solved := true over() as pure <- rtn as Boolean: solved or tries >= maxTries triesUsed() as pure <- rtn <- Integer(tries) report() as pure -> player as String <- rtn as String: String() rtn: solved <- `Well done ${player} - ${tries} tries.` : `Out of tries - the number was ${secret}.` default operator ? defines function //Compare a guess against the secret and say which way to go. hint() as pure -> secret as Integer guess as Integer <- rtn as String: String() order <- guess <=> secret rtn: order < 0 <- "higher" : order > 0 <- "lower" : "correct" //Put a score board in order: fewest tries first. rank() as pure -> board as List of Score <- rtn as List of Score: List() of Score rtn: cat board | sort | collect as List of Score //The scores already on the board when a new game starts. startingBoard() <- rtn as List of Score: List() of Score rtn += Score("Ada", 3) rtn += Score("Alan", 5) //Add this game's score to the opening board. finalBoard() -> player as String tries as Integer <- rtn as List of Score: List() of Score rtn: startingBoard() rtn += Score(player, tries) //Render the ranked board as one block of text, best first. boardLines() -> board as List of Score <- rtn as String: "" for entry in rank(board) rtn += ` ${entry}\n` //Work out the player's name from the command-line options. playerName() -> argv as List of String <- rtn as String: String() usage <- "Usage: Guess [-p name]" supported <- { "-p": ":" } rtn: GetOpt(":", supported, usage).options(argv).getOrDefault("-p", "Player") defines program Guess() -> argv as List of String stdin <- Stdin() stdout <- Stdout() player <- playerName(argv) high <- 100 maxTries <- 7 game <- Game(OS().random(1, high), maxTries) stdout.println(`I am thinking of a number from 1 to ${high}. You have ${maxTries} tries.`) while not game.over() number <- Integer(stdin.next()) stdout.println(` ${number}: ${game.guess(number)}`) stdout.println(game.report(player)) stdout.println("Fewest tries wins:") stdout.print(boardLines(finalBoard(player, game.triesUsed())))
dev/tests.ek9
#!ek9 defines module book.guess.tests references book.guess::boardLines book.guess::finalBoard book.guess::Game book.guess::hint book.guess::playerName book.guess::rank book.guess::Score book.guess::startingBoard defines program @Test HigherTest() assert hint(50, 20) == "higher" @Test LowerTest() assert hint(30, 80) == "lower" @Test CorrectTest() assert hint(70, 70) == "correct" @Test ScoreShowsNameAndTries() assert $Score("Ada", 3) == "Ada (3)" @Test FewerTriesComeFirst() strong <- Score("Grace", 1) weak <- Score("Alan", 6) assert strong <=> weak < 0 assert strong? @Test EqualScoresHashAlike() assert (#?Score("Grace", 1)) == (#?Score("Grace", 1)) @Test GameIsSet() assert Game(8, 9)? @Test WinningGame() game <- Game(42, 7) assert game.guess(50) == "lower" assert not game.over() assert game.guess(42) == "correct" assert game.over() assert game.triesUsed() == 2 assert game.report("Sam") == "Well done Sam - 2 tries." @Test LosingGame() game <- Game(30, 3) assert game.guess(10)? assert game.guess(90)? assert game.guess(25)? assert game.over() assert game.report("Sam") == "Out of tries - the number was 30." @Test RankPutsFewestFirst() board <- [ Score("Cleo", 5), Score("Bob", 4) ] best <- rank(board).getOrDefault(0, Score()) assert best.who == "Bob" @Test BoardOpensWithAda() opening <- rank(startingBoard()).getOrDefault(0, Score()) assert opening.who == "Ada" @Test TopScoreJoinsBoard() best <- rank(finalBoard("Max", 1)).getOrDefault(0, Score()) assert best.who == "Max" @Test BoardLinesListsEveryone() lines <- boardLines([Score("Ida", 7), Score("Uma", 2)]) assert lines contains "Ida (7)" assert lines contains "Uma (2)" @Test PlayerNameFromFlag() assert playerName(["-p", "Zoe"]) == "Zoe" @Test PlayerNameDefaults() assert playerName(List() of String) == "Player"
Step 1: Running the tests
Run ▸ Run Tests (⌘T) runs every @Test in the project - all fifteen of them - and reports pass or fail, the same as ek9 -t at the command line. Choose Run Tests.
The Tests tab lists all fifteen, each with a green tick. They cover Score, Game and the six functions - everything the chapter's own prose calls out as tested. The one thing no test here touches is Guess itself: the interactive loop, which needs a keyboard, not an assertion.
Step 2: The Journal
Choose Run > New Journal Investigation…
The project has several programs - Guess, and every @Test. Choose WinningGame, watch Game.tries, and leave the input sets blank: WinningGame takes no arguments, so one line is one run with none. Choose Start.
The test runs while the Journal records every change to Game.tries. Every assert in WinningGame holds, and the run shows in the Journal tab as pass.
A run with no symptom opens at its WHOLE story, oldest first: Game.tries starts at 0, then becomes 1 - the first guess, 50, which guess() counted before it compared - then 2, the winning guess, 42. Two ticks, one for each call to guess() the test makes: the class was already proved correct, long before any program used it.
The result
All fifteen tests pass, covering everything but the interactive loop - and the Journal showed exactly what WinningGame's own asserts already proved: tries counted up, one for each guess, inside a class that was tested long before the program that uses it ever ran.
Where this lesson comes from
- For AI assistants - the MCP tool
ek9_run_tests,ek9_debug_inspect.





