{name} wins
. Here is what the current CLI
code looks like, but be sure to familiarise yourself with the other code too before starting.time.AfterFunc
AfterFunc
waits for the duration to elapse and then calls f in its own goroutine. It returns aTimer
that can be used to cancel the call using its Stop method.
A Duration represents the elapsed time between two instants as an int64 nanosecond count.
PlayPoker
we'll schedule all of our blind alerts.time.AfterFunc
its second argument is the function it will run. You cannot compare functions in Go so we'd be unable to test what function has been sent in. So we'll need to write some kind of wrapper around time.AfterFunc
which will take the time to run and the amount to print so we can spy on that.SpyBlindAlerter
which we are trying to inject into our CLI
and then checking that after we call PlayerPoker
that an alert is scheduled.SpyBlindAlerter
NewCLI
accept a *SpyBlindAlerter
but let's cheat a little and just define the dependency as an interface.BlindAlerter
passed in to NewCLI
.BlindAlerter
as a field on our CLI
so we can reference it in our PlayPoker
method.BlindAlerter
with anything we likeSpyBlindAlerter
to see if the alert has been scheduled with the correct values.blinds
and calling the scheduler on an increasing blindTime
PlayPoker
read a little clearer.ScheduledAlert
. Let's refactor that into a new type and then make some helpers to compare them.String()
method to our type so it prints nicely if the test failsassertScheduledAlert
yourself.NewCLI
.BlindAlerter
that we can use in our application.BlindAlerter.go
and move our BlindAlerter
interface and add the new things belowstructs
. If you are making a library that exposes an interface with one function defined it is a common idiom to also expose a MyInterfaceFunc
type.func
which will also implement your interface. That way users of your interface have the option to implement your interface with just a function; rather than having to create an empty struct
type.StdOutAlerter
which has the same signature as the function and just use time.AfterFunc
to schedule it to print to os.Stdout
.main
where we create NewCLI
to see this in actionblindTime
increment in CLI
to be 10 seconds rather than 10 minutes just so you can see it in action.Shaun wins
into the CLI and it will stop the program how we'd expect.os.Stdout
is an io.Writer
so we can check what is written if we use dependency injection to pass in a bytes.Buffer
in our test and see what our code will write.CLI
, that feels like maybe it is starting to have too many responsibilities. Let's live with it for now and see if a refactoring emerges as we add this new functionality.os.Stdout
in main
and see what is written.NewCLI
io.Writer
being passed into NewCLI
.dummyStdout
for the other tests.CLI
so we can reference it in PlayPoker
CLI
.numberOfPlayersInput
into a stringcli.readLine()
to get the input from the user and then call Atoi
to convert it into an integer - ignoring any error scenarios. We'll need to write a test for that scenario later.scheduleBlindAlerts
to accept a number of players. We then calculate a blindIncrement
time to use to add to blindTime
as we iterate over the blind amountsGame
with the correct number of players. Game
.Game
first and our test should continue to pass. Once we've made the structural changes we want we can think about how we can refactor the tests to reflect our new separation of concernsGame
would offer and what our CLI
should be doing.NewCLI
as we don't want to change the test code and the client code at the same time as that is too much to juggle and we could end up breaking things.Start
a Game
, indicating how many people are playingFinish
a Game
, declaring the winnerGame
type encapsulates this for us.BlindAlerter
and PlayerStore
to Game
as it is now responsible for alerting and storing results.CLI
is now just concerned with:Game
with its existing dependencies (which we'll refactor next)Game
Game
so that we inject it into CLI
. We'll do the smallest changes in our tests to facilitate that and then we'll see how we can break up the tests into the themes of parsing user input and game management.NewCLI
Game
.Game
right now, just initialise real Game
s just to get everything compiling and tests green.main.go
too before the next stage.Game
we should move our game specific assertions into tests separate from CLI.CLI
tests but with less dependenciesGame
's methods when appropriateCLI
no longer relies on a concrete Game
type but instead accepts an interface with Start(numberOfPlayers)
and Finish(winner)
. We can then create a spy of that type and verify the correct calls are made.Game
to TexasHoldem
(as that's the kind of game we're playing) and the new interface will be called Game
. This keeps faithful to the notion that our CLI is oblivious to the actual game we're playing and what happens when you Start
and Finish
.*Game
inside CLI
and replace them with Game
(our new interface). As always keep re-running tests to check everything is green while we are refactoring.CLI
from TexasHoldem
we can use spies to check that Start
and Finish
are called when we expect them to, with the correct arguments.Game
CLI
test which is testing any game specific logic with checks on how our GameSpy
is called. This will then reflect the responsibilities of CLI in our tests clearly.CLI
should be easier.GameSpy
a field StartCalled
which only gets set if Start
is calledAtoi
we just need to check for the errorstdout
.stdout
before so we can copy that code for nowpoker.PlayerPrompt