go fmt code block to reduce formating errors (#350)

I used gofmtmd package:
```sh
ls -1 *.md  | xargs -I % sh -c 'echo %; gofmtmd % -r'
```

There is some errors, in file that could not be parsed
This commit is contained in:
Benoit Tigeot 2020-05-23 08:56:06 +02:00 committed by GitHub
parent a6fcd41941
commit d2e1cf2ef6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 1169 additions and 1169 deletions

View File

@ -22,14 +22,14 @@ import "testing"
func TestSum(t *testing.T) {
numbers := [5]int{1, 2, 3, 4, 5}
numbers := [5]int{1, 2, 3, 4, 5}
got := Sum(numbers)
want := 15
got := Sum(numbers)
want := 15
if got != want {
t.Errorf("got %d want %d given, %v", got, want, numbers)
}
if got != want {
t.Errorf("got %d want %d given, %v", got, want, numbers)
}
}
```
@ -57,7 +57,7 @@ In `sum.go`
package main
func Sum(numbers [5]int) int {
return 0
return 0
}
```
@ -69,11 +69,11 @@ Your test should now fail with _a clear error message_
```go
func Sum(numbers [5]int) int {
sum := 0
for i := 0; i < 5; i++ {
sum += numbers[i]
}
return sum
sum := 0
for i := 0; i < 5; i++ {
sum += numbers[i]
}
return sum
}
```
@ -87,11 +87,11 @@ Let's introduce [`range`](https://gobyexample.com/range) to help clean up our co
```go
func Sum(numbers [5]int) int {
sum := 0
for _, number := range numbers {
sum += number
}
return sum
sum := 0
for _, number := range numbers {
sum += number
}
return sum
}
```
@ -125,27 +125,27 @@ declaring them
```go
func TestSum(t *testing.T) {
t.Run("collection of 5 numbers", func(t *testing.T) {
numbers := [5]int{1, 2, 3, 4, 5}
t.Run("collection of 5 numbers", func(t *testing.T) {
numbers := [5]int{1, 2, 3, 4, 5}
got := Sum(numbers)
want := 15
got := Sum(numbers)
want := 15
if got != want {
t.Errorf("got %d want %d given, %v", got, want, numbers)
}
})
if got != want {
t.Errorf("got %d want %d given, %v", got, want, numbers)
}
})
t.Run("collection of any size", func(t *testing.T) {
numbers := []int{1, 2, 3}
t.Run("collection of any size", func(t *testing.T) {
numbers := []int{1, 2, 3}
got := Sum(numbers)
want := 6
got := Sum(numbers)
want := 6
if got != want {
t.Errorf("got %d want %d given, %v", got, want, numbers)
}
})
if got != want {
t.Errorf("got %d want %d given, %v", got, want, numbers)
}
})
}
```
@ -169,11 +169,11 @@ In our case, no-one else is using our function so rather than having two functio
```go
func Sum(numbers []int) int {
sum := 0
for _, number := range numbers {
sum += number
}
return sum
sum := 0
for _, number := range numbers {
sum += number
}
return sum
}
```
@ -190,27 +190,27 @@ We had already refactored `Sum` and all we've done is changing from arrays to sl
```go
func TestSum(t *testing.T) {
t.Run("collection of 5 numbers", func(t *testing.T) {
numbers := []int{1, 2, 3, 4, 5}
t.Run("collection of 5 numbers", func(t *testing.T) {
numbers := []int{1, 2, 3, 4, 5}
got := Sum(numbers)
want := 15
got := Sum(numbers)
want := 15
if got != want {
t.Errorf("got %d want %d given, %v", got, want, numbers)
}
})
if got != want {
t.Errorf("got %d want %d given, %v", got, want, numbers)
}
})
t.Run("collection of any size", func(t *testing.T) {
numbers := []int{1, 2, 3}
t.Run("collection of any size", func(t *testing.T) {
numbers := []int{1, 2, 3}
got := Sum(numbers)
want := 6
got := Sum(numbers)
want := 6
if got != want {
t.Errorf("got %d want %d given, %v", got, want, numbers)
}
})
if got != want {
t.Errorf("got %d want %d given, %v", got, want, numbers)
}
})
}
```
@ -263,12 +263,12 @@ or
```go
func TestSumAll(t *testing.T) {
got := SumAll([]int{1,2}, []int{0,9})
want := []int{3, 9}
got := SumAll([]int{1, 2}, []int{0, 9})
want := []int{3, 9}
if got != want {
t.Errorf("got %v want %v", got, want)
}
if got != want {
t.Errorf("got %v want %v", got, want)
}
}
```
@ -284,7 +284,7 @@ Go can let you write [_variadic functions_](https://gobyexample.com/variadic-fun
```go
func SumAll(numbersToSum ...[]int) (sums []int) {
return
return
}
```
@ -300,12 +300,12 @@ useful for seeing if _any_ two variables are the same.
```go
func TestSumAll(t *testing.T) {
got := SumAll([]int{1,2}, []int{0,9})
want := []int{3, 9}
got := SumAll([]int{1, 2}, []int{0, 9})
want := []int{3, 9}
if !reflect.DeepEqual(got, want) {
t.Errorf("got %v want %v", got, want)
}
if !reflect.DeepEqual(got, want) {
t.Errorf("got %v want %v", got, want)
}
}
```
@ -318,12 +318,12 @@ temporarily change the test to:
```go
func TestSumAll(t *testing.T) {
got := SumAll([]int{1,2}, []int{0,9})
want := "bob"
got := SumAll([]int{1, 2}, []int{0, 9})
want := "bob"
if !reflect.DeepEqual(got, want) {
t.Errorf("got %v want %v", got, want)
}
if !reflect.DeepEqual(got, want) {
t.Errorf("got %v want %v", got, want)
}
}
```
@ -343,14 +343,14 @@ What we need to do is iterate over the varargs, calculate the sum using our
```go
func SumAll(numbersToSum ...[]int) []int {
lengthOfNumbers := len(numbersToSum)
sums := make([]int, lengthOfNumbers)
lengthOfNumbers := len(numbersToSum)
sums := make([]int, lengthOfNumbers)
for i, numbers := range numbersToSum {
sums[i] = Sum(numbers)
}
for i, numbers := range numbersToSum {
sums[i] = Sum(numbers)
}
return sums
return sums
}
```
@ -374,12 +374,12 @@ returning a new slice with all the items in it.
```go
func SumAll(numbersToSum ...[]int) []int {
var sums []int
for _, numbers := range numbersToSum {
sums = append(sums, Sum(numbers))
}
var sums []int
for _, numbers := range numbersToSum {
sums = append(sums, Sum(numbers))
}
return sums
return sums
}
```
@ -394,12 +394,12 @@ all the items apart from the first one \(the "head"\)
```go
func TestSumAllTails(t *testing.T) {
got := SumAllTails([]int{1,2}, []int{0,9})
want := []int{2, 9}
got := SumAllTails([]int{1, 2}, []int{0, 9})
want := []int{2, 9}
if !reflect.DeepEqual(got, want) {
t.Errorf("got %v want %v", got, want)
}
if !reflect.DeepEqual(got, want) {
t.Errorf("got %v want %v", got, want)
}
}
```
@ -417,13 +417,13 @@ Rename the function to `SumAllTails` and re-run the test
```go
func SumAllTails(numbersToSum ...[]int) []int {
var sums []int
for _, numbers := range numbersToSum {
tail := numbers[1:]
sums = append(sums, Sum(tail))
}
var sums []int
for _, numbers := range numbersToSum {
tail := numbers[1:]
sums = append(sums, Sum(tail))
}
return sums
return sums
}
```
@ -446,23 +446,23 @@ capture all elements from `myEmptySlice[1:]`?
```go
func TestSumAllTails(t *testing.T) {
t.Run("make the sums of some slices", func(t *testing.T) {
got := SumAllTails([]int{1,2}, []int{0,9})
want := []int{2, 9}
t.Run("make the sums of some slices", func(t *testing.T) {
got := SumAllTails([]int{1, 2}, []int{0, 9})
want := []int{2, 9}
if !reflect.DeepEqual(got, want) {
t.Errorf("got %v want %v", got, want)
}
})
if !reflect.DeepEqual(got, want) {
t.Errorf("got %v want %v", got, want)
}
})
t.Run("safely sum empty slices", func(t *testing.T) {
got := SumAllTails([]int{}, []int{3, 4, 5})
want := []int{0, 9}
t.Run("safely sum empty slices", func(t *testing.T) {
got := SumAllTails([]int{}, []int{3, 4, 5})
want := []int{0, 9}
if !reflect.DeepEqual(got, want) {
t.Errorf("got %v want %v", got, want)
}
})
if !reflect.DeepEqual(got, want) {
t.Errorf("got %v want %v", got, want)
}
})
}
```
@ -482,17 +482,17 @@ works, runtime errors are our enemies because they affect our users.
```go
func SumAllTails(numbersToSum ...[]int) []int {
var sums []int
for _, numbers := range numbersToSum {
if len(numbers) == 0 {
sums = append(sums, 0)
} else {
tail := numbers[1:]
sums = append(sums, Sum(tail))
}
}
var sums []int
for _, numbers := range numbersToSum {
if len(numbers) == 0 {
sums = append(sums, 0)
} else {
tail := numbers[1:]
sums = append(sums, Sum(tail))
}
}
return sums
return sums
}
```
@ -503,24 +503,24 @@ Our tests have some repeated code around assertion again, let's extract that int
```go
func TestSumAllTails(t *testing.T) {
checkSums := func(t *testing.T, got, want []int) {
t.Helper()
if !reflect.DeepEqual(got, want) {
t.Errorf("got %v want %v", got, want)
}
}
checkSums := func(t *testing.T, got, want []int) {
t.Helper()
if !reflect.DeepEqual(got, want) {
t.Errorf("got %v want %v", got, want)
}
}
t.Run("make the sums of tails of", func(t *testing.T) {
got := SumAllTails([]int{1, 2}, []int{0, 9})
want := []int{2, 9}
checkSums(t, got, want)
})
t.Run("make the sums of tails of", func(t *testing.T) {
got := SumAllTails([]int{1, 2}, []int{0, 9})
want := []int{2, 9}
checkSums(t, got, want)
})
t.Run("safely sum empty slices", func(t *testing.T) {
got := SumAllTails([]int{}, []int{3, 4, 5})
want := []int{0, 9}
checkSums(t, got, want)
})
t.Run("safely sum empty slices", func(t *testing.T) {
got := SumAllTails([]int{}, []int{3, 4, 5})
want := []int{0, 9}
checkSums(t, got, want)
})
}
```

View File

@ -14,9 +14,9 @@ We have an application with a `main.go` file that launches an HTTP server. The H
```go
type PlayerStore interface {
GetPlayerScore(name string) int
RecordWin(name string)
GetLeague() League
GetPlayerScore(name string) int
RecordWin(name string)
GetLeague() League
}
```
@ -71,32 +71,32 @@ The paths will be different on your computer, but it should be similar to this:
package main
import (
"log"
"net/http"
"os"
"github.com/quii/learn-go-with-tests/command-line/v1"
"github.com/quii/learn-go-with-tests/command-line/v1"
"log"
"net/http"
"os"
)
const dbFileName = "game.db.json"
func main() {
db, err := os.OpenFile(dbFileName, os.O_RDWR|os.O_CREATE, 0666)
db, err := os.OpenFile(dbFileName, os.O_RDWR|os.O_CREATE, 0666)
if err != nil {
log.Fatalf("problem opening %s %v", dbFileName, err)
}
if err != nil {
log.Fatalf("problem opening %s %v", dbFileName, err)
}
store, err := poker.NewFileSystemPlayerStore(db)
store, err := poker.NewFileSystemPlayerStore(db)
if err != nil {
log.Fatalf("problem creating file system player store, %v ", err)
}
if err != nil {
log.Fatalf("problem creating file system player store, %v ", err)
}
server := poker.NewPlayerServer(store)
server := poker.NewPlayerServer(store)
if err := http.ListenAndServe(":5000", server); err != nil {
log.Fatalf("could not listen on port 5000 %v", err)
}
if err := http.ListenAndServe(":5000", server); err != nil {
log.Fatalf("could not listen on port 5000 %v", err)
}
}
```
@ -122,7 +122,7 @@ package main
import "fmt"
func main() {
fmt.Println("Let's play poker")
fmt.Println("Let's play poker")
}
```
@ -138,13 +138,13 @@ Inside `CLI_test.go` (in the root of the project, not inside `cmd`)
```go
func TestCLI(t *testing.T) {
playerStore := &StubPlayerStore{}
cli := &CLI{playerStore}
cli.PlayPoker()
playerStore := &StubPlayerStore{}
cli := &CLI{playerStore}
cli.PlayPoker()
if len(playerStore.winCalls) !=1 {
t.Fatal("expected a win call but didn't get any")
}
if len(playerStore.winCalls) != 1 {
t.Fatal("expected a win call but didn't get any")
}
}
```
@ -168,7 +168,7 @@ You should end up with code like this
```go
type CLI struct {
playerStore PlayerStore
playerStore PlayerStore
}
func (cli *CLI) PlayPoker() {}
@ -186,7 +186,7 @@ FAIL
```go
func (cli *CLI) PlayPoker() {
cli.playerStore.RecordWin("Cleo")
cli.playerStore.RecordWin("Cleo")
}
```
@ -200,22 +200,22 @@ Let's extend our test to exercise this.
```go
func TestCLI(t *testing.T) {
in := strings.NewReader("Chris wins\n")
playerStore := &StubPlayerStore{}
in := strings.NewReader("Chris wins\n")
playerStore := &StubPlayerStore{}
cli := &CLI{playerStore, in}
cli.PlayPoker()
cli := &CLI{playerStore, in}
cli.PlayPoker()
if len(playerStore.winCalls) < 1 {
t.Fatal("expected a win call but didn't get any")
}
if len(playerStore.winCalls) < 1 {
t.Fatal("expected a win call but didn't get any")
}
got := playerStore.winCalls[0]
want := "Chris"
got := playerStore.winCalls[0]
want := "Chris"
if got != want {
t.Errorf("didn't record correct winner, got %q, want %q", got, want)
}
if got != want {
t.Errorf("didn't record correct winner, got %q, want %q", got, want)
}
}
```
@ -233,8 +233,8 @@ We need to add our new dependency into `CLI`.
```go
type CLI struct {
playerStore PlayerStore
in io.Reader
playerStore PlayerStore
in io.Reader
}
```
@ -250,7 +250,7 @@ Remember to do the strictly easiest thing first
```go
func (cli *CLI) PlayPoker() {
cli.playerStore.RecordWin("Chris")
cli.playerStore.RecordWin("Chris")
}
```
@ -262,15 +262,15 @@ In `server_test` we earlier did checks to see if wins are recorded as we have he
```go
func assertPlayerWin(t *testing.T, store *StubPlayerStore, winner string) {
t.Helper()
t.Helper()
if len(store.winCalls) != 1 {
t.Fatalf("got %d calls to RecordWin want %d", len(store.winCalls), 1)
}
if len(store.winCalls) != 1 {
t.Fatalf("got %d calls to RecordWin want %d", len(store.winCalls), 1)
}
if store.winCalls[0] != winner {
t.Errorf("did not store correct winner got %q want %q", store.winCalls[0], winner)
}
if store.winCalls[0] != winner {
t.Errorf("did not store correct winner got %q want %q", store.winCalls[0], winner)
}
}
```
@ -280,13 +280,13 @@ The test should now read like so
```go
func TestCLI(t *testing.T) {
in := strings.NewReader("Chris wins\n")
playerStore := &StubPlayerStore{}
in := strings.NewReader("Chris wins\n")
playerStore := &StubPlayerStore{}
cli := &CLI{playerStore, in}
cli.PlayPoker()
cli := &CLI{playerStore, in}
cli.PlayPoker()
assertPlayerWin(t, playerStore, "Chris")
assertPlayerWin(t, playerStore, "Chris")
}
```
@ -297,25 +297,25 @@ Now let's write _another_ test with different user input to force us into actual
```go
func TestCLI(t *testing.T) {
t.Run("record chris win from user input", func(t *testing.T) {
in := strings.NewReader("Chris wins\n")
playerStore := &StubPlayerStore{}
t.Run("record chris win from user input", func(t *testing.T) {
in := strings.NewReader("Chris wins\n")
playerStore := &StubPlayerStore{}
cli := &CLI{playerStore, in}
cli.PlayPoker()
cli := &CLI{playerStore, in}
cli.PlayPoker()
assertPlayerWin(t, playerStore, "Chris")
})
assertPlayerWin(t, playerStore, "Chris")
})
t.Run("record cleo win from user input", func(t *testing.T) {
in := strings.NewReader("Cleo wins\n")
playerStore := &StubPlayerStore{}
t.Run("record cleo win from user input", func(t *testing.T) {
in := strings.NewReader("Cleo wins\n")
playerStore := &StubPlayerStore{}
cli := &CLI{playerStore, in}
cli.PlayPoker()
cli := &CLI{playerStore, in}
cli.PlayPoker()
assertPlayerWin(t, playerStore, "Cleo")
})
assertPlayerWin(t, playerStore, "Cleo")
})
}
```
@ -343,18 +343,18 @@ Update the code to the following
```go
type CLI struct {
playerStore PlayerStore
in io.Reader
playerStore PlayerStore
in io.Reader
}
func (cli *CLI) PlayPoker() {
reader := bufio.NewScanner(cli.in)
reader.Scan()
cli.playerStore.RecordWin(extractWinner(reader.Text()))
cli.playerStore.RecordWin(extractWinner(reader.Text()))
}
func extractWinner(userInput string) string {
return strings.Replace(userInput, " wins", "", 1)
return strings.Replace(userInput, " wins", "", 1)
}
```
@ -371,32 +371,32 @@ In `main.go` add the following and run it. (you may have to adjust the path of t
package main
import (
"fmt"
"github.com/quii/learn-go-with-tests/command-line/v3"
"log"
"os"
"fmt"
"github.com/quii/learn-go-with-tests/command-line/v3"
"log"
"os"
)
const dbFileName = "game.db.json"
func main() {
fmt.Println("Let's play poker")
fmt.Println("Type {Name} wins to record a win")
fmt.Println("Let's play poker")
fmt.Println("Type {Name} wins to record a win")
db, err := os.OpenFile(dbFileName, os.O_RDWR|os.O_CREATE, 0666)
db, err := os.OpenFile(dbFileName, os.O_RDWR|os.O_CREATE, 0666)
if err != nil {
log.Fatalf("problem opening %s %v", dbFileName, err)
}
if err != nil {
log.Fatalf("problem opening %s %v", dbFileName, err)
}
store, err := poker.NewFileSystemPlayerStore(db)
store, err := poker.NewFileSystemPlayerStore(db)
if err != nil {
log.Fatalf("problem creating file system player store, %v ", err)
}
if err != nil {
log.Fatalf("problem creating file system player store, %v ", err)
}
game := poker.CLI{store, os.Stdin}
game.PlayPoker()
game := poker.CLI{store, os.Stdin}
game.PlayPoker()
}
```
@ -454,34 +454,34 @@ package poker
import "testing"
type StubPlayerStore struct {
scores map[string]int
winCalls []string
league []Player
scores map[string]int
winCalls []string
league []Player
}
func (s *StubPlayerStore) GetPlayerScore(name string) int {
score := s.scores[name]
return score
score := s.scores[name]
return score
}
func (s *StubPlayerStore) RecordWin(name string) {
s.winCalls = append(s.winCalls, name)
s.winCalls = append(s.winCalls, name)
}
func (s *StubPlayerStore) GetLeague() League {
return s.league
return s.league
}
func AssertPlayerWin(t *testing.T, store *StubPlayerStore, winner string) {
t.Helper()
t.Helper()
if len(store.winCalls) != 1 {
t.Fatalf("got %d calls to RecordWin want %d", len(store.winCalls), 1)
}
if len(store.winCalls) != 1 {
t.Fatalf("got %d calls to RecordWin want %d", len(store.winCalls), 1)
}
if store.winCalls[0] != winner {
t.Errorf("did not store correct winner got %q want %q", store.winCalls[0], winner)
}
if store.winCalls[0] != winner {
t.Errorf("did not store correct winner got %q want %q", store.winCalls[0], winner)
}
}
// todo for you - the rest of the helpers
@ -494,25 +494,25 @@ In our `CLI` test you'll need to call the code as if you were using it within a
```go
func TestCLI(t *testing.T) {
t.Run("record chris win from user input", func(t *testing.T) {
in := strings.NewReader("Chris wins\n")
playerStore := &poker.StubPlayerStore{}
t.Run("record chris win from user input", func(t *testing.T) {
in := strings.NewReader("Chris wins\n")
playerStore := &poker.StubPlayerStore{}
cli := &poker.CLI{playerStore, in}
cli.PlayPoker()
cli := &poker.CLI{playerStore, in}
cli.PlayPoker()
poker.AssertPlayerWin(t, playerStore, "Chris")
})
poker.AssertPlayerWin(t, playerStore, "Chris")
})
t.Run("record cleo win from user input", func(t *testing.T) {
in := strings.NewReader("Cleo wins\n")
playerStore := &poker.StubPlayerStore{}
t.Run("record cleo win from user input", func(t *testing.T) {
in := strings.NewReader("Cleo wins\n")
playerStore := &poker.StubPlayerStore{}
cli := &poker.CLI{playerStore, in}
cli.PlayPoker()
cli := &poker.CLI{playerStore, in}
cli.PlayPoker()
poker.AssertPlayerWin(t, playerStore, "Cleo")
})
poker.AssertPlayerWin(t, playerStore, "Cleo")
})
}
```
@ -604,25 +604,25 @@ Now refactor both of our applications to use this function to create the store.
package main
import (
"github.com/quii/learn-go-with-tests/command-line/v3"
"log"
"os"
"fmt"
"fmt"
"github.com/quii/learn-go-with-tests/command-line/v3"
"log"
"os"
)
const dbFileName = "game.db.json"
func main() {
store, close, err := poker.FileSystemPlayerStoreFromFile(dbFileName)
store, close, err := poker.FileSystemPlayerStoreFromFile(dbFileName)
if err != nil {
log.Fatal(err)
}
defer close()
if err != nil {
log.Fatal(err)
}
defer close()
fmt.Println("Let's play poker")
fmt.Println("Type {Name} wins to record a win")
poker.NewCLI(store, os.Stdin).PlayPoker()
fmt.Println("Let's play poker")
fmt.Println("Type {Name} wins to record a win")
poker.NewCLI(store, os.Stdin).PlayPoker()
}
```
@ -632,26 +632,26 @@ func main() {
package main
import (
"github.com/quii/learn-go-with-tests/command-line/v3"
"log"
"net/http"
"github.com/quii/learn-go-with-tests/command-line/v3"
"log"
"net/http"
)
const dbFileName = "game.db.json"
func main() {
store, close, err := poker.FileSystemPlayerStoreFromFile(dbFileName)
store, close, err := poker.FileSystemPlayerStoreFromFile(dbFileName)
if err != nil {
log.Fatal(err)
}
defer close()
if err != nil {
log.Fatal(err)
}
defer close()
server := poker.NewPlayerServer(store)
server := poker.NewPlayerServer(store)
if err := http.ListenAndServe(":5000", server); err != nil {
log.Fatalf("could not listen on port 5000 %v", err)
}
if err := http.ListenAndServe(":5000", server); err != nil {
log.Fatalf("could not listen on port 5000 %v", err)
}
}
```

View File

@ -11,13 +11,13 @@ package concurrency
type WebsiteChecker func(string) bool
func CheckWebsites(wc WebsiteChecker, urls []string) map[string]bool {
results := make(map[string]bool)
results := make(map[string]bool)
for _, url := range urls {
results[url] = wc(url)
}
for _, url := range urls {
results[url] = wc(url)
}
return results
return results
}
```
@ -36,35 +36,35 @@ Here's the test they've written:
package concurrency
import (
"reflect"
"testing"
"reflect"
"testing"
)
func mockWebsiteChecker(url string) bool {
if url == "waat://furhurterwe.geds" {
return false
}
return true
if url == "waat://furhurterwe.geds" {
return false
}
return true
}
func TestCheckWebsites(t *testing.T) {
websites := []string{
"http://google.com",
"http://blog.gypsydave5.com",
"waat://furhurterwe.geds",
}
websites := []string{
"http://google.com",
"http://blog.gypsydave5.com",
"waat://furhurterwe.geds",
}
want := map[string]bool{
"http://google.com": true,
"http://blog.gypsydave5.com": true,
"waat://furhurterwe.geds": false,
}
want := map[string]bool{
"http://google.com": true,
"http://blog.gypsydave5.com": true,
"waat://furhurterwe.geds": false,
}
got := CheckWebsites(mockWebsiteChecker, websites)
got := CheckWebsites(mockWebsiteChecker, websites)
if !reflect.DeepEqual(want, got) {
t.Fatalf("Wanted %v, got %v", want, got)
}
if !reflect.DeepEqual(want, got) {
t.Fatalf("Wanted %v, got %v", want, got)
}
}
```
@ -81,24 +81,24 @@ effect of our changes.
package concurrency
import (
"testing"
"time"
"testing"
"time"
)
func slowStubWebsiteChecker(_ string) bool {
time.Sleep(20 * time.Millisecond)
return true
time.Sleep(20 * time.Millisecond)
return true
}
func BenchmarkCheckWebsites(b *testing.B) {
urls := make([]string, 100)
for i := 0; i < len(urls); i++ {
urls[i] = "a url"
}
urls := make([]string, 100)
for i := 0; i < len(urls); i++ {
urls[i] = "a url"
}
for i := 0; i < b.N; i++ {
CheckWebsites(slowStubWebsiteChecker, urls)
}
for i := 0; i < b.N; i++ {
CheckWebsites(slowStubWebsiteChecker, urls)
}
}
```
@ -158,15 +158,15 @@ package concurrency
type WebsiteChecker func(string) bool
func CheckWebsites(wc WebsiteChecker, urls []string) map[string]bool {
results := make(map[string]bool)
results := make(map[string]bool)
for _, url := range urls {
go func() {
results[url] = wc(url)
}()
}
for _, url := range urls {
go func() {
results[url] = wc(url)
}()
}
return results
return results
}
```
@ -228,17 +228,17 @@ import "time"
type WebsiteChecker func(string) bool
func CheckWebsites(wc WebsiteChecker, urls []string) map[string]bool {
results := make(map[string]bool)
results := make(map[string]bool)
for _, url := range urls {
go func() {
results[url] = wc(url)
}()
}
for _, url := range urls {
go func() {
results[url] = wc(url)
}()
}
time.Sleep(2 * time.Second)
time.Sleep(2 * time.Second)
return results
return results
}
```
@ -266,23 +266,23 @@ To fix this:
package concurrency
import (
"time"
"time"
)
type WebsiteChecker func(string) bool
func CheckWebsites(wc WebsiteChecker, urls []string) map[string]bool {
results := make(map[string]bool)
results := make(map[string]bool)
for _, url := range urls {
go func(u string) {
results[u] = wc(u)
}(url)
}
for _, url := range urls {
go func(u string) {
results[u] = wc(u)
}(url)
}
time.Sleep(2 * time.Second)
time.Sleep(2 * time.Second)
return results
return results
}
```
@ -405,26 +405,26 @@ package concurrency
type WebsiteChecker func(string) bool
type result struct {
string
bool
string
bool
}
func CheckWebsites(wc WebsiteChecker, urls []string) map[string]bool {
results := make(map[string]bool)
resultChannel := make(chan result)
results := make(map[string]bool)
resultChannel := make(chan result)
for _, url := range urls {
go func(u string) {
resultChannel <- result{u, wc(u)}
}(url)
}
for _, url := range urls {
go func(u string) {
resultChannel <- result{u, wc(u)}
}(url)
}
for i := 0; i < len(urls); i++ {
result := <-resultChannel
results[result.string] = result.bool
}
for i := 0; i < len(urls); i++ {
result := <-resultChannel
results[result.string] = result.bool
}
return results
return results
}
```

View File

@ -17,7 +17,7 @@ Just to recap, here is what that function could look like
```go
func Greet(name string) {
fmt.Printf("Hello, %s", name)
fmt.Printf("Hello, %s", name)
}
```
@ -34,7 +34,7 @@ If you look at the source code of `fmt.Printf` you can see a way for us to hook
```go
// It returns the number of bytes written and any write error encountered.
func Printf(format string, a ...interface{}) (n int, err error) {
return Fprintf(os.Stdout, format, a...)
return Fprintf(os.Stdout, format, a...)
}
```
@ -44,11 +44,11 @@ What exactly _is_ an `os.Stdout`? What does `Fprintf` expect to get passed to it
```go
func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
p := newPrinter()
p.doPrintf(format, a)
n, err = w.Write(p.buf)
p.free()
return
p := newPrinter()
p.doPrintf(format, a)
n, err = w.Write(p.buf)
p.free()
return
}
```
@ -56,7 +56,7 @@ An `io.Writer`
```go
type Writer interface {
Write(p []byte) (n int, err error)
Write(p []byte) (n int, err error)
}
```
@ -68,15 +68,15 @@ So we know under the covers we're ultimately using `Writer` to send our greeting
```go
func TestGreet(t *testing.T) {
buffer := bytes.Buffer{}
Greet(&buffer,"Chris")
buffer := bytes.Buffer{}
Greet(&buffer, "Chris")
got := buffer.String()
want := "Hello, Chris"
got := buffer.String()
want := "Hello, Chris"
if got != want {
t.Errorf("got %q want %q", got, want)
}
if got != want {
t.Errorf("got %q want %q", got, want)
}
}
```
@ -100,7 +100,7 @@ _Listen to the compiler_ and fix the problem.
```go
func Greet(writer *bytes.Buffer, name string) {
fmt.Printf("Hello, %s", name)
fmt.Printf("Hello, %s", name)
}
```
@ -114,7 +114,7 @@ Use the writer to send the greeting to the buffer in our test. Remember `fmt.Fpr
```go
func Greet(writer *bytes.Buffer, name string) {
fmt.Fprintf(writer, "Hello, %s", name)
fmt.Fprintf(writer, "Hello, %s", name)
}
```
@ -128,7 +128,7 @@ To demonstrate this, try wiring up the `Greet` function into a Go application wh
```go
func main() {
Greet(os.Stdout, "Elodie")
Greet(os.Stdout, "Elodie")
}
```
@ -152,7 +152,7 @@ func Greet(writer io.Writer, name string) {
}
func main() {
Greet(os.Stdout, "Elodie")
Greet(os.Stdout, "Elodie")
}
```
@ -168,21 +168,21 @@ Run the following
package main
import (
"fmt"
"io"
"net/http"
"fmt"
"io"
"net/http"
)
func Greet(writer io.Writer, name string) {
fmt.Fprintf(writer, "Hello, %s", name)
fmt.Fprintf(writer, "Hello, %s", name)
}
func MyGreeterHandler(w http.ResponseWriter, r *http.Request) {
Greet(w, "world")
Greet(w, "world")
}
func main() {
http.ListenAndServe(":5000", http.HandlerFunc(MyGreeterHandler))
http.ListenAndServe(":5000", http.HandlerFunc(MyGreeterHandler))
}
```

View File

@ -37,23 +37,23 @@ As Pedro says, we _could_ write a test for the status error like so.
```go
t.Run("when you don't get a 200 you get a status error", func(t *testing.T) {
svr := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
res.WriteHeader(http.StatusTeapot)
}))
defer svr.Close()
svr := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
res.WriteHeader(http.StatusTeapot)
}))
defer svr.Close()
_, err := DumbGetter(svr.URL)
_, err := DumbGetter(svr.URL)
if err == nil {
t.Fatal("expected an error")
}
if err == nil {
t.Fatal("expected an error")
}
want := fmt.Sprintf("did not get 200 from %s, got %d", svr.URL, http.StatusTeapot)
got := err.Error()
want := fmt.Sprintf("did not get 200 from %s, got %d", svr.URL, http.StatusTeapot)
got := err.Error()
if got != want {
t.Errorf(`got "%v", want "%v"`, got, want)
}
if got != want {
t.Errorf(`got "%v", want "%v"`, got, want)
}
})
```
@ -95,28 +95,28 @@ Let's change our existing test to reflect this need
```go
t.Run("when you don't get a 200 you get a status error", func(t *testing.T) {
svr := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
res.WriteHeader(http.StatusTeapot)
}))
defer svr.Close()
svr := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
res.WriteHeader(http.StatusTeapot)
}))
defer svr.Close()
_, err := DumbGetter(svr.URL)
_, err := DumbGetter(svr.URL)
if err == nil {
t.Fatal("expected an error")
}
if err == nil {
t.Fatal("expected an error")
}
got, isStatusErr := err.(BadStatusError)
got, isStatusErr := err.(BadStatusError)
if !isStatusErr {
t.Fatalf("was not a BadStatusError, got %T", err)
}
if !isStatusErr {
t.Fatalf("was not a BadStatusError, got %T", err)
}
want := BadStatusError{URL:svr.URL, Status:http.StatusTeapot}
want := BadStatusError{URL: svr.URL, Status: http.StatusTeapot}
if got != want {
t.Errorf("got %v, want %v", got, want)
}
if got != want {
t.Errorf("got %v, want %v", got, want)
}
})
```
@ -144,7 +144,7 @@ Let's fix `DumbGetter` by updating our error handling code to use our type
```go
if res.StatusCode != http.StatusOK {
return "", BadStatusError{URL: url, Status: res.StatusCode}
return "", BadStatusError{URL: url, Status: res.StatusCode}
}
```
@ -169,28 +169,28 @@ As of Go 1.13 there are new ways to work with errors in the standard library whi
```go
t.Run("when you don't get a 200 you get a status error", func(t *testing.T) {
svr := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
res.WriteHeader(http.StatusTeapot)
}))
defer svr.Close()
svr := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
res.WriteHeader(http.StatusTeapot)
}))
defer svr.Close()
_, err := DumbGetter(svr.URL)
_, err := DumbGetter(svr.URL)
if err == nil {
t.Fatal("expected an error")
}
if err == nil {
t.Fatal("expected an error")
}
var got BadStatusError
isBadStatusError := errors.As(err, &got)
want := BadStatusError{URL: svr.URL, Status: http.StatusTeapot}
var got BadStatusError
isBadStatusError := errors.As(err, &got)
want := BadStatusError{URL: svr.URL, Status: http.StatusTeapot}
if !isBadStatusError {
t.Fatalf("was not a BadStatusError, got %T", err)
}
if !isBadStatusError {
t.Fatalf("was not a BadStatusError, got %T", err)
}
if got != want {
t.Errorf("got %v, want %v", got, want)
}
if got != want {
t.Errorf("got %v, want %v", got, want)
}
})
```

View File

@ -20,7 +20,7 @@ package main
import "fmt"
func main() {
fmt.Println("Hello, world")
fmt.Println("Hello, world")
}
```
@ -44,11 +44,11 @@ package main
import "fmt"
func Hello() string {
return "Hello, world"
return "Hello, world"
}
func main() {
fmt.Println(Hello())
fmt.Println(Hello())
}
```
@ -62,12 +62,12 @@ package main
import "testing"
func TestHello(t *testing.T) {
got := Hello()
want := "Hello, world"
got := Hello()
want := "Hello, world"
if got != want {
t.Errorf("got %q want %q", got, want)
}
if got != want {
t.Errorf("got %q want %q", got, want)
}
}
```
@ -126,12 +126,12 @@ package main
import "testing"
func TestHello(t *testing.T) {
got := Hello("Chris")
want := "Hello, Chris"
got := Hello("Chris")
want := "Hello, Chris"
if got != want {
t.Errorf("got %q want %q", got, want)
}
if got != want {
t.Errorf("got %q want %q", got, want)
}
}
```
@ -151,7 +151,7 @@ Edit the `Hello` function to accept an argument of type string
```go
func Hello(name string) string {
return "Hello, world"
return "Hello, world"
}
```
@ -159,7 +159,7 @@ If you try and run your tests again your `main.go` will fail to compile because
```go
func main() {
fmt.Println(Hello("world"))
fmt.Println(Hello("world"))
}
```
@ -175,7 +175,7 @@ Let's make the test pass by using the name argument and concatenate it with `Hel
```go
func Hello(name string) string {
return "Hello, " + name
return "Hello, " + name
}
```
@ -205,7 +205,7 @@ We can now refactor our code
const englishHelloPrefix = "Hello, "
func Hello(name string) string {
return englishHelloPrefix + name
return englishHelloPrefix + name
}
```
@ -224,23 +224,23 @@ Start by writing a new failing test
```go
func TestHello(t *testing.T) {
t.Run("saying hello to people", func(t *testing.T) {
got := Hello("Chris")
want := "Hello, Chris"
t.Run("saying hello to people", func(t *testing.T) {
got := Hello("Chris")
want := "Hello, Chris"
if got != want {
t.Errorf("got %q want %q", got, want)
}
})
if got != want {
t.Errorf("got %q want %q", got, want)
}
})
t.Run("say 'Hello, World' when an empty string is supplied", func(t *testing.T) {
got := Hello("")
want := "Hello, World"
t.Run("say 'Hello, World' when an empty string is supplied", func(t *testing.T) {
got := Hello("")
want := "Hello, World"
if got != want {
t.Errorf("got %q want %q", got, want)
}
})
if got != want {
t.Errorf("got %q want %q", got, want)
}
})
}
```
@ -260,24 +260,24 @@ We can and should refactor our tests.
```go
func TestHello(t *testing.T) {
assertCorrectMessage := func(t *testing.T, got, want string) {
t.Helper()
if got != want {
t.Errorf("got %q want %q", got, want)
}
}
assertCorrectMessage := func(t *testing.T, got, want string) {
t.Helper()
if got != want {
t.Errorf("got %q want %q", got, want)
}
}
t.Run("saying hello to people", func(t *testing.T) {
got := Hello("Chris")
want := "Hello, Chris"
assertCorrectMessage(t, got, want)
})
t.Run("saying hello to people", func(t *testing.T) {
got := Hello("Chris")
want := "Hello, Chris"
assertCorrectMessage(t, got, want)
})
t.Run("empty string defaults to 'World'", func(t *testing.T) {
got := Hello("")
want := "Hello, World"
assertCorrectMessage(t, got, want)
})
t.Run("empty string defaults to 'World'", func(t *testing.T) {
got := Hello("")
want := "Hello, World"
assertCorrectMessage(t, got, want)
})
}
```
@ -294,10 +294,10 @@ Now that we have a well-written failing test, let's fix the code, using an `if`.
const englishHelloPrefix = "Hello, "
func Hello(name string) string {
if name == "" {
name = "World"
}
return englishHelloPrefix + name
if name == "" {
name = "World"
}
return englishHelloPrefix + name
}
```
@ -337,11 +337,11 @@ We should be confident that we can use TDD to flesh out this functionality easil
Write a test for a user passing in Spanish. Add it to the existing suite.
```go
t.Run("in Spanish", func(t *testing.T) {
got := Hello("Elodie", "Spanish")
want := "Hola, Elodie"
assertCorrectMessage(t, got, want)
})
t.Run("in Spanish", func(t *testing.T) {
got := Hello("Elodie", "Spanish")
want := "Hola, Elodie"
assertCorrectMessage(t, got, want)
})
```
Remember not to cheat! _Test first_. When you try and run the test, the compiler _should_ complain because you are calling `Hello` with two arguments rather than one.
@ -356,10 +356,10 @@ Fix the compilation problems by adding another string argument to `Hello`
```go
func Hello(name string, language string) string {
if name == "" {
name = "World"
}
return englishHelloPrefix + name
if name == "" {
name = "World"
}
return englishHelloPrefix + name
}
```
@ -381,15 +381,15 @@ We can use `if` here to check the language is equal to "Spanish" and if so chang
```go
func Hello(name string, language string) string {
if name == "" {
name = "World"
}
if name == "" {
name = "World"
}
if language == "Spanish" {
return "Hola, " + name
}
if language == "Spanish" {
return "Hola, " + name
}
return englishHelloPrefix + name
return englishHelloPrefix + name
}
```
@ -403,15 +403,15 @@ const englishHelloPrefix = "Hello, "
const spanishHelloPrefix = "Hola, "
func Hello(name string, language string) string {
if name == "" {
name = "World"
}
if name == "" {
name = "World"
}
if language == spanish {
return spanishHelloPrefix + name
}
if language == spanish {
return spanishHelloPrefix + name
}
return englishHelloPrefix + name
return englishHelloPrefix + name
}
```
@ -425,19 +425,19 @@ You may have written something that looks roughly like this
```go
func Hello(name string, language string) string {
if name == "" {
name = "World"
}
if name == "" {
name = "World"
}
if language == spanish {
return spanishHelloPrefix + name
}
if language == spanish {
return spanishHelloPrefix + name
}
if language == french {
return frenchHelloPrefix + name
}
if language == french {
return frenchHelloPrefix + name
}
return englishHelloPrefix + name
return englishHelloPrefix + name
}
```
@ -447,20 +447,20 @@ When you have lots of `if` statements checking a particular value it is common t
```go
func Hello(name string, language string) string {
if name == "" {
name = "World"
}
if name == "" {
name = "World"
}
prefix := englishHelloPrefix
prefix := englishHelloPrefix
switch language {
case french:
prefix = frenchHelloPrefix
case spanish:
prefix = spanishHelloPrefix
}
switch language {
case french:
prefix = frenchHelloPrefix
case spanish:
prefix = spanishHelloPrefix
}
return prefix + name
return prefix + name
}
```
@ -472,23 +472,23 @@ You could argue that maybe our function is getting a little big. The simplest re
```go
func Hello(name string, language string) string {
if name == "" {
name = "World"
}
if name == "" {
name = "World"
}
return greetingPrefix(language) + name
return greetingPrefix(language) + name
}
func greetingPrefix(language string) (prefix string) {
switch language {
case french:
prefix = frenchHelloPrefix
case spanish:
prefix = spanishHelloPrefix
default:
prefix = englishHelloPrefix
}
return
switch language {
case french:
prefix = frenchHelloPrefix
case spanish:
prefix = spanishHelloPrefix
default:
prefix = englishHelloPrefix
}
return
}
```

View File

@ -210,10 +210,10 @@ func NewUserServer(service UserService) *UserServer {
return &UserServer{service: service}
}
func (u *UserServer) RegisterUser(w http.ResponseWriter, r *http.Request) {
func (u *UserServer) RegisterUser(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
// request parsing and validation
// request parsing and validation
var newUser User
err := json.NewDecoder(r.Body).Decode(&newUser)
@ -222,10 +222,10 @@ func (u *UserServer) RegisterUser(w http.ResponseWriter, r *http.Request) {
return
}
// call a service thing to take care of the hard work
// call a service thing to take care of the hard work
insertedID, err := u.service.Register(newUser)
// depending on what we get back, respond accordingly
// depending on what we get back, respond accordingly
if err != nil {
//todo: handle different kinds of errors differently
http.Error(w, fmt.Sprintf("problem registering new user: %v", err), http.StatusInternalServerError)

View File

@ -57,7 +57,7 @@ This will start a web server listening on a port, creating a goroutine for every
```go
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
ServeHTTP(ResponseWriter, *Request)
}
```
@ -67,19 +67,19 @@ Let's write a test for a function `PlayerServer` that takes in those two argumen
```go
func TestGETPlayers(t *testing.T) {
t.Run("returns Pepper's score", func(t *testing.T) {
request, _ := http.NewRequest(http.MethodGet, "/players/Pepper", nil)
response := httptest.NewRecorder()
t.Run("returns Pepper's score", func(t *testing.T) {
request, _ := http.NewRequest(http.MethodGet, "/players/Pepper", nil)
response := httptest.NewRecorder()
PlayerServer(response, request)
PlayerServer(response, request)
got := response.Body.String()
want := "20"
got := response.Body.String()
want := "20"
if got != want {
t.Errorf("got %q, want %q", got, want)
}
})
if got != want {
t.Errorf("got %q, want %q", got, want)
}
})
}
```
@ -134,7 +134,7 @@ From the DI chapter, we touched on HTTP servers with a `Greet` function. We lear
```go
func PlayerServer(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "20")
fmt.Fprint(w, "20")
}
```
@ -153,15 +153,15 @@ Create a new file for our application and put this code in.
package main
import (
"log"
"net/http"
"log"
"net/http"
)
func main() {
handler := http.HandlerFunc(PlayerServer)
if err := http.ListenAndServe(":5000", handler); err != nil {
log.Fatalf("could not listen on port 5000 %v", err)
}
handler := http.HandlerFunc(PlayerServer)
if err := http.ListenAndServe(":5000", handler); err != nil {
log.Fatalf("could not listen on port 5000 %v", err)
}
}
```
@ -196,17 +196,17 @@ We'll add another subtest to our suite which tries to get the score of a differe
```go
t.Run("returns Floyd's score", func(t *testing.T) {
request, _ := http.NewRequest(http.MethodGet, "/players/Floyd", nil)
response := httptest.NewRecorder()
request, _ := http.NewRequest(http.MethodGet, "/players/Floyd", nil)
response := httptest.NewRecorder()
PlayerServer(response, request)
PlayerServer(response, request)
got := response.Body.String()
want := "10"
got := response.Body.String()
want := "10"
if got != want {
t.Errorf("got %q, want %q", got, want)
}
if got != want {
t.Errorf("got %q, want %q", got, want)
}
})
```
@ -230,17 +230,17 @@ Remember we are just trying to take as small as steps as reasonably possible, so
```go
func PlayerServer(w http.ResponseWriter, r *http.Request) {
player := strings.TrimPrefix(r.URL.Path, "/players/")
player := strings.TrimPrefix(r.URL.Path, "/players/")
if player == "Pepper" {
fmt.Fprint(w, "20")
return
}
if player == "Pepper" {
fmt.Fprint(w, "20")
return
}
if player == "Floyd" {
fmt.Fprint(w, "10")
return
}
if player == "Floyd" {
fmt.Fprint(w, "10")
return
}
}
```
@ -258,21 +258,21 @@ We can simplify the `PlayerServer` by separating out the score retrieval into a
```go
func PlayerServer(w http.ResponseWriter, r *http.Request) {
player := strings.TrimPrefix(r.URL.Path, "/players/")
player := strings.TrimPrefix(r.URL.Path, "/players/")
fmt.Fprint(w, GetPlayerScore(player))
fmt.Fprint(w, GetPlayerScore(player))
}
func GetPlayerScore(name string) string {
if name == "Pepper" {
return "20"
}
if name == "Pepper" {
return "20"
}
if name == "Floyd" {
return "10"
}
if name == "Floyd" {
return "10"
}
return ""
return ""
}
```
@ -280,35 +280,35 @@ And we can DRY up some of the code in the tests by making some helpers
```go
func TestGETPlayers(t *testing.T) {
t.Run("returns Pepper's score", func(t *testing.T) {
request := newGetScoreRequest("Pepper")
response := httptest.NewRecorder()
t.Run("returns Pepper's score", func(t *testing.T) {
request := newGetScoreRequest("Pepper")
response := httptest.NewRecorder()
PlayerServer(response, request)
PlayerServer(response, request)
assertResponseBody(t, response.Body.String(), "20")
})
assertResponseBody(t, response.Body.String(), "20")
})
t.Run("returns Floyd's score", func(t *testing.T) {
request := newGetScoreRequest("Floyd")
response := httptest.NewRecorder()
t.Run("returns Floyd's score", func(t *testing.T) {
request := newGetScoreRequest("Floyd")
response := httptest.NewRecorder()
PlayerServer(response, request)
PlayerServer(response, request)
assertResponseBody(t, response.Body.String(), "10")
})
assertResponseBody(t, response.Body.String(), "10")
})
}
func newGetScoreRequest(name string) *http.Request {
req, _ := http.NewRequest(http.MethodGet, fmt.Sprintf("/players/%s", name), nil)
return req
req, _ := http.NewRequest(http.MethodGet, fmt.Sprintf("/players/%s", name), nil)
return req
}
func assertResponseBody(t *testing.T, got, want string) {
t.Helper()
if got != want {
t.Errorf("response body is wrong, got %q want %q", got, want)
}
t.Helper()
if got != want {
t.Errorf("response body is wrong, got %q want %q", got, want)
}
}
```
@ -322,7 +322,7 @@ Let's move our function we re-factored to be an interface instead
```go
type PlayerStore interface {
GetPlayerScore(name string) int
GetPlayerScore(name string) int
}
```
@ -330,7 +330,7 @@ For our `PlayerServer` to be able to use a `PlayerStore`, it will need a referen
```go
type PlayerServer struct {
store PlayerStore
store PlayerStore
}
```
@ -338,8 +338,8 @@ Finally, we will now implement the `Handler` interface by adding a method to our
```go
func (p *PlayerServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
player := strings.TrimPrefix(r.URL.Path, "/players/")
fmt.Fprint(w, p.store.GetPlayerScore(player))
player := strings.TrimPrefix(r.URL.Path, "/players/")
fmt.Fprint(w, p.store.GetPlayerScore(player))
}
```
@ -349,16 +349,16 @@ Here is the full code listing of our server
```go
type PlayerStore interface {
GetPlayerScore(name string) int
GetPlayerScore(name string) int
}
type PlayerServer struct {
store PlayerStore
store PlayerStore
}
func (p *PlayerServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
player := strings.TrimPrefix(r.URL.Path, "/players/")
fmt.Fprint(w, p.store.GetPlayerScore(player))
player := strings.TrimPrefix(r.URL.Path, "/players/")
fmt.Fprint(w, p.store.GetPlayerScore(player))
}
```
@ -372,25 +372,25 @@ We need to change our tests to instead create a new instance of our `PlayerServe
```go
func TestGETPlayers(t *testing.T) {
server := &PlayerServer{}
server := &PlayerServer{}
t.Run("returns Pepper's score", func(t *testing.T) {
request := newGetScoreRequest("Pepper")
response := httptest.NewRecorder()
t.Run("returns Pepper's score", func(t *testing.T) {
request := newGetScoreRequest("Pepper")
response := httptest.NewRecorder()
server.ServeHTTP(response, request)
server.ServeHTTP(response, request)
assertResponseBody(t, response.Body.String(), "20")
})
assertResponseBody(t, response.Body.String(), "20")
})
t.Run("returns Floyd's score", func(t *testing.T) {
request := newGetScoreRequest("Floyd")
response := httptest.NewRecorder()
t.Run("returns Floyd's score", func(t *testing.T) {
request := newGetScoreRequest("Floyd")
response := httptest.NewRecorder()
server.ServeHTTP(response, request)
server.ServeHTTP(response, request)
assertResponseBody(t, response.Body.String(), "10")
})
assertResponseBody(t, response.Body.String(), "10")
})
}
```
@ -404,11 +404,11 @@ Now `main.go` won't compile for the same reason.
```go
func main() {
server := &PlayerServer{}
server := &PlayerServer{}
if err := http.ListenAndServe(":5000", server); err != nil {
log.Fatalf("could not listen on port 5000 %v", err)
}
if err := http.ListenAndServe(":5000", server); err != nil {
log.Fatalf("could not listen on port 5000 %v", err)
}
}
```
@ -424,12 +424,12 @@ This is because we have not passed in a `PlayerStore` in our tests. We'll need t
```go
type StubPlayerStore struct {
scores map[string]int
scores map[string]int
}
func (s *StubPlayerStore) GetPlayerScore(name string) int {
score := s.scores[name]
return score
score := s.scores[name]
return score
}
```
@ -437,31 +437,31 @@ A `map` is a quick and easy way of making a stub key/value store for our tests.
```go
func TestGETPlayers(t *testing.T) {
store := StubPlayerStore{
map[string]int{
"Pepper": 20,
"Floyd": 10,
},
}
server := &PlayerServer{&store}
store := StubPlayerStore{
map[string]int{
"Pepper": 20,
"Floyd": 10,
},
}
server := &PlayerServer{&store}
t.Run("returns Pepper's score", func(t *testing.T) {
request := newGetScoreRequest("Pepper")
response := httptest.NewRecorder()
t.Run("returns Pepper's score", func(t *testing.T) {
request := newGetScoreRequest("Pepper")
response := httptest.NewRecorder()
server.ServeHTTP(response, request)
server.ServeHTTP(response, request)
assertResponseBody(t, response.Body.String(), "20")
})
assertResponseBody(t, response.Body.String(), "20")
})
t.Run("returns Floyd's score", func(t *testing.T) {
request := newGetScoreRequest("Floyd")
response := httptest.NewRecorder()
t.Run("returns Floyd's score", func(t *testing.T) {
request := newGetScoreRequest("Floyd")
response := httptest.NewRecorder()
server.ServeHTTP(response, request)
server.ServeHTTP(response, request)
assertResponseBody(t, response.Body.String(), "10")
})
assertResponseBody(t, response.Body.String(), "10")
})
}
```
@ -479,15 +479,15 @@ We'll need to make an implementation of one, but that's difficult right now as w
type InMemoryPlayerStore struct{}
func (i *InMemoryPlayerStore) GetPlayerScore(name string) int {
return 123
return 123
}
func main() {
server := &PlayerServer{&InMemoryPlayerStore{}}
server := &PlayerServer{&InMemoryPlayerStore{}}
if err := http.ListenAndServe(":5000", server); err != nil {
log.Fatalf("could not listen on port 5000 %v", err)
}
if err := http.ListenAndServe(":5000", server); err != nil {
log.Fatalf("could not listen on port 5000 %v", err)
}
}
```
@ -507,17 +507,17 @@ Add a missing player scenario to our existing suite
```go
t.Run("returns 404 on missing players", func(t *testing.T) {
request := newGetScoreRequest("Apollo")
response := httptest.NewRecorder()
request := newGetScoreRequest("Apollo")
response := httptest.NewRecorder()
server.ServeHTTP(response, request)
server.ServeHTTP(response, request)
got := response.Code
want := http.StatusNotFound
got := response.Code
want := http.StatusNotFound
if got != want {
t.Errorf("got status %d want %d", got, want)
}
if got != want {
t.Errorf("got status %d want %d", got, want)
}
})
```
@ -533,11 +533,11 @@ t.Run("returns 404 on missing players", func(t *testing.T) {
```go
func (p *PlayerServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
player := strings.TrimPrefix(r.URL.Path, "/players/")
player := strings.TrimPrefix(r.URL.Path, "/players/")
w.WriteHeader(http.StatusNotFound)
w.WriteHeader(http.StatusNotFound)
fmt.Fprint(w, p.store.GetPlayerScore(player))
fmt.Fprint(w, p.store.GetPlayerScore(player))
}
```
@ -553,61 +553,61 @@ Here are the new tests
```go
func TestGETPlayers(t *testing.T) {
store := StubPlayerStore{
map[string]int{
"Pepper": 20,
"Floyd": 10,
},
}
server := &PlayerServer{&store}
store := StubPlayerStore{
map[string]int{
"Pepper": 20,
"Floyd": 10,
},
}
server := &PlayerServer{&store}
t.Run("returns Pepper's score", func(t *testing.T) {
request := newGetScoreRequest("Pepper")
response := httptest.NewRecorder()
t.Run("returns Pepper's score", func(t *testing.T) {
request := newGetScoreRequest("Pepper")
response := httptest.NewRecorder()
server.ServeHTTP(response, request)
server.ServeHTTP(response, request)
assertStatus(t, response.Code, http.StatusOK)
assertResponseBody(t, response.Body.String(), "20")
})
assertStatus(t, response.Code, http.StatusOK)
assertResponseBody(t, response.Body.String(), "20")
})
t.Run("returns Floyd's score", func(t *testing.T) {
request := newGetScoreRequest("Floyd")
response := httptest.NewRecorder()
t.Run("returns Floyd's score", func(t *testing.T) {
request := newGetScoreRequest("Floyd")
response := httptest.NewRecorder()
server.ServeHTTP(response, request)
server.ServeHTTP(response, request)
assertStatus(t, response.Code, http.StatusOK)
assertResponseBody(t, response.Body.String(), "10")
})
assertStatus(t, response.Code, http.StatusOK)
assertResponseBody(t, response.Body.String(), "10")
})
t.Run("returns 404 on missing players", func(t *testing.T) {
request := newGetScoreRequest("Apollo")
response := httptest.NewRecorder()
t.Run("returns 404 on missing players", func(t *testing.T) {
request := newGetScoreRequest("Apollo")
response := httptest.NewRecorder()
server.ServeHTTP(response, request)
server.ServeHTTP(response, request)
assertStatus(t, response.Code, http.StatusNotFound)
})
assertStatus(t, response.Code, http.StatusNotFound)
})
}
func assertStatus(t *testing.T, got, want int) {
t.Helper()
if got != want {
t.Errorf("did not get correct status, got %d, want %d", got, want)
}
t.Helper()
if got != want {
t.Errorf("did not get correct status, got %d, want %d", got, want)
}
}
func newGetScoreRequest(name string) *http.Request {
req, _ := http.NewRequest(http.MethodGet, fmt.Sprintf("/players/%s", name), nil)
return req
req, _ := http.NewRequest(http.MethodGet, fmt.Sprintf("/players/%s", name), nil)
return req
}
func assertResponseBody(t *testing.T, got, want string) {
t.Helper()
if got != want {
t.Errorf("response body is wrong, got %q want %q", got, want)
}
t.Helper()
if got != want {
t.Errorf("response body is wrong, got %q want %q", got, want)
}
}
```
@ -617,15 +617,15 @@ Now our first two tests fail because of the 404 instead of 200, so we can fix `P
```go
func (p *PlayerServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
player := strings.TrimPrefix(r.URL.Path, "/players/")
player := strings.TrimPrefix(r.URL.Path, "/players/")
score := p.store.GetPlayerScore(player)
score := p.store.GetPlayerScore(player)
if score == 0 {
w.WriteHeader(http.StatusNotFound)
}
if score == 0 {
w.WriteHeader(http.StatusNotFound)
}
fmt.Fprint(w, score)
fmt.Fprint(w, score)
}
```
@ -637,19 +637,19 @@ Now that we can retrieve scores from a store it now makes sense to be able to st
```go
func TestStoreWins(t *testing.T) {
store := StubPlayerStore{
map[string]int{},
}
server := &PlayerServer{&store}
store := StubPlayerStore{
map[string]int{},
}
server := &PlayerServer{&store}
t.Run("it returns accepted on POST", func(t *testing.T) {
request, _ := http.NewRequest(http.MethodPost, "/players/Pepper", nil)
response := httptest.NewRecorder()
t.Run("it returns accepted on POST", func(t *testing.T) {
request, _ := http.NewRequest(http.MethodPost, "/players/Pepper", nil)
response := httptest.NewRecorder()
server.ServeHTTP(response, request)
server.ServeHTTP(response, request)
assertStatus(t, response.Code, http.StatusAccepted)
})
assertStatus(t, response.Code, http.StatusAccepted)
})
}
```
@ -670,20 +670,20 @@ Remember we are deliberately committing sins, so an `if` statement based on the
```go
func (p *PlayerServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodPost {
w.WriteHeader(http.StatusAccepted)
return
}
if r.Method == http.MethodPost {
w.WriteHeader(http.StatusAccepted)
return
}
player := strings.TrimPrefix(r.URL.Path, "/players/")
player := strings.TrimPrefix(r.URL.Path, "/players/")
score := p.store.GetPlayerScore(player)
score := p.store.GetPlayerScore(player)
if score == 0 {
w.WriteHeader(http.StatusNotFound)
}
if score == 0 {
w.WriteHeader(http.StatusNotFound)
}
fmt.Fprint(w, score)
fmt.Fprint(w, score)
}
```
@ -694,29 +694,29 @@ The handler is looking a bit muddled now. Let's break the code up to make it eas
```go
func (p *PlayerServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodPost:
p.processWin(w)
case http.MethodGet:
p.showScore(w, r)
}
switch r.Method {
case http.MethodPost:
p.processWin(w)
case http.MethodGet:
p.showScore(w, r)
}
}
func (p *PlayerServer) showScore(w http.ResponseWriter, r *http.Request) {
player := strings.TrimPrefix(r.URL.Path, "/players/")
player := strings.TrimPrefix(r.URL.Path, "/players/")
score := p.store.GetPlayerScore(player)
score := p.store.GetPlayerScore(player)
if score == 0 {
w.WriteHeader(http.StatusNotFound)
}
if score == 0 {
w.WriteHeader(http.StatusNotFound)
}
fmt.Fprint(w, score)
fmt.Fprint(w, score)
}
func (p *PlayerServer) processWin(w http.ResponseWriter) {
w.WriteHeader(http.StatusAccepted)
w.WriteHeader(http.StatusAccepted)
}
```
@ -730,17 +730,17 @@ We can accomplish this by extending our `StubPlayerStore` with a new `RecordWin`
```go
type StubPlayerStore struct {
scores map[string]int
winCalls []string
scores map[string]int
winCalls []string
}
func (s *StubPlayerStore) GetPlayerScore(name string) int {
score := s.scores[name]
return score
score := s.scores[name]
return score
}
func (s *StubPlayerStore) RecordWin(name string) {
s.winCalls = append(s.winCalls, name)
s.winCalls = append(s.winCalls, name)
}
```
@ -748,28 +748,28 @@ Now extend our test to check the number of invocations for a start
```go
func TestStoreWins(t *testing.T) {
store := StubPlayerStore{
map[string]int{},
}
server := &PlayerServer{&store}
store := StubPlayerStore{
map[string]int{},
}
server := &PlayerServer{&store}
t.Run("it records wins when POST", func(t *testing.T) {
request := newPostWinRequest("Pepper")
response := httptest.NewRecorder()
t.Run("it records wins when POST", func(t *testing.T) {
request := newPostWinRequest("Pepper")
response := httptest.NewRecorder()
server.ServeHTTP(response, request)
server.ServeHTTP(response, request)
assertStatus(t, response.Code, http.StatusAccepted)
assertStatus(t, response.Code, http.StatusAccepted)
if len(store.winCalls) != 1 {
t.Errorf("got %d calls to RecordWin want %d", len(store.winCalls), 1)
}
})
if len(store.winCalls) != 1 {
t.Errorf("got %d calls to RecordWin want %d", len(store.winCalls), 1)
}
})
}
func newPostWinRequest(name string) *http.Request {
req, _ := http.NewRequest(http.MethodPost, fmt.Sprintf("/players/%s", name), nil)
return req
req, _ := http.NewRequest(http.MethodPost, fmt.Sprintf("/players/%s", name), nil)
return req
}
```
@ -786,8 +786,8 @@ We need to update our code where we create a `StubPlayerStore` as we've added a
```go
store := StubPlayerStore{
map[string]int{},
nil,
map[string]int{},
nil,
}
```
@ -805,8 +805,8 @@ We need to update `PlayerServer`'s idea of what a `PlayerStore` is by changing t
```go
type PlayerStore interface {
GetPlayerScore(name string) int
RecordWin(name string)
GetPlayerScore(name string) int
RecordWin(name string)
}
```
@ -831,8 +831,8 @@ Now that `PlayerStore` has `RecordWin` we can call it within our `PlayerServer`
```go
func (p *PlayerServer) processWin(w http.ResponseWriter) {
p.store.RecordWin("Bob")
w.WriteHeader(http.StatusAccepted)
p.store.RecordWin("Bob")
w.WriteHeader(http.StatusAccepted)
}
```
@ -842,22 +842,22 @@ Run the tests and it should be passing! Obviously `"Bob"` isn't exactly what we
```go
t.Run("it records wins on POST", func(t *testing.T) {
player := "Pepper"
player := "Pepper"
request := newPostWinRequest(player)
response := httptest.NewRecorder()
request := newPostWinRequest(player)
response := httptest.NewRecorder()
server.ServeHTTP(response, request)
server.ServeHTTP(response, request)
assertStatus(t, response.Code, http.StatusAccepted)
assertStatus(t, response.Code, http.StatusAccepted)
if len(store.winCalls) != 1 {
t.Fatalf("got %d calls to RecordWin want %d", len(store.winCalls), 1)
}
if len(store.winCalls) != 1 {
t.Fatalf("got %d calls to RecordWin want %d", len(store.winCalls), 1)
}
if store.winCalls[0] != player {
t.Errorf("did not store correct winner got %q want %q", store.winCalls[0], player)
}
if store.winCalls[0] != player {
t.Errorf("did not store correct winner got %q want %q", store.winCalls[0], player)
}
})
```
@ -875,9 +875,9 @@ Now that we know there is one element in our `winCalls` slice we can safely refe
```go
func (p *PlayerServer) processWin(w http.ResponseWriter, r *http.Request) {
player := strings.TrimPrefix(r.URL.Path, "/players/")
p.store.RecordWin(player)
w.WriteHeader(http.StatusAccepted)
player := strings.TrimPrefix(r.URL.Path, "/players/")
p.store.RecordWin(player)
w.WriteHeader(http.StatusAccepted)
}
```
@ -889,29 +889,29 @@ We can DRY up this code a bit as we're extracting the player name the same way i
```go
func (p *PlayerServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
player := strings.TrimPrefix(r.URL.Path, "/players/")
player := strings.TrimPrefix(r.URL.Path, "/players/")
switch r.Method {
case http.MethodPost:
p.processWin(w, player)
case http.MethodGet:
p.showScore(w, player)
}
switch r.Method {
case http.MethodPost:
p.processWin(w, player)
case http.MethodGet:
p.showScore(w, player)
}
}
func (p *PlayerServer) showScore(w http.ResponseWriter, player string) {
score := p.store.GetPlayerScore(player)
score := p.store.GetPlayerScore(player)
if score == 0 {
w.WriteHeader(http.StatusNotFound)
}
if score == 0 {
w.WriteHeader(http.StatusNotFound)
}
fmt.Fprint(w, score)
fmt.Fprint(w, score)
}
func (p *PlayerServer) processWin(w http.ResponseWriter, player string) {
p.store.RecordWin(player)
w.WriteHeader(http.StatusAccepted)
p.store.RecordWin(player)
w.WriteHeader(http.StatusAccepted)
}
```
@ -937,19 +937,19 @@ In the interest of brevity, I am going to show you the final refactored integrat
```go
func TestRecordingWinsAndRetrievingThem(t *testing.T) {
store := InMemoryPlayerStore{}
server := PlayerServer{&store}
player := "Pepper"
store := InMemoryPlayerStore{}
server := PlayerServer{&store}
player := "Pepper"
server.ServeHTTP(httptest.NewRecorder(), newPostWinRequest(player))
server.ServeHTTP(httptest.NewRecorder(), newPostWinRequest(player))
server.ServeHTTP(httptest.NewRecorder(), newPostWinRequest(player))
server.ServeHTTP(httptest.NewRecorder(), newPostWinRequest(player))
server.ServeHTTP(httptest.NewRecorder(), newPostWinRequest(player))
server.ServeHTTP(httptest.NewRecorder(), newPostWinRequest(player))
response := httptest.NewRecorder()
server.ServeHTTP(response, newGetScoreRequest(player))
assertStatus(t, response.Code, http.StatusOK)
response := httptest.NewRecorder()
server.ServeHTTP(response, newGetScoreRequest(player))
assertStatus(t, response.Code, http.StatusOK)
assertResponseBody(t, response.Body.String(), "3")
assertResponseBody(t, response.Body.String(), "3")
}
```
@ -974,19 +974,19 @@ If I were to get stuck in this scenario, I would revert my changes back to the f
```go
func NewInMemoryPlayerStore() *InMemoryPlayerStore {
return &InMemoryPlayerStore{map[string]int{}}
return &InMemoryPlayerStore{map[string]int{}}
}
type InMemoryPlayerStore struct{
store map[string]int
type InMemoryPlayerStore struct {
store map[string]int
}
func (i *InMemoryPlayerStore) RecordWin(name string) {
i.store[name]++
i.store[name]++
}
func (i *InMemoryPlayerStore) GetPlayerScore(name string) int {
return i.store[name]
return i.store[name]
}
```
@ -1000,16 +1000,16 @@ The integration test passes, now we just need to change `main` to use `NewInMemo
package main
import (
"log"
"net/http"
"log"
"net/http"
)
func main() {
server := &PlayerServer{NewInMemoryPlayerStore()}
server := &PlayerServer{NewInMemoryPlayerStore()}
if err := http.ListenAndServe(":5000", server); err != nil {
log.Fatalf("could not listen on port 5000 %v", err)
}
if err := http.ListenAndServe(":5000", server); err != nil {
log.Fatalf("could not listen on port 5000 %v", err)
}
}
```

View File

@ -14,12 +14,12 @@ package integers
import "testing"
func TestAdder(t *testing.T) {
sum := Add(2, 2)
expected := 4
sum := Add(2, 2)
expected := 4
if sum != expected {
t.Errorf("expected '%d' but got '%d'", expected, sum)
}
if sum != expected {
t.Errorf("expected '%d' but got '%d'", expected, sum)
}
}
```
@ -43,7 +43,7 @@ Write enough code to satisfy the compiler _and that's all_ - remember we want to
package integers
func Add(x, y int) int {
return 0
return 0
}
```
@ -61,7 +61,7 @@ In the strictest sense of TDD we should now write the _minimal amount of code to
```go
func Add(x, y int) int {
return 4
return 4
}
```
@ -75,7 +75,7 @@ For now, let's fix it properly
```go
func Add(x, y int) int {
return x + y
return x + y
}
```
@ -94,7 +94,7 @@ You can add documentation to functions with comments, and these will appear in G
```go
// Add takes two integers and returns the sum of them.
func Add(x, y int) int {
return x + y
return x + y
}
```
@ -112,9 +112,9 @@ As with typical tests, examples are functions that reside in a package's `_test.
```go
func ExampleAdd() {
sum := Add(1, 5)
fmt.Println(sum)
// Output: 6
sum := Add(1, 5)
fmt.Println(sum)
// Output: 6
}
```

View File

@ -16,12 +16,12 @@ package iteration
import "testing"
func TestRepeat(t *testing.T) {
repeated := Repeat("a")
expected := "aaaaa"
repeated := Repeat("a")
expected := "aaaaa"
if repeated != expected {
t.Errorf("expected %q but got %q", expected, repeated)
}
if repeated != expected {
t.Errorf("expected %q but got %q", expected, repeated)
}
}
```
@ -39,7 +39,7 @@ All you need to do right now is enough to make it compile so you can check your
package iteration
func Repeat(character string) string {
return ""
return ""
}
```
@ -53,18 +53,18 @@ The `for` syntax is very unremarkable and follows most C-like languages.
```go
func Repeat(character string) string {
var repeated string
for i := 0; i < 5; i++ {
repeated = repeated + character
}
return repeated
var repeated string
for i := 0; i < 5; i++ {
repeated = repeated + character
}
return repeated
}
```
Unlike other languages like C, Java, or JavaScript there are no parentheses surrounding the three components of the for statement and the braces `{ }` are always required. You might wonder what is happening in the row
```go
var repeated string
var repeated string
```
as we've been using `:=` so far to declare and initializing variables. However, `:=` is simply [short hand for both steps](https://gobyexample.com/variables). Here we are declaring a `string` variable only. Hence, the explicit version. We can also use `var` to declare functions, as we'll see later on.
@ -81,7 +81,7 @@ Now it's time to refactor and introduce another construct `+=` assignment operat
const repeatCount = 5
func Repeat(character string) string {
var repeated string
var repeated string
for i := 0; i < repeatCount; i++ {
repeated += character
}
@ -97,9 +97,9 @@ Writing [benchmarks](https://golang.org/pkg/testing/#hdr-Benchmarks) in Go is an
```go
func BenchmarkRepeat(b *testing.B) {
for i := 0; i < b.N; i++ {
Repeat("a")
}
for i := 0; i < b.N; i++ {
Repeat("a")
}
}
```

402
json.md
View File

@ -13,43 +13,43 @@ Our product owner has a new requirement; to have a new endpoint called `/league`
package main
import (
"fmt"
"net/http"
"fmt"
"net/http"
)
type PlayerStore interface {
GetPlayerScore(name string) int
RecordWin(name string)
GetPlayerScore(name string) int
RecordWin(name string)
}
type PlayerServer struct {
store PlayerStore
store PlayerStore
}
func (p *PlayerServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
player := r.URL.Path[len("/players/"):]
player := r.URL.Path[len("/players/"):]
switch r.Method {
case http.MethodPost:
p.processWin(w, player)
case http.MethodGet:
p.showScore(w, player)
}
switch r.Method {
case http.MethodPost:
p.processWin(w, player)
case http.MethodGet:
p.showScore(w, player)
}
}
func (p *PlayerServer) showScore(w http.ResponseWriter, player string) {
score := p.store.GetPlayerScore(player)
score := p.store.GetPlayerScore(player)
if score == 0 {
w.WriteHeader(http.StatusNotFound)
}
if score == 0 {
w.WriteHeader(http.StatusNotFound)
}
fmt.Fprint(w, score)
fmt.Fprint(w, score)
}
func (p *PlayerServer) processWin(w http.ResponseWriter, player string) {
p.store.RecordWin(player)
w.WriteHeader(http.StatusAccepted)
p.store.RecordWin(player)
w.WriteHeader(http.StatusAccepted)
}
```
@ -58,19 +58,19 @@ func (p *PlayerServer) processWin(w http.ResponseWriter, player string) {
package main
func NewInMemoryPlayerStore() *InMemoryPlayerStore {
return &InMemoryPlayerStore{map[string]int{}}
return &InMemoryPlayerStore{map[string]int{}}
}
type InMemoryPlayerStore struct {
store map[string]int
store map[string]int
}
func (i *InMemoryPlayerStore) RecordWin(name string) {
i.store[name]++
i.store[name]++
}
func (i *InMemoryPlayerStore) GetPlayerScore(name string) int {
return i.store[name]
return i.store[name]
}
```
@ -80,16 +80,16 @@ func (i *InMemoryPlayerStore) GetPlayerScore(name string) int {
package main
import (
"log"
"net/http"
"log"
"net/http"
)
func main() {
server := &PlayerServer{NewInMemoryPlayerStore()}
server := &PlayerServer{NewInMemoryPlayerStore()}
if err := http.ListenAndServe(":5000", server); err != nil {
log.Fatalf("could not listen on port 5000 %v", err)
}
if err := http.ListenAndServe(":5000", server); err != nil {
log.Fatalf("could not listen on port 5000 %v", err)
}
}
```
@ -103,17 +103,17 @@ We'll extend the existing suite as we have some useful test functions and a fake
```go
func TestLeague(t *testing.T) {
store := StubPlayerStore{}
server := &PlayerServer{&store}
store := StubPlayerStore{}
server := &PlayerServer{&store}
t.Run("it returns 200 on /league", func(t *testing.T) {
request, _ := http.NewRequest(http.MethodGet, "/league", nil)
response := httptest.NewRecorder()
t.Run("it returns 200 on /league", func(t *testing.T) {
request, _ := http.NewRequest(http.MethodGet, "/league", nil)
response := httptest.NewRecorder()
server.ServeHTTP(response, request)
server.ServeHTTP(response, request)
assertStatus(t, response.Code, http.StatusOK)
})
assertStatus(t, response.Code, http.StatusOK)
})
}
```
@ -152,24 +152,24 @@ Let's commit some sins and get the tests passing in the quickest way we can, kno
```go
func (p *PlayerServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
router := http.NewServeMux()
router := http.NewServeMux()
router.Handle("/league", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}))
router.Handle("/league", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}))
router.Handle("/players/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
player := r.URL.Path[len("/players/"):]
router.Handle("/players/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
player := r.URL.Path[len("/players/"):]
switch r.Method {
case http.MethodPost:
p.processWin(w, player)
case http.MethodGet:
p.showScore(w, player)
}
}))
switch r.Method {
case http.MethodPost:
p.processWin(w, player)
case http.MethodGet:
p.showScore(w, player)
}
}))
router.ServeHTTP(w, r)
router.ServeHTTP(w, r)
}
```
@ -187,26 +187,26 @@ The tests should now pass.
```go
func (p *PlayerServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
router := http.NewServeMux()
router.Handle("/league", http.HandlerFunc(p.leagueHandler))
router.Handle("/players/", http.HandlerFunc(p.playersHandler))
router := http.NewServeMux()
router.Handle("/league", http.HandlerFunc(p.leagueHandler))
router.Handle("/players/", http.HandlerFunc(p.playersHandler))
router.ServeHTTP(w, r)
router.ServeHTTP(w, r)
}
func (p *PlayerServer) leagueHandler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.WriteHeader(http.StatusOK)
}
func (p *PlayerServer) playersHandler(w http.ResponseWriter, r *http.Request) {
player := r.URL.Path[len("/players/"):]
player := r.URL.Path[len("/players/"):]
switch r.Method {
case http.MethodPost:
p.processWin(w, player)
case http.MethodGet:
p.showScore(w, player)
}
switch r.Method {
case http.MethodPost:
p.processWin(w, player)
case http.MethodGet:
p.showScore(w, player)
}
}
```
@ -214,24 +214,24 @@ It's quite odd (and inefficient) to be setting up a router as a request comes in
```go
type PlayerServer struct {
store PlayerStore
router *http.ServeMux
store PlayerStore
router *http.ServeMux
}
func NewPlayerServer(store PlayerStore) *PlayerServer {
p := &PlayerServer{
store,
http.NewServeMux(),
}
p := &PlayerServer{
store,
http.NewServeMux(),
}
p.router.Handle("/league", http.HandlerFunc(p.leagueHandler))
p.router.Handle("/players/", http.HandlerFunc(p.playersHandler))
p.router.Handle("/league", http.HandlerFunc(p.leagueHandler))
p.router.Handle("/players/", http.HandlerFunc(p.playersHandler))
return p
return p
}
func (p *PlayerServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
p.router.ServeHTTP(w, r)
p.router.ServeHTTP(w, r)
}
```
@ -245,22 +245,22 @@ Try changing the code to the following.
```go
type PlayerServer struct {
store PlayerStore
http.Handler
store PlayerStore
http.Handler
}
func NewPlayerServer(store PlayerStore) *PlayerServer {
p := new(PlayerServer)
p := new(PlayerServer)
p.store = store
p.store = store
router := http.NewServeMux()
router.Handle("/league", http.HandlerFunc(p.leagueHandler))
router.Handle("/players/", http.HandlerFunc(p.playersHandler))
router := http.NewServeMux()
router.Handle("/league", http.HandlerFunc(p.leagueHandler))
router.Handle("/players/", http.HandlerFunc(p.playersHandler))
p.Handler = router
p.Handler = router
return p
return p
}
```
@ -284,8 +284,8 @@ Embedding is a very interesting language feature. You can use it with interfaces
```go
type Animal interface {
Eater
Sleeper
Eater
Sleeper
}
```
@ -324,25 +324,25 @@ We'll start by trying to parse the response into something meaningful.
```go
func TestLeague(t *testing.T) {
store := StubPlayerStore{}
server := NewPlayerServer(&store)
store := StubPlayerStore{}
server := NewPlayerServer(&store)
t.Run("it returns 200 on /league", func(t *testing.T) {
request, _ := http.NewRequest(http.MethodGet, "/league", nil)
response := httptest.NewRecorder()
t.Run("it returns 200 on /league", func(t *testing.T) {
request, _ := http.NewRequest(http.MethodGet, "/league", nil)
response := httptest.NewRecorder()
server.ServeHTTP(response, request)
server.ServeHTTP(response, request)
var got []Player
var got []Player
err := json.NewDecoder(response.Body).Decode(&got)
err := json.NewDecoder(response.Body).Decode(&got)
if err != nil {
t.Fatalf ("Unable to parse response from server %q into slice of Player, '%v'", response.Body, err)
}
if err != nil {
t.Fatalf("Unable to parse response from server %q into slice of Player, '%v'", response.Body, err)
}
assertStatus(t, response.Code, http.StatusOK)
})
assertStatus(t, response.Code, http.StatusOK)
})
}
```
@ -365,8 +365,8 @@ Given the JSON data model, it looks like we need an array of `Player` with some
```go
type Player struct {
Name string
Wins int
Name string
Wins int
}
```
@ -397,13 +397,13 @@ Our endpoint currently does not return a body so it cannot be parsed into JSON.
```go
func (p *PlayerServer) leagueHandler(w http.ResponseWriter, r *http.Request) {
leagueTable := []Player{
{"Chris", 20},
}
leagueTable := []Player{
{"Chris", 20},
}
json.NewEncoder(w).Encode(leagueTable)
json.NewEncoder(w).Encode(leagueTable)
w.WriteHeader(http.StatusOK)
w.WriteHeader(http.StatusOK)
}
```
@ -424,14 +424,14 @@ It would be nice to introduce a separation of concern between our handler and ge
```go
func (p *PlayerServer) leagueHandler(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(p.getLeagueTable())
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(p.getLeagueTable())
w.WriteHeader(http.StatusOK)
}
func (p *PlayerServer) getLeagueTable() []Player{
return []Player{
{"Chris", 20},
}
func (p *PlayerServer) getLeagueTable() []Player {
return []Player{
{"Chris", 20},
}
}
```
@ -445,9 +445,9 @@ Update `StubPlayerStore` to let it store a league, which is just a slice of `Pla
```go
type StubPlayerStore struct {
scores map[string]int
winCalls []string
league []Player
scores map[string]int
winCalls []string
league []Player
}
```
@ -456,35 +456,35 @@ Next, update our current test by putting some players in the league property of
```go
func TestLeague(t *testing.T) {
t.Run("it returns the league table as JSON", func(t *testing.T) {
wantedLeague := []Player{
{"Cleo", 32},
{"Chris", 20},
{"Tiest", 14},
}
t.Run("it returns the league table as JSON", func(t *testing.T) {
wantedLeague := []Player{
{"Cleo", 32},
{"Chris", 20},
{"Tiest", 14},
}
store := StubPlayerStore{nil, nil, wantedLeague}
server := NewPlayerServer(&store)
store := StubPlayerStore{nil, nil, wantedLeague}
server := NewPlayerServer(&store)
request, _ := http.NewRequest(http.MethodGet, "/league", nil)
response := httptest.NewRecorder()
request, _ := http.NewRequest(http.MethodGet, "/league", nil)
response := httptest.NewRecorder()
server.ServeHTTP(response, request)
server.ServeHTTP(response, request)
var got []Player
var got []Player
err := json.NewDecoder(response.Body).Decode(&got)
err := json.NewDecoder(response.Body).Decode(&got)
if err != nil {
t.Fatalf("Unable to parse response from server %q into slice of Player, '%v'", response.Body, err)
}
if err != nil {
t.Fatalf("Unable to parse response from server %q into slice of Player, '%v'", response.Body, err)
}
assertStatus(t, response.Code, http.StatusOK)
assertStatus(t, response.Code, http.StatusOK)
if !reflect.DeepEqual(got, wantedLeague) {
t.Errorf("got %v want %v", got, wantedLeague)
}
})
if !reflect.DeepEqual(got, wantedLeague) {
t.Errorf("got %v want %v", got, wantedLeague)
}
})
}
```
@ -513,9 +513,9 @@ We know the data is in our `StubPlayerStore` and we've abstracted that away into
```go
type PlayerStore interface {
GetPlayerScore(name string) int
RecordWin(name string)
GetLeague() []Player
GetPlayerScore(name string) int
RecordWin(name string)
GetLeague() []Player
}
```
@ -523,8 +523,8 @@ Now we can update our handler code to call that rather than returning a hard-cod
```go
func (p *PlayerServer) leagueHandler(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(p.store.GetLeague())
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(p.store.GetLeague())
w.WriteHeader(http.StatusOK)
}
```
@ -550,7 +550,7 @@ For `StubPlayerStore` it's pretty easy, just return the `league` field we added
```go
func (s *StubPlayerStore) GetLeague() []Player {
return s.league
return s.league
}
```
@ -558,7 +558,7 @@ Here's a reminder of how `InMemoryStore` is implemented.
```go
type InMemoryPlayerStore struct {
store map[string]int
store map[string]int
}
```
@ -568,7 +568,7 @@ So let's just get the compiler happy for now and live with the uncomfortable fee
```go
func (i *InMemoryPlayerStore) GetLeague() []Player {
return nil
return nil
}
```
@ -582,23 +582,23 @@ The test code does not convey out intent very well and has a lot of boilerplate
```go
t.Run("it returns the league table as JSON", func(t *testing.T) {
wantedLeague := []Player{
{"Cleo", 32},
{"Chris", 20},
{"Tiest", 14},
}
wantedLeague := []Player{
{"Cleo", 32},
{"Chris", 20},
{"Tiest", 14},
}
store := StubPlayerStore{nil, nil, wantedLeague}
server := NewPlayerServer(&store)
store := StubPlayerStore{nil, nil, wantedLeague}
server := NewPlayerServer(&store)
request := newLeagueRequest()
response := httptest.NewRecorder()
request := newLeagueRequest()
response := httptest.NewRecorder()
server.ServeHTTP(response, request)
server.ServeHTTP(response, request)
got := getLeagueFromResponse(t, response.Body)
assertStatus(t, response.Code, http.StatusOK)
assertLeague(t, got, wantedLeague)
got := getLeagueFromResponse(t, response.Body)
assertStatus(t, response.Code, http.StatusOK)
assertLeague(t, got, wantedLeague)
})
```
@ -606,26 +606,26 @@ Here are the new helpers
```go
func getLeagueFromResponse(t *testing.T, body io.Reader) (league []Player) {
t.Helper()
err := json.NewDecoder(body).Decode(&league)
t.Helper()
err := json.NewDecoder(body).Decode(&league)
if err != nil {
t.Fatalf("Unable to parse response from server %q into slice of Player, '%v'", body, err)
}
if err != nil {
t.Fatalf("Unable to parse response from server %q into slice of Player, '%v'", body, err)
}
return
return
}
func assertLeague(t *testing.T, got, want []Player) {
t.Helper()
if !reflect.DeepEqual(got, want) {
t.Errorf("got %v want %v", got, want)
}
t.Helper()
if !reflect.DeepEqual(got, want) {
t.Errorf("got %v want %v", got, want)
}
}
func newLeagueRequest() *http.Request {
req, _ := http.NewRequest(http.MethodGet, "/league", nil)
return req
req, _ := http.NewRequest(http.MethodGet, "/league", nil)
return req
}
```
@ -637,7 +637,7 @@ Add this assertion to the existing test
```go
if response.Result().Header.Get("content-type") != "application/json" {
t.Errorf("response did not have content-type of application/json, got %v", response.Result().Header)
t.Errorf("response did not have content-type of application/json, got %v", response.Result().Header)
}
```
@ -655,8 +655,8 @@ Update `leagueHandler`
```go
func (p *PlayerServer) leagueHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("content-type", "application/json")
json.NewEncoder(w).Encode(p.store.GetLeague())
w.Header().Set("content-type", "application/json")
json.NewEncoder(w).Encode(p.store.GetLeague())
}
```
@ -670,10 +670,10 @@ Add a helper for `assertContentType`.
const jsonContentType = "application/json"
func assertContentType(t *testing.T, response *httptest.ResponseRecorder, want string) {
t.Helper()
if response.Result().Header.Get("content-type") != want {
t.Errorf("response did not have content-type of %s, got %v", want, response.Result().Header)
}
t.Helper()
if response.Result().Header.Get("content-type") != want {
t.Errorf("response did not have content-type of %s, got %v", want, response.Result().Header)
}
}
```
@ -693,33 +693,33 @@ We can use `t.Run` to break up this test a bit and we can reuse the helpers from
```go
func TestRecordingWinsAndRetrievingThem(t *testing.T) {
store := NewInMemoryPlayerStore()
server := NewPlayerServer(store)
player := "Pepper"
store := NewInMemoryPlayerStore()
server := NewPlayerServer(store)
player := "Pepper"
server.ServeHTTP(httptest.NewRecorder(), newPostWinRequest(player))
server.ServeHTTP(httptest.NewRecorder(), newPostWinRequest(player))
server.ServeHTTP(httptest.NewRecorder(), newPostWinRequest(player))
server.ServeHTTP(httptest.NewRecorder(), newPostWinRequest(player))
server.ServeHTTP(httptest.NewRecorder(), newPostWinRequest(player))
server.ServeHTTP(httptest.NewRecorder(), newPostWinRequest(player))
t.Run("get score", func(t *testing.T) {
response := httptest.NewRecorder()
server.ServeHTTP(response, newGetScoreRequest(player))
assertStatus(t, response.Code, http.StatusOK)
t.Run("get score", func(t *testing.T) {
response := httptest.NewRecorder()
server.ServeHTTP(response, newGetScoreRequest(player))
assertStatus(t, response.Code, http.StatusOK)
assertResponseBody(t, response.Body.String(), "3")
})
assertResponseBody(t, response.Body.String(), "3")
})
t.Run("get league", func(t *testing.T) {
response := httptest.NewRecorder()
server.ServeHTTP(response, newLeagueRequest())
assertStatus(t, response.Code, http.StatusOK)
t.Run("get league", func(t *testing.T) {
response := httptest.NewRecorder()
server.ServeHTTP(response, newLeagueRequest())
assertStatus(t, response.Code, http.StatusOK)
got := getLeagueFromResponse(t, response.Body)
want := []Player{
{"Pepper", 3},
}
assertLeague(t, got, want)
})
got := getLeagueFromResponse(t, response.Body)
want := []Player{
{"Pepper", 3},
}
assertLeague(t, got, want)
})
}
```
@ -737,11 +737,11 @@ func TestRecordingWinsAndRetrievingThem(t *testing.T) {
```go
func (i *InMemoryPlayerStore) GetLeague() []Player {
var league []Player
for name, wins := range i.store {
league = append(league, Player{name, wins})
}
return league
var league []Player
for name, wins := range i.store {
league = append(league, Player{name, wins})
}
return league
}
```

266
select.md
View File

@ -17,15 +17,15 @@ Let's start with something naive to get us going.
```go
func TestRacer(t *testing.T) {
slowURL := "http://www.facebook.com"
fastURL := "http://www.quii.co.uk"
slowURL := "http://www.facebook.com"
fastURL := "http://www.quii.co.uk"
want := fastURL
got := Racer(slowURL, fastURL)
want := fastURL
got := Racer(slowURL, fastURL)
if got != want {
t.Errorf("got %q, want %q", got, want)
}
if got != want {
t.Errorf("got %q, want %q", got, want)
}
}
```
@ -39,7 +39,7 @@ We know this isn't perfect and has problems but it will get us going. It's impor
```go
func Racer(a, b string) (winner string) {
return
return
}
```
@ -49,19 +49,19 @@ func Racer(a, b string) (winner string) {
```go
func Racer(a, b string) (winner string) {
startA := time.Now()
http.Get(a)
aDuration := time.Since(startA)
startA := time.Now()
http.Get(a)
aDuration := time.Since(startA)
startB := time.Now()
http.Get(b)
bDuration := time.Since(startB)
startB := time.Now()
http.Get(b)
bDuration := time.Since(startB)
if aDuration < bDuration {
return a
}
if aDuration < bDuration {
return a
}
return b
return b
}
```
@ -92,27 +92,27 @@ Let's change our tests to use mocks so we have reliable servers to test against
```go
func TestRacer(t *testing.T) {
slowServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
time.Sleep(20 * time.Millisecond)
w.WriteHeader(http.StatusOK)
}))
slowServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
time.Sleep(20 * time.Millisecond)
w.WriteHeader(http.StatusOK)
}))
fastServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}))
fastServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}))
slowURL := slowServer.URL
fastURL := fastServer.URL
slowURL := slowServer.URL
fastURL := fastServer.URL
want := fastURL
got := Racer(slowURL, fastURL)
want := fastURL
got := Racer(slowURL, fastURL)
if got != want {
t.Errorf("got %q, want %q", got, want)
}
if got != want {
t.Errorf("got %q, want %q", got, want)
}
slowServer.Close()
fastServer.Close()
slowServer.Close()
fastServer.Close()
}
```
@ -136,20 +136,20 @@ We have some duplication in both our production code and test code.
```go
func Racer(a, b string) (winner string) {
aDuration := measureResponseTime(a)
bDuration := measureResponseTime(b)
aDuration := measureResponseTime(a)
bDuration := measureResponseTime(b)
if aDuration < bDuration {
return a
}
if aDuration < bDuration {
return a
}
return b
return b
}
func measureResponseTime(url string) time.Duration {
start := time.Now()
http.Get(url)
return time.Since(start)
start := time.Now()
http.Get(url)
return time.Since(start)
}
```
@ -158,28 +158,28 @@ This DRY-ing up makes our `Racer` code a lot easier to read.
```go
func TestRacer(t *testing.T) {
slowServer := makeDelayedServer(20 * time.Millisecond)
fastServer := makeDelayedServer(0 * time.Millisecond)
slowServer := makeDelayedServer(20 * time.Millisecond)
fastServer := makeDelayedServer(0 * time.Millisecond)
defer slowServer.Close()
defer fastServer.Close()
defer slowServer.Close()
defer fastServer.Close()
slowURL := slowServer.URL
fastURL := fastServer.URL
slowURL := slowServer.URL
fastURL := fastServer.URL
want := fastURL
got := Racer(slowURL, fastURL)
want := fastURL
got := Racer(slowURL, fastURL)
if got != want {
t.Errorf("got %q, want %q", got, want)
}
if got != want {
t.Errorf("got %q, want %q", got, want)
}
}
func makeDelayedServer(delay time.Duration) *httptest.Server {
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
time.Sleep(delay)
w.WriteHeader(http.StatusOK)
}))
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
time.Sleep(delay)
w.WriteHeader(http.StatusOK)
}))
}
```
@ -204,21 +204,21 @@ To do this, we're going to introduce a new construct called `select` which helps
```go
func Racer(a, b string) (winner string) {
select {
case <-ping(a):
return a
case <-ping(b):
return b
}
select {
case <-ping(a):
return a
case <-ping(b):
return b
}
}
func ping(url string) chan struct{} {
ch := make(chan struct{})
go func() {
http.Get(url)
close(ch)
}()
return ch
ch := make(chan struct{})
go func() {
http.Get(url)
close(ch)
}()
return ch
}
```
@ -258,17 +258,17 @@ Our final requirement was to return an error if `Racer` takes longer than 10 sec
```go
t.Run("returns an error if a server doesn't respond within 10s", func(t *testing.T) {
serverA := makeDelayedServer(11 * time.Second)
serverB := makeDelayedServer(12 * time.Second)
serverA := makeDelayedServer(11 * time.Second)
serverB := makeDelayedServer(12 * time.Second)
defer serverA.Close()
defer serverB.Close()
defer serverA.Close()
defer serverB.Close()
_, err := Racer(serverA.URL, serverB.URL)
_, err := Racer(serverA.URL, serverB.URL)
if err == nil {
t.Error("expected an error but didn't get one")
}
if err == nil {
t.Error("expected an error but didn't get one")
}
})
```
@ -282,12 +282,12 @@ We've made our test servers take longer than 10s to return to exercise this scen
```go
func Racer(a, b string) (winner string, error error) {
select {
case <-ping(a):
return a, nil
case <-ping(b):
return b, nil
}
select {
case <-ping(a):
return a, nil
case <-ping(b):
return b, nil
}
}
```
@ -307,14 +307,14 @@ If you run it now after 11 seconds it will fail.
```go
func Racer(a, b string) (winner string, error error) {
select {
case <-ping(a):
return a, nil
case <-ping(b):
return b, nil
case <-time.After(10 * time.Second):
return "", fmt.Errorf("timed out waiting for %s and %s", a, b)
}
select {
case <-ping(a):
return a, nil
case <-ping(b):
return b, nil
case <-time.After(10 * time.Second):
return "", fmt.Errorf("timed out waiting for %s and %s", a, b)
}
}
```
@ -330,14 +330,14 @@ What we can do is make the timeout configurable. So in our test, we can have a v
```go
func Racer(a, b string, timeout time.Duration) (winner string, error error) {
select {
case <-ping(a):
return a, nil
case <-ping(b):
return b, nil
case <-time.After(timeout):
return "", fmt.Errorf("timed out waiting for %s and %s", a, b)
}
select {
case <-ping(a):
return a, nil
case <-ping(b):
return b, nil
case <-time.After(timeout):
return "", fmt.Errorf("timed out waiting for %s and %s", a, b)
}
}
```
@ -354,18 +354,18 @@ Given this knowledge, let's do a little refactoring to be sympathetic to both ou
var tenSecondTimeout = 10 * time.Second
func Racer(a, b string) (winner string, error error) {
return ConfigurableRacer(a, b, tenSecondTimeout)
return ConfigurableRacer(a, b, tenSecondTimeout)
}
func ConfigurableRacer(a, b string, timeout time.Duration) (winner string, error error) {
select {
case <-ping(a):
return a, nil
case <-ping(b):
return b, nil
case <-time.After(timeout):
return "", fmt.Errorf("timed out waiting for %s and %s", a, b)
}
select {
case <-ping(a):
return a, nil
case <-ping(b):
return b, nil
case <-time.After(timeout):
return "", fmt.Errorf("timed out waiting for %s and %s", a, b)
}
}
```
@ -374,39 +374,39 @@ Our users and our first test can use `Racer` (which uses `ConfigurableRacer` und
```go
func TestRacer(t *testing.T) {
t.Run("compares speeds of servers, returning the url of the fastest one", func(t *testing.T) {
slowServer := makeDelayedServer(20 * time.Millisecond)
fastServer := makeDelayedServer(0 * time.Millisecond)
t.Run("compares speeds of servers, returning the url of the fastest one", func(t *testing.T) {
slowServer := makeDelayedServer(20 * time.Millisecond)
fastServer := makeDelayedServer(0 * time.Millisecond)
defer slowServer.Close()
defer fastServer.Close()
defer slowServer.Close()
defer fastServer.Close()
slowURL := slowServer.URL
fastURL := fastServer.URL
slowURL := slowServer.URL
fastURL := fastServer.URL
want := fastURL
got, err := Racer(slowURL, fastURL)
want := fastURL
got, err := Racer(slowURL, fastURL)
if err != nil {
t.Fatalf("did not expect an error but got one %v", err)
}
if err != nil {
t.Fatalf("did not expect an error but got one %v", err)
}
if got != want {
t.Errorf("got %q, want %q", got, want)
}
})
if got != want {
t.Errorf("got %q, want %q", got, want)
}
})
t.Run("returns an error if a server doesn't respond within 10s", func(t *testing.T) {
server := makeDelayedServer(25 * time.Millisecond)
t.Run("returns an error if a server doesn't respond within 10s", func(t *testing.T) {
server := makeDelayedServer(25 * time.Millisecond)
defer server.Close()
defer server.Close()
_, err := ConfigurableRacer(server.URL, server.URL, 20*time.Millisecond)
_, err := ConfigurableRacer(server.URL, server.URL, 20*time.Millisecond)
if err == nil {
t.Error("expected an error but didn't get one")
}
})
if err == nil {
t.Error("expected an error but didn't get one")
}
})
}
```