测试案例实例

一、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)
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值