Countdown
which we will then put inside a main
program so it looks something like this:Countdown
function to write data somewhere and io.Writer
is the de-facto way of capturing that as an interface in Go.main
we will send to os.Stdout
so our users see the countdown printed to the terminal.bytes.Buffer
so our tests can capture what data is being generated../countdown_test.go:11:2: undefined: Countdown
Countdown
countdown_test.go:17: got '' want '3'
fmt.Fprint
which takes an io.Writer
(like *bytes.Buffer
) and sends a string
to it. The test should pass.*bytes.Buffer
works, it would be better to use a general purpose interface instead.main
so we have some working software to reassure ourselves we're making progress.string
but lets you put things like newlines which is perfect for our test.for
loop counting backwards with i--
and use fmt.Fprintln
to print to out
with our number followed by a newline character. Finally use fmt.Fprint
to send "Go!" aftward.time.Sleep
. Try adding it in to our code.Countdown
?Sleep
ing which we need to extract so we can then control it in our tests.time.Sleep
we can use dependency injection to use it instead of a "real" time.Sleep
and then we can spy on the calls to make assertions on them.main
and a spy sleeper in our tests. By using an interface our Countdown
function is oblivious to this and adds some flexibility for the caller.Countdown
function would not be responsible for how long the sleep is. This simplifies our code a little for now at least and means a user of our function can configure that sleepiness however they like.Sleep()
is called so we can check it in our test.Countdown
to accept our Sleeper
main
will no longer compile for the same reasontime.Sleep
rather than the injected in dependency. Let's fix that.Countdown
should sleep before each print, e.g:Sleep
Print N
Sleep
Print N-1
Sleep
Print Go!
SpyCountdownOperations
implements both io.Writer
and Sleeper
, recording every call into one slice. In this test we're only concerned about the order of operations, so just recording them as list of named operations is sufficient.Countdown
back to how it was to fix the test.Sleeper
so we can now refactor our test so one is testing what is being printed and the other one is ensuring we're sleeping in between the prints. Finally we can delete our first spy as it's not used anymore.Sleeper
to be configurable. This means that we can adjust the sleep time in our main program.ConfigurableSleeper
that accepts what we need for configuration and testing.duration
to configure the time slept and sleep
as a way to pass in a sleep function. The signature of sleep
is the same as for time.Sleep
allowing us to use time.Sleep
in our real implementation and the following spy in our tests:Sleep
method created on our ConfigurableSleeper
.Sleep
function implemented we have a failing test.Sleep
function for ConfigurableSleeper
.ConfigurableSleeper
in the main function.ConfigurableSleeper
, it is now safe to delete the DefaultSleeper
implementation. Wrapping up our program and having a more generic Sleeper with arbitrary long countdowns."When to use iterative development? You should use iterative development only on projects that you want to succeed."