一、hello
- hello.go
package main
const (
spanish = "Spanish"
french = "French"
englishHelloPrefix = "Hello, "
spanishHelloPrefix = "Hola, "
frenchHelloPrefix = "Bonjour, "
)
func main() {
}
func Hello(name, language string) string {
if name == "" {
name = "World"
}
return greetingPrefix(language) + name
}
func greetingPrefix(language string) (prefix string) {
switch language {
case french:
prefix = frenchHelloPrefix
case spanish:
prefix = spanishHelloPrefix
default:
prefix = englishHelloPrefix
}
return
}
- hello_test.go
package main
import "testing"
func TestHello(t *testing.T) {
assertCorrectMessage := func(t *testing.T, got, want string) {
//当测试失败时,报错不会显示真正的错误行数,而是会显示至 调用该辅助函数的位置
t.Helper() //t.Helper() 需要告诉测试套件这个方法是辅助函数(helper)
if got != want {
t.Errorf("got '%q' want '%q'", got, want)
}
}
//为西班牙语用户编写测试
t.Run("in Spanish", func(t *testing.T) {
got := Hello("Elodie", "Spanish")
want := "Hola, Elodie"
assertCorrectMessage(t, got, want)
})
t.Run("saying hello to people", func(t *testing.T) { // t.Run 用于在测试函数内部运行子测试
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)
})
}
二、整数
package integers
import (
"fmt"
"testing"
)
func TestAdder(t *testing.T) {
sum := Add(2, 2)
expected := 4
if sum != expected {
t.Errorf("expected '%d' but got '%d'", expected, sum)
}
}
/*
go test -v (显示详细信息)
因为有这行注释 //Output:6 才会在terminal中输出
=== RUN ExampleAdd
--- PASS: ExampleAdd (0.00s)
并且 添加注释 //Output:值 若给定output后的值 与 结果sum值不匹配 会报错
*/
func ExampleAdd() {
sum := Add(1, 5)
fmt.Println(sum)
//Output:6
}
func Add(x, y int) int {
return x + y
}
三、迭代
package iteration
const repeatCount = 5
// 将字符串重复5次
func Repeat(character string) string {
var repeatStr string
for i := 0; i < repeatCount; i++ {
repeatStr += character
}
return repeatStr
}
repeat_test.go
// repeat_test.go
package iteration
import (
"fmt"
"testing"
)
// 测试用例
func TestRepeat(t *testing.T) {
repeated := Repeat("a")
expected := "aaaaa"
if repeated != expected {
t.Errorf("expected '%s' got '%s'", expected, repeated)
}
}
// ExampleRepeat is a test function for Repeat.
func ExampleRepeat() {
sum := Repeat("B")
fmt.Println(sum)
//Output: BBBBB
}
// terminal: go test -bench="."
// 基准测试
func BenchmarkRepeat(b *testing.B) {
for i := 0; i < b.N; i++ {
Repeat("a")
}
}
四、数组与切片
package go_tests
import (
"reflect"
"testing"
)
// go test -cover 命令用于运行测试并生成代码覆盖率报告
func TestSumAllTails(t *testing.T) {
checksSums := func(t *testing.T, got, want []int) {
if !reflect.DeepEqual(got, want) {
t.Errorf("got %v want %v ", got, want)
}
}
t.Run("make the sums of some slices", func(t *testing.T) {
got := SumAllTails([]int{1, 2}, []int{0, 9})
want := []int{2, 9}
checksSums(t, got, want)
})
t.Run("safely sum empty slices", func(t *testing.T) {
got := SumAllTails([]int{1}, []int{0, 9})
want := []int{0, 9}
checksSums(t, got, want)
})
}
// 它会把每个切片的尾部元素相加(尾部的意思就是除去第一个元素以外的其他元素)。
func SumAllTails(numbersToSum ...[]int) []int {
var sums []int
for _, v := range numbersToSum {
if len(v) == 0 { //[]int{}
sums = append(sums, 0)
} else {
tail := v[1:] //除了第一个不要
sums = append(sums, Sum(tail))
}
}
return sums
}
func TestSumAll(t *testing.T) {
got := SumAll([]int{1, 2}, []int{0, 9})
want := []int{3, 9}
//测试或比较两个结构体、切片、映射等复杂数据结构的相等性。
if !reflect.DeepEqual(got, want) { //got != want
t.Errorf("got %v want %v ", got, want)
}
}
// 这回我们需要一个 SumAll 函数,它接受多个切片,并返回由每个切片元素的总和组成的新切片。
func SumAll(numbersToSum ...[]int) []int {
var sums []int
for _, v := range numbersToSum {
sums = append(sums, Sum(v))
}
//如果你有一个容量为 2 的切片,但使用 mySlice[10]=1 进行赋值,会报运行时错误。
//sums = make([]int, lengthOfNumbers)
//for i, v := range numbersToSum {
// sums[i] = Sum(v)
//}
return sums
}
// 我们创建一个 Sum 函数,它使用 for 来循环获取数组中的元素并返回所有元素的总和。
func TestSum(t *testing.T) {
// 普通sum数组
//t.Run("collection of 5 numbers", func(t *testing.T) {
// numbers := []int{1, 2, 3, 4, 5}
// got := Sum(numbers)
// want := 15
// if want != got {
// t.Errorf("got %d want %d given %v", got, want, numbers)
// }
//})
//动态sum数组
t.Run("collection of any size", func(t *testing.T) {
numbers := []int{1, 2, 3}
got := Sum(numbers)
want := 6
if want != got {
t.Errorf("got %d want %d given %v", got, want, numbers)
}
})
}
func Sum(numbers []int) int {
sum := 0
for i, _ := range numbers {
sum += numbers[i]
}
return sum
}
五、结构体 方法 接口
// 结构体 方法 接口
package go_tests
import (
"math"
"testing"
)
type Shape interface {
Area() float64
}
// 长方形
type Rectangle struct {
width float64
height float64
}
func (r Rectangle) Area() float64 {
return r.width * r.height
}
// 圆形
type Circle struct {
radius float64
}
func (r Circle) Area() float64 {
return r.radius * r.radius * math.Pi
}
// 三角形
type Triangle struct {
Base float64
Height float64
}
func (r Triangle) Area() float64 {
return r.Base * r.Height * 0.5
}
// 表格驱动测试 go test -run TestAreaTable/Circle Circle为 t.run()的第一个参数
func TestAreaTable(t *testing.T) {
areaTests := []struct {
name string
shape Shape
hasArea float64
}{
{name: "Rectangle", shape: Rectangle{width: 12, height: 6}, hasArea: 72.0},
{name: "Circle", shape: Circle{10}, hasArea: 314.1592653589793},
{name: "Triangle", shape: Triangle{12, 6}, hasArea: 36.0},
}
for _, tt := range areaTests {
t.Run(tt.name, func(t *testing.T) {
//使用 tt.name 作为 t.Run 的 name
got := tt.shape.Area()
if got != tt.hasArea {
t.Errorf("%#v got %.2f want %.2f", tt.shape, got, tt.hasArea)
}
})
}
}
// 面积 go test -run TestArea/circle (circle为测试函数 TestArea 的子测试)
func TestArea(t *testing.T) {
checkArea := func(t *testing.T, shape Shape, want float64) {
t.Helper()
got := shape.Area()
if got != want {
t.Errorf("got %v want %v", got, want)
}
}
// 长方形面积
t.Run("rectangles", func(t *testing.T) {
rectangle := Rectangle{12.0, 6.0}
checkArea(t, rectangle, 72.0)
})
// 圆形面积
t.Run("circle", func(t *testing.T) {
circle := Circle{10}
checkArea(t, circle, 314.1592653589793)
})
}
// 1.周长
func TestPerimeter(t *testing.T) {
rectangle := Rectangle{10.0, 10.0}
got := Perimeter(rectangle)
want := 40.0
if got != want {
t.Errorf("got %v want %v", got, want)
}
}
func Perimeter(rectangle Rectangle) float64 {
return 2 * (rectangle.width + rectangle.height)
}
六、指针和错误
package go_tests
import (
"errors"
"fmt"
"testing"
)
/*
https://github.com/kisielk/errcheck
Errcheck是一个程序,用于检查GO代码中未检查的错误
这里返回 wallet_test.go:40:18: wallet.Withdraw(Bitcoin(10))
意思这行代码并没有检查 Withdraw()函数 返回的 err 错误
*/
var InsufficientFundsError = errors.New("cannot withdraw,insufficient funds")
type Bitcoin int
type Wallet struct {
balance Bitcoin //余额
}
// 自定义 %s 的输出格式
func (b Bitcoin) String() string {
return fmt.Sprintf("%d BTC by myself", b)
}
// 在我们的例子中,我们只想让自己的方法修改这个值,而其他的不可以。
// 如果一个符号(例如变量、类型、函数等)是以小写符号开头 那么它在 定义它的包之外 就是私有的。
// 记住,我们可以使用「receiver」变量访问结构体内部的 balance 字 段。
func (r *Wallet) Deposit(num Bitcoin) {
r.balance += num
}
func (r *Wallet) Balance() Bitcoin {
return r.balance
}
func (w *Wallet) Withdraw(amount Bitcoin) error {
if amount > w.balance {
return InsufficientFundsError
}
w.balance -= amount
return nil
}
func TestWallet(t *testing.T) {
t.Run("Withdraw with funds", func(t *testing.T) {
wallet := Wallet{Bitcoin(20)}
wallet.Withdraw(Bitcoin(10))
assertBalance(t, wallet, Bitcoin(10))
})
t.Run("Withdraw insufficient funds", func(t *testing.T) {
startingBalance := Bitcoin(20)
wallet := Wallet{startingBalance}
err := wallet.Withdraw(Bitcoin(100))
assertBalance(t, wallet, startingBalance)
assertError(t, err, InsufficientFundsError)
})
t.Run("Deposit", func(t *testing.T) {
wallet := Wallet{}
wallet.Deposit(Bitcoin(10))
assertBalance(t, wallet, Bitcoin(10))
})
}
func assertBalance(t *testing.T, wallet Wallet, want Bitcoin) {
t.Helper()
if wallet.Balance() != want {
t.Errorf("got %s want %s", wallet.Balance(), want)
}
}
func assertError(t *testing.T, got error, want error) {
if got == nil {
t.Fatal("didn't get an error but wanted one")
}
if got != want {
t.Errorf("got '%s' , want '%s'", got, want)
}
}
七、maps
package dictionary
import (
"testing"
)
const (
ErrNotFound = DictionaryErr("could not find the word you were looking for")
ErrWordExists = DictionaryErr("cannot add word because it already exists")
ErrWordDoesNotExist = DictionaryErr("cannot update word because it does not exist")
)
type DictionaryErr string
type Dictionary map[string]string
// DictionaryErr 实现了 error 接口的 Error 方法
func (e DictionaryErr) Error() string {
return string(e)
}
func (d Dictionary) search(key string) (string, error) {
definition, ok := d[key]
if !ok {
return "", ErrNotFound
}
return definition, nil
}
func (d Dictionary) Add(word, definition string) error {
_, err := d.search(word)
switch err {
case ErrNotFound:
d[word] = definition
case nil:
return ErrWordExists
default:
return err
}
return nil
}
func (d Dictionary) Update(word, definition string) error {
_, err := d.search(word)
switch err {
case ErrNotFound:
return ErrWordDoesNotExist
case nil:
d[word] = definition
default:
return err
}
return nil
}
func (d Dictionary) Delete(word string) {
delete(d, word)
}
func TestDelete(t *testing.T) {
word := "test"
dictionary := Dictionary{word: "test definition"}
dictionary.Delete(word)
_, err := dictionary.search(word)
if err != ErrNotFound {
t.Errorf("Expected '%s' to be deleted", word)
}
}
func TestUpdate(t *testing.T) {
word := "test"
definition := "this is just a test"
dictionary := Dictionary{word: definition}
newDefinition := "new definition"
dictionary.Update(word, newDefinition)
assertDefinition(t, dictionary, word, newDefinition)
}
func TestAdd(t *testing.T) {
t.Run("new word", func(t *testing.T) {
dictionary := Dictionary{}
word := "test"
definition := "this is just a test"
err := dictionary.Update(word, definition)
assertError(t, err, ErrWordDoesNotExist)
})
t.Run("existing word", func(t *testing.T) {
word := "test"
definition := "this is just a test"
newDefinition := "new definition"
dictionary := Dictionary{word: definition}
err := dictionary.Update(word, newDefinition)
assertError(t, err, nil)
assertDefinition(t, dictionary, word, newDefinition)
})
}
func TestSearch(t *testing.T) {
dictionary := Dictionary{"test": "this is just a test"}
t.Run("known word", func(t *testing.T) {
got, _ := dictionary.search("test")
want := "this is just a test"
assertStrings(t, got, want)
})
t.Run("unknow word", func(t *testing.T) {
_, err := dictionary.search("unknown")
assertError(t, err, ErrNotFound)
})
}
func assertStrings(t *testing.T, got, want string) {
t.Helper()
if got != want {
t.Errorf("got '%s' want '%s'", got, want)
}
}
func assertError(t *testing.T, got, want error) {
t.Helper()
if got != want {
t.Errorf("got error '%s' want '%s' ", got, want)
}
}
func assertDefinition(t *testing.T, dictionary Dictionary, word, definition string) {
t.Helper()
got, err := dictionary.search(word)
if err != nil {
t.Fatal("should find added word;", err)
}
if definition != got {
t.Errorf("got '%s' want '%s'", got, definition)
}
}
func search(dictionary map[string]string, word string) string {
return dictionary[word]
}
八、依赖注入
package dependency_injection
import (
"bytes"
"fmt"
"io"
"net/http"
"os"
"testing"
)
func TestInternet(t *testing.T) {
http.ListenAndServe(":5000", http.HandlerFunc(MyGreeterHandler))
}
func MyGreeterHandler(w http.ResponseWriter, r *http.Request) {
Greet(w, "world")
}
func TestGreet(t *testing.T) {
Greet(os.Stdout, "Elodie")
buffer := bytes.Buffer{}
Greet(&buffer, "Chris")
got := buffer.String()
want := "Hello, Chris"
if got != want {
t.Errorf("got '%s' want '%s'", got, want)
}
}
// 用 writer 把问候发送到我们测试中的缓冲区
func Greet(write io.Writer, name string) {
fmt.Fprintf(write, "Hello, %s", name)
}
八 、mocking
package mocking
import (
"bytes"
"fmt"
"io"
"os"
"reflect"
"testing"
"time"
)
const (
finalWord = "GO!"
countdownStart = 3
write = "write"
sleep = "sleep"
)
type Sleeper interface {
Sleep()
}
type SpySleeper struct {
Calls int
}
// 不在需要
type ConfigurableSleeper struct {
duration time.Duration
}
type CountdownOperationSpy struct {
Calls []string
}
func (c *CountdownOperationSpy) Sleep() {
c.Calls = append(c.Calls, sleep)
}
func (c *CountdownOperationSpy) Write(p []byte) (n int, err error) {
c.Calls = append(c.Calls, write)
return
}
func (s *SpySleeper) Sleep() {
s.Calls++
}
func (c *ConfigurableSleeper) Sleep() {
time.Sleep(c.duration)
}
func Countdown(out io.Writer, sleeper Sleeper) {
for i := countdownStart; i > 0; i-- {
sleeper.Sleep()
fmt.Fprintln(out, i)
}
sleeper.Sleep()
fmt.Fprintf(out, finalWord)
}
func TestMainly(t *testing.T) {
sleeper := &ConfigurableSleeper{duration: 1 * time.Second}
Countdown(os.Stdout, sleeper)
}
func TestCountdown(t *testing.T) {
t.Run("prints 3 to GO!", func(t *testing.T) {
buffer := &bytes.Buffer{}
Countdown(buffer, &CountdownOperationSpy{})
got := buffer.String()
want := `3
2
1
GO!`
if got != want {
t.Errorf("got '%s' want '%s'", got, want)
}
})
t.Run("sleep after every print", func(t *testing.T) {
spySleeperPrinter := &CountdownOperationSpy{}
Countdown(spySleeperPrinter, spySleeperPrinter)
want := []string{
sleep,
write,
sleep,
write,
sleep,
write,
sleep,
write,
}
//结构体、切片、映射
if !reflect.DeepEqual(want, spySleeperPrinter.Calls) {
t.Errorf("wanted calls %v got %v", want, spySleeperPrinter.Calls)
}
})
}
九 并发
package concurrency
import (
"reflect"
"testing"
"time"
)
type WebsiteChecker func(string) bool
type result struct {
string
bool
}
func TestCheckWebsites(t *testing.T) {
websites := []string{
"http://google.com",
"http://blog.gypsydave5.com",
"waat://furhurterwe.geds",
}
actualResults := CheckWebsites(mockWebsiteChecker, websites)
want := len(websites)
got := len(actualResults)
if want != got {
t.Fatalf("Wanted %v,got %v", want, got)
}
expectedResults := map[string]bool{
"http://google.com": true,
"http://blog.gypsydave5.com": true,
"waat://furhurterwe.geds": false,
}
if !reflect.DeepEqual(actualResults, expectedResults) {
t.Fatalf("Wanted %v,got %v", expectedResults, actualResults)
}
}
// go test -race 竞争详情
func CheckWebsites(wc WebsiteChecker, urls []string) map[string]bool {
results := make(map[string]bool)
resultChannel := make(chan result)
for _, url := range urls {
//改正
//通过给每个匿名函数一个参数 url(u),然后用 url 作为参数调用匿名函数,
//我们确保 u 的值固定为循环迭代的 url 值,重新启动 goroutine。u 是 url 值的副本,因此无法更改。
go func(u string) {
resultChannel <- result{
string: u,
bool: wc(u),
}
}(url)
//go func() {
// //但是我们的每个 goroutine 都是 url 变量的引用 ——
// //它们没有自己的独立副本。所以他们都会写入在迭代结束时的 url ——
// //最后一个 url。这就是为什么我们得到的结果是最后一个 url。
// results[url] = wc(url)
//}()
}
for i := 0; i < len(urls); i++ {
result := <-resultChannel
results[result.string] = result.bool
}
return results
}
func slowStubWebsiteChecker(_ string) bool {
time.Sleep(20 * time.Millisecond)
return true
}
func mockWebsiteChecker(url string) bool {
if url == "waat://furhurterwe.geds" {
return false
}
return true
}
// go test -bench="."
func BenchmarkCheckWebsites(b *testing.B) {
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)
}
}
十 、select
package _select
import (
"fmt"
"net/http"
"net/http/httptest"
"testing"
"time"
)
/*
你被要求编写一个叫做 WebsiteRacer 的函数,用来对比请求两个 URL 来「比赛」,
并返回先响应的 URL。如果两个 URL 在 10 秒内都未返回结果,那么应该返回一个 error。
*/
var tenSecondTimeout = 10 * time.Second
func TestRacer(t *testing.T) {
t.Run("compares speed of servers,returning the url of the fastest one", func(t *testing.T) {
//使用net/http/httptest,建立一个 HTTP 模拟服务器(mock HTTP server)。模拟测试
slowServer := makeDelayedServer(20 * time.Millisecond)
fastServer := makeDelayedServer(10 * time.Millisecond)
defer slowServer.Close()
defer fastServer.Close()
slowURL := slowServer.URL
fastURL := fastServer.URL
want := fastURL
got, err := Racer(slowURL, fastURL, 25*time.Millisecond)
if err != nil {
t.Fatalf("did not expect an error but got one %v", err)
}
if got != want {
t.Errorf("got '%s',want '%s' ", got, want)
}
})
t.Run("returns an error if a server doesn't respond within 10s", func(t *testing.T) {
server := makeDelayedServer(30 * time.Millisecond)
defer server.Close()
_, err := ConfigurableRacer(server.URL, server.URL, 25*time.Millisecond)
if err == nil {
t.Error("expected an error but didn't get one")
}
})
}
func ConfigurableRacer(a, b string, timeout time.Duration) (string, error) {
//二、进程同步
select {
case <-ping(a):
return a, nil
case <-ping(b):
return b, nil
case <-time.After(timeout):
return "", fmt.Errorf("time out waiting for %s and %s", a, b)
}
}
func Racer(a, b string, timeout time.Duration) (string, error) {
return ConfigurableRacer(a, b, timeout)
//一、
//aDuration := measureResponseTime(a)
//bDuration := measureResponseTime(b)
//
//if aDuration < bDuration {
// return a
//}
//return b
}
func ping(url string) chan bool {
ch := make(chan bool)
go func() {
http.Get(url)
ch <- true
}()
return ch
}
func makeDelayedServer(delay time.Duration) *httptest.Server {
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, request *http.Request) {
time.Sleep(delay)
w.WriteHeader(http.StatusOK)
}))
}
func measureResponseTime(url string) time.Duration {
start := time.Now()
http.Get(url)
return time.Since(start)
}
十一、reflect 反射
package reflection
import (
"reflect"
"testing"
)
/*
编写函数 walk(x interface{}, fn func(string)),
参数为结构体 x,并对 x 中的所有字符串字段调用 fn 函数。难度级别:递归。
*/
type Person struct {
Name string
Profile Profile
}
type Profile struct {
Age int
City string
}
func TestWalk(t *testing.T) {
cases := []struct {
Name string
Input interface{}
ExpectedCalls []string
}{
{"Struct with one string field",
struct {
Name string
}{"Chris"},
[]string{"Chris"}},
{
"Struct with two string fields",
struct {
Name string
City string
}{"Chris", "London"},
[]string{"Chris", "London"},
},
{
"Struct with non string field",
struct {
Name string
Age int
}{"Chris", 33},
[]string{"Chris"},
},
{
"Nested fields",
Person{
"Chris",
Profile{33, "London"},
},
[]string{"Chris", "London"},
},
{
"Pointer to things",
&Person{
Name: "Chris",
Profile: Profile{33, "London"},
},
[]string{"Chris", "London"},
},
{
"Slices",
[]Profile{
{33, "London"},
{34, "Reykjavik"},
},
[]string{"London", "Reykjavik"},
},
{
"Arrays",
[2]Profile{
{33, "London"},
{34, "Reykjavik"},
},
[]string{"London", "Reykjavik"},
},
{
"Maps",
map[string]string{
"Foo": "Bar",
"Baz": "Boz",
},
[]string{"Bar", "Boz"},
},
}
for _, test := range cases {
t.Run(test.Name, func(t *testing.T) {
var got []string
walk(test.Input, func(input string) {
got = append(got, input)
})
if !reflect.DeepEqual(got, test.ExpectedCalls) {
t.Errorf("got %v,want %v", got, test.ExpectedCalls)
}
})
}
//因为map输出的无序性,我们将它放在一个新的测试中
t.Run("with maps", func(t *testing.T) {
aMap := map[string]string{
"Foo": "Bar",
"Baz": "Boz",
}
var got []string
walk(aMap, func(input string) {
got = append(got, input)
})
assertContains(t, got, "Bar")
assertContains(t, got, "Boz")
})
}
func assertContains(t *testing.T, got []string, needle string) {
contains := false
for _, value := range got {
if value == needle {
contains = true
}
}
if !contains {
t.Errorf("expected %+v to contain '%s' but it didnt", got, needle)
}
}
func walk(x interface{}, fn func(input string)) {
val := getValue(x)
walkValue := func(value reflect.Value) {
walk(value.Interface(), fn)
}
switch val.Kind() {
case reflect.String:
fn(val.String())
case reflect.Struct:
for i := 0; i < val.NumField(); i++ {
walkValue(val.Field(i))
}
//数组 处理方式 与 切片 处理方式 一致
case reflect.Slice, reflect.Array:
for i := 0; i < val.Len(); i++ {
walkValue(val.Index(i))
}
case reflect.Map:
for _, key := range val.MapKeys() {
walkValue(val.MapIndex(key))
}
}
}
func getValue(x interface{}) reflect.Value {
val := reflect.ValueOf(x)
//如果传入的是 地址
if val.Kind() == reflect.Ptr {
val = val.Elem()
}
return val
}
十二、HTTP服务器
- http.go go build 生成可执行文件后,执行
package main
import (
"log"
"net/http"
)
type InMemoryPlayerStore struct {
store map[string]int
}
func NewInMemoryPlayerStore() *InMemoryPlayerStore {
return &InMemoryPlayerStore{map[string]int{}}
}
func (i *InMemoryPlayerStore) GetPlayerScore(name string) int {
return i.store[name]
}
func (i *InMemoryPlayerStore) RecordWin(name string) {
i.store[name]++
}
func main() {
server := &PlayerServer{NewInMemoryPlayerStore()}
if err := http.ListenAndServe(":5000", server); err != nil {
log.Fatalf("could not listen on port 5000 %v", err)
}
}
- handler
package main
import (
"fmt"
"net/http"
)
type PlayerStore interface {
GetPlayerScore(name string) int
RecordWin(name string)
}
type PlayerServer struct {
store PlayerStore
}
type StubPlayerStore struct {
scores map[string]int
winCalls []string
}
func (p *PlayerServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
player := r.URL.Path[len("/players/"):]
switch r.Method {
case http.MethodGet:
p.showScore(w, player)
case http.MethodPost:
p.processWin(w, player)
}
}
func (p *PlayerServer) showScore(w http.ResponseWriter, player string) {
score := p.store.GetPlayerScore(player)
if score == 0 {
w.WriteHeader(http.StatusNotFound)
}
fmt.Fprint(w, score)
}
func (p *PlayerServer) processWin(w http.ResponseWriter, player string) {
p.store.RecordWin(player)
w.WriteHeader(http.StatusAccepted)
}
func (s *StubPlayerStore) GetPlayerScore(name string) int {
scores := s.scores[name]
return scores
}
func (s *StubPlayerStore) RecordWin(name string) {
s.winCalls = append(s.winCalls, name)
}
- server_test.go
package main
import (
"fmt"
"net/http"
"net/http/httptest"
"testing"
)
// 集成测试
func TestRecordingWinsAndRetrievingThem(t *testing.T) {
store := NewInMemoryPlayerStore()
server := PlayerServer{store}
player := "Pepper"
server.ServeHTTP(httptest.NewRecorder(), newPostScoreRequest(player))
server.ServeHTTP(httptest.NewRecorder(), newPostScoreRequest(player))
server.ServeHTTP(httptest.NewRecorder(), newPostScoreRequest(player))
response := httptest.NewRecorder()
server.ServeHTTP(response, newGetScoreRequest(player))
assertStatus(t, response.Code, http.StatusOK)
assertResponseBody(t, response.Body.String(), "3")
}
func TestStoreWins(t *testing.T) {
store := StubPlayerStore{map[string]int{}, nil}
server := PlayerServer{store: &store}
t.Run("it returns accepted on POST", func(t *testing.T) {
player := "Pepper"
request := newPostScoreRequest(player)
response := httptest.NewRecorder()
server.ServeHTTP(response, request)
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 store.winCalls[0] != player {
t.Errorf("did not store correct winner got '%s' want '%s'", store.winCalls[0], player)
}
})
}
func TestGETPlaters(t *testing.T) {
store := StubPlayerStore{map[string]int{
"Pepper": 20,
"Floyd": 10,
}, nil}
server := &PlayerServer{store: &store}
t.Run("returns Pepper's score", func(t *testing.T) {
request := newGetScoreRequest("Pepper")
response := httptest.NewRecorder() //响应记录器
server.ServeHTTP(response, request)
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()
server.ServeHTTP(response, request)
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()
server.ServeHTTP(response, request)
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)
}
}
func newGetScoreRequest(name string) *http.Request {
request, _ := http.NewRequest(http.MethodGet, fmt.Sprintf("/players/%s", name), nil)
return request
}
func newPostScoreRequest(name string) *http.Request {
request, _ := http.NewRequest(http.MethodPost, fmt.Sprintf("/players/%s", name), nil)
return request
}
func assertResponseBody(t *testing.T, got, want string) {
t.Helper()
if got != want {
t.Errorf("response body is wrong,got '%s' want '%s'", got, want)
}
}