Golang Unit Test
Tags: tutorial, unittest, go
Published: 2022-03-01 06:01:01

---
title: Golang Unit Test
tags: Tutorial, Unittest, golang
author: Corey Lai
---
# Golang Unit Test


# 使用 Go 進行單元測試
```go
package main
import  "testing"
func  TestHello(t *testing.T) {
	got  :=  Hello()
	want  :=  "Hello, world"
	if got != want {
		t.Errorf("got '%s' want '%s'", got, want)
	}
}
```
```go
package main
import  "fmt"
func  Hello() string {
	return  "Hello, world"
}
func  main() {
	fmt.Println(Hello())
}
```
```bash
go test
PASS
ok      .../Golang_TDD 0.086s
```
## Array
TDD 模式 先寫測試在寫code
```go
// 測試數字相加
func  TestSumAll(t *testing.T) {
	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)
	}
}
// 測試去除第一數陣列後相加
func  TestSumAllTails(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)
	}
}
```
```go
func  Sum(numbers []int) int {
	sum  :=  0
	for  _, number := range numbers {
		sum += number
	}
	return sum
}
func  SumAllTails(numbersToSum ...[]int)  []int  {
	var sums []int
	for  _, numbers := range numbersToSum {
		 tail := numbers[1:]
		 sums =  append(sums,  Sum(tail))
	}
	return sums
}
```
製造錯誤
```go
// 使用空陣列讓原有程式造成 panic
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)
	}
})
// error logs
panic: runtime error: slice bounds out of range [recovered]
panic: runtime error: slice bounds out of range
```
修正錯誤
```go
func  SumAllTails(numbersToSum ...[]int) []int {
	var  sums []int
	for  _, numbers  :=  range numbersToSum {
		// 增加判斷numbers 為空判斷
		if  len(numbers) ==  0 {
			sums  =  append(sums, 0)
		} else {
			tail  := numbers[1:]
			sums  =  append(sums, Sum(tail))
		}
	}
	return sums
}
```
減少 test 重複 code
```go
checkSums  :=  func(t *testing.T, got, want []int) {
	// 告知 testing 這是一個 helper function 當發生與預期不符時會指向呼叫 checksums的行數
	t.Helper()
	if  !reflect.DeepEqual(got, want) {
		t.Errorf("got %v want %v", got, want)
	}
}
```

### 結構與介面
```go
func  TestArea(t *testing.T) {
	// shape interface
	checkArea  :=  func(t *testing.T, shape Shape, want float64) {
		t.Helper()
		got  := shape.Area()
		if got != want {
			t.Errorf("got %.2f want %.2f", got, want)
		}
	}
	t.Run("rectangles", func(t *testing.T) {
		rectangle  := Rectangle{12, 6}
		// Rectangle struct have Area member function
		checkArea(t, rectangle, 72.0)
	})

	t.Run("circles", func(t *testing.T) {
		circle  := Circle{10}
		// Circle struct have Area member function
		checkArea(t, circle, 314.1592653589793)
	})
}
```
```go
// 介面
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 (c Circle) Area() float64 {
	return math.Pi * c.Radius * c.Radius
}
```
### Table Driven Tests
```go
func  TableDrivenTestsArea(t *testing.T) {
	// 批次測試
	areaTests  := []struct {
	shape Shape
	want float64
	}{
		{Rectangle{12, 6}, 72.0},
		{Circle{10}, 314.1592653589793},
	}

	for  _, tt  :=  range areaTests {
		got  := tt.shape.Area()
		if got != tt.want {
			t.Errorf("got %.2f want %.2f", got, tt.want)
		}
	}
}
```
### 執行特定測試
```go 
func  TestTableDrivenArea(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{Radius: 10}, hasArea: 314.1592653589793},
	}
	for  _, tt  :=  range areaTests {
		t.Run(tt.name, func(t *testing.T) {
			got  := tt.shape.Area()
			if got != tt.hasArea {
				t.Errorf("%#v got %.2f want %.2f", tt.shape, got, tt.hasArea)
			}
		})
	}
}
go test -run TestTableDrivenArea/Rectangle
```
## Extends繼承
```go
package main
import "fmt"

type Person struct {
	Id   int
	Name string
}
type Tester interface {
	Test()
	Eat()
}
func (this *Person) Test() {
	fmt.Println("\tthis =", &this, "Person.Test")
}
func (this *Person) Eat() {
	fmt.Println("\tthis =", &this, "Person.Eat")
}
// Employee從Person繼承，並直接繼承Eat方法，並且將Test方法覆蓋。
type Employee struct {
	Person
}
func (this *Employee) Test() {
	fmt.Println("\tthis =", &this, "Employee.Test")
	this.Person.Test() // 調用父類別方法
}
func main() {
	fmt.Println("An Employee instance :")
	var nu Employee
	nu.Id = 2
	nu.Name = "NTom"
	nu.Test()
	nu.Eat()
	fmt.Println()
	
	fmt.Println("A Tester interface to Employee instance :")
	var t Tester
	t = &nu
	t.Test()
	t.Eat()
	fmt.Println()
	
	fmt.Println("A Tester interface to Person instance :")
	t = &nu.Person
	t.Test()
	t.Eat()
}
```
## Pointer
以下範例以帳號內存取現金示範
使用 TDD 開發需先撰寫測試文件
```go
func  assertNoError(t *testing.T, got error) {
	t.Helper()
	if got !=  nil {
		t.Fatal("got an error but didnt want one")
	}
}

func  assertBalance(t *testing.T, wallet Wallet, want Bitcoin) {
	t.Helper()
	got  := wallet.Balance()
	if got != want {
		t.Errorf("got '%s' want '%s'", got, want)
	}
}

func assertError(t *testing.T, got error, want error) {
	t.Helper()
	if got ==  nil {
		t.Fatal("didn't get an error but wanted one")
	}
	if got != want {
		t.Errorf("got '%s', want '%s'", got, want)
	}
}

func  TestWallet(t *testing.T) {
	t.Run("Deposit", func(t *testing.T) {
		wallet  := Wallet{}
		// 使用 & 檢查 Wallet 記憶體位置是否一至
		fmt.Printf("address of Wallet in test is %p \n", &wallet)
		wallet.Deposit(Bitcoin(10))
		assertBalance(t, wallet, Bitcoin(10))
	})
	t.Run("Withdraw", func(t *testing.T) {
		wallet  := Wallet{Bitcoin(20)}
		err  := wallet.Withdraw(Bitcoin(10))
		assertBalance(t, wallet, Bitcoin(10))
		assertNoError(t, err)
	})
	t.Run("Withdraw insufficient funds", func(t *testing.T) {
		wallet  := Wallet{Bitcoin(20)}
		err  := wallet.Withdraw(Bitcoin(100))
		assertBalance(t, wallet, Bitcoin(20))
		assertError(t, err, ErrInsufficientFunds)
	})
}
```
```go
// 創建一個 error 發生
var  ErrInsufficientFunds  = errors.New("cannot withdraw, insufficient funds")
// 設定新 type 並指定為 int
type  Bitcoin  int
// Bitcoin to string 時回傳 %d BTC 結果
func (b Bitcoin) String() string {
	return fmt.Sprintf("%d BTC", b)
}

// 建構 Wallet struct 並擁有型別為 Bitcoin 的成員 balance 
type  Wallet  struct {
	balance Bitcoin
}
// 建立成員方法 (w *Wallet) *代表使用記憶體位置取得 Wallet struct
func (w *Wallet) Deposit(amount Bitcoin) {
	// w *Wallet 的pointer(*) w 已經代表記憶體位置本身，所已不需 &
	fmt.Printf("address of Wallet in Deposit is %p \n", w)
	w.balance += amount
}

func (w *Wallet) Withdraw(amount Bitcoin) error {
	// 檢查所領款的金額是否超過存款金額
	if amount > w.balance {
		// 如果超過回傳 error
		return ErrInsufficientFunds
	}
	w.balance -= amount
	return  nil
}

func (w *Wallet) Balance() Bitcoin {
	return w.balance
}
```
## Map 
定義一個  map[KeyType]ValueType 的成員
```go
// 建立 map 兩種方法
dictionary =  map[string]string{}
// OR
dictionary =  make(map[string]string)
```
撰寫測試文件
```go
func  TestSearch(t *testing.T) {
	dictionary  :=  map[string]string{"test": "this is just a test"}
	got  :=  Search(dictionary, "test")
	want  :=  "this is just a test"
	if got != want {
		t.Errorf("got '%s' want '%s' given, '%s'", got, want, "test")
	}
}
```
根據測試文件撰寫符合規格的程式
```go
func Search(dictionary map[string]string, word string) string {
	return dictionary[word]
}
```
優化重構程式
```go
func  TestSearchRefactor(t *testing.T) {
	dictionary  := Dictionary{"test": "this is just a test"}
	// 檢查 Dictionary記憶體位置是否一至，這邊不需用&取得記憶體位置，Map 本身就是 reference types
	fmt.Printf("address of Dictionary in test is %p \n", dictionary)
	t.Run("known word", func(t *testing.T) {
		got, _  := dictionary.Search("test")
		want  :=  "this is just a test"
		assertStrings(t, got, want)
	})

	t.Run("unknown word", func(t *testing.T) {
		_, err  := dictionary.Search("unknown")
		want  :=  "could not find the word you were looking for"
		if err ==  nil {
			t.Fatal("expected to get an error.")
		}
		assertStrings(t, err.Error(), want)
	})
}
```
```go 
type Dictionary map[string]string
var ErrNotFound = errors.New("could not find the word you were looking for")
// 這邊的 Dictionary 物件成員並沒有使用 * 因為 map 本身就是 reference types 所以不需重新指向記憶體位置
func (d Dictionary) Search(word string) (string, error) {
	fmt.Printf("address of Dictionary in Search is %p \n", d)
	definition, ok := d[word]
	if !ok {
		return  "", ErrNotFound
	}
	return definition, nil
}
```
新增新功能 Add 
```go
func  TestAdd(t *testing.T)  {
	dictionary := Dictionary{}
	dictionary.Add("test",  "this is just a test")
	want :=  "this is just a test"
	got, err := dictionary.Search("test")
	if err !=  nil  {
		t.Fatal("should find added word:", err)
	}
	if want != got {
		t.Errorf("got '%s' want '%s'", got, want)
	}
}
```
根據測試文件開發功能
```go
func (d Dictionary)  Add(word, definition string)  {
	 d[word]  = definition
}
```
優化重構程式
```go
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 TestAdd(t *testing.T)  {
	t.Run("new word",  func(t *testing.T)  {
		dictionary := Dictionary{}
		word :=  "test"
		definition :=  "this is just a test"
		err := dictionary.Add(word, definition)
		assertError(t, err,  nil)
		assertDefinition(t, dictionary, word, definition)
	})
	t.Run("existing word",  func(t *testing.T)  {
		word :=  "test"
		definition :=  "this is just a test"
		dictionary := Dictionary{word: definition}
		err := dictionary.Add(word,  "new test")
		assertError(t, err, ErrWordExists)
		assertDefinition(t, dictionary, word, definition)
	})
}
```
```go
const  (
 ErrNotFound =  DictionaryErr("could not find the word you were looking for")
 ErrWordExists =  DictionaryErr("cannot add word because it already exists")
)
type DictionaryErr string
func (e DictionaryErr) Error()  string  {
	return  string(e)
}

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
}
```
新增新功能Update
```go
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)
})

t.Run("new word", func(t *testing.T) {
    word := "test"
    definition := "this is just a test"
    dictionary := Dictionary{}
    err := dictionary.Update(word, definition)
    assertError(t, err, ErrWordDoesNotExist)
})
```
```go 
const (
    ErrNotFound         = DictionaryErr("could not find the word you were looking for")
    ErrWordExists       = DictionaryErr("cannot add word because it already exists")
    // new
    ErrWordDoesNotExist = DictionaryErr("cannot update word because it does not exist")
)
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
}
```
新增新功能Delete
```go
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)
    }
}
```
```go
func (d Dictionary) Delete(word string) {
	// go lang 內建刪除功能
    delete(d, word)
}
```
## Dependency Injection
使用依賴性注入將會有已下優點
-   不需要一個特定框架
-   不會將你的code變得更加複雜
-   更有助於程式測試
-  您將會編寫出色的通用功能方法

讓我們回到最初的hello方法
```go
func TestGreet(t *testing.T) {
    buffer := bytes.Buffer{}
    Greet(&buffer,"Chris")
    got := buffer.String()
    want := "Hello, Chris"
    if got != want {
        t.Errorf("got '%s' want '%s'", got, want)
    }
}
```
```go
func Greet(writer *bytes.Buffer, name string)  {
	// fmt.Fprintf 就像是 fmt.Printf 一樣，但他用 writer 取代了預設的 stdout
	fmt.Fprintf(writer, "Hello, %s", name)
}
```
讓我們看看 go 的 fmt 實現
```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...)
}

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
}

type Writer interface {
	Write(p []byte)  (n int, err error)
}
```
我們嘗試使用 Greet function
```go
// 失敗因為 os.Stdout 不是 *bytes.Buffer type
Greet(os.Stdout,  "Elodie")
```
修正錯誤
```go
// 我們使用 io.Writer 來解決寫入問題
func Greet(writer io.Writer, name string) {
	fmt.Fprintf(writer, "Hello, %s", name)
}
```
現在已改寫完成了 Greet 方法，我們來嘗試用他來輸出至網頁上
```go
package main

import (
    "fmt"
    "io"
    "net/http"
)

func Greet(writer io.Writer, name string) {
    fmt.Fprintf(writer, "Hello, %s", name)
}

func MyGreeterHandler(w http.ResponseWriter, r *http.Request) {
    Greet(w, "world")
}

func main() {
    http.ListenAndServe(":5000", http.HandlerFunc(MyGreeterHandler))
}
```

## Mocking
我們開發了一個程式，必須到數3秒代碼如下：
```go
func TestCountdown(t *testing.T) {
    buffer := &bytes.Buffer{}
    Countdown(buffer)
    got := buffer.String()
    want := `3
2
1
Go!`
    if got != want {
        t.Errorf("got '%s' want '%s'", got, want)
    }
}
```
```go
const finalWord =  "Go!"
const countdownStart =  3

func Countdown(out io.Writer) {
    for i := countdownStart; i > 0; i-- {
        time.Sleep(1 * time.Second)
        fmt.Fprintln(out, i)
    }

    time.Sleep(1 * time.Second)
    fmt.Fprint(out, finalWord)
}
```
不難發現當執行測試時，必須花費超過3秒的時間，想像當我們程式更加複雜後，必須要花多久時間執行測試呢?
所以以下我們改變了寫法 ：
```go
func  TestCountdownMock(t *testing.T) {
	buffer  :=  &bytes.Buffer{}
	// 我們創建了一個假的 sleep 方法
	spySleeper  :=  &SpySleeper{}
	CountdownMock(buffer, spySleeper)
	got  := buffer.String()
	want  :=  `3
	2
	1
	Go!`
	if got != want {
		t.Errorf("got '%s' want '%s'", got, want)
	}
	if spySleeper.Calls !=  4 {
		t.Errorf("not enough calls to sleeper, want 4 got %d", spySleeper.Calls)
	}
}
```
```go
type  Sleeper  interface {
	Sleep()
}
type  SpySleeper  struct {
	Calls int
}
func (s *SpySleeper) Sleep() {
	s.Calls++
}

type DefaultSleeper struct {}
func (d *DefaultSleeper) Sleep() {
    time.Sleep(1 * time.Second)
}

func CountdownMock(out io.Writer, sleeper Sleeper) {
	for  i  := countdownStart; i >  0; i-- {
		sleeper.Sleep()
		fmt.Fprintln(out, i)
	}
	sleeper.Sleep()
	fmt.Fprint(out, finalWord)
}

func main() {
	// 真實 sleep
    sleeper := &DefaultSleeper{}
    Countdown(os.Stdout, sleeper)
}
```
通過以上代碼我們測試時明顯減少了等待時間，但是仍然有些問題我們還尚未測試，他是否是依照 先執行 sleep 後再執行 print 直到最後。

```go
const write = "write"
const sleep = "sleep"

type CountdownOperationsSpy struct {
	// 字串陣列，記錄呼叫的方法
	Calls []string
}
// 記錄Sleep
func (s *CountdownOperationsSpy) Sleep() {
	s.Calls = append(s.Calls, sleep)
}
// 記錄Write
func (s *CountdownOperationsSpy) Write(p []byte) (n int, err error) {
	s.Calls = append(s.Calls, write)
	return
}
// 監控 Time 時間
type SpyTime struct {
	durationSlept time.Duration
}
// 記錄下睡眠時間
func (s *SpyTime) Sleep(duration time.Duration) {
	s.durationSlept = duration
}

func TestCountdown(t *testing.T) {
	// 測試輸出資料是否正確
	t.Run("prints 5 to Go!", func(t *testing.T) {
		buffer := &bytes.Buffer{}
		Countdown(buffer, &CountdownOperationsSpy{})
		got := buffer.String()
		want := `3
2
1
Go!`
		if got != want {
			t.Errorf("got '%s' want '%s'", got, want)
		}
	})
	// 測試執行 function 順序是否正確
	t.Run("sleep after every print", func(t *testing.T) {
		spySleepPrinter := &CountdownOperationsSpy{}
		Countdown(spySleepPrinter, spySleepPrinter)
		want := []string{
			sleep,
			write,
			sleep,
			write,
			sleep,
			write,
			sleep,
			write,
		}
		if !reflect.DeepEqual(want, spySleepPrinter.Calls) {
			t.Errorf("wanted calls %v got %v", want, spySleepPrinter.Calls)
		}
	})
}

func TestConfigurableSleeper(t *testing.T) {
	// 測試 ConfigurableSleeper 是否執行正確的睡眠時間
	sleepTime := 5 * time.Second
	spyTime := &SpyTime{}
	sleeper := ConfigurableSleeper{sleepTime, spyTime.Sleep}
	sleeper.Sleep()
	if spyTime.durationSlept != sleepTime {
		t.Errorf("should have slept for %v but slept for %v", sleepTime, spyTime.durationSlept)
	}
}
```
```go
// Sleeper 介面
type Sleeper interface {
	Sleep()
}

// ConfigurableSleeper 實做 Sleeper 且定義 delay 時間
type ConfigurableSleeper struct {
	duration time.Duration
	sleep    func(time.Duration)
}

// Sleep將會依照 duration 時間暫停程式執行
func (c *ConfigurableSleeper) Sleep() {
	c.sleep(c.duration)
}

const finalWord = "Go!"
const countdownStart = 3

// Countdown 將根據 delay 時間印出倒數
func Countdown(out io.Writer, sleeper Sleeper) {
	for i := countdownStart; i > 0; i-- {
		sleeper.Sleep()
		fmt.Fprintln(out, i)
	}
	sleeper.Sleep()
	fmt.Fprint(out, finalWord)
}

func main() {
	sleeper := &ConfigurableSleeper{1 * time.Second, time.Sleep}
	Countdown(os.Stdout, sleeper)
}
```
## Concurrency
我們以測試網站是否正常為例
```go
func  mockWebsiteChecker(url string) bool {
	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",
	}
	want  :=  map[string]bool{
		"http://google.com": true,
		"http://blog.gypsydave5.com": true,
		"waat://furhurterwe.geds": false,
	}
	got := CheckWebsites(mockWebsiteChecker, websites)
	if  !reflect.DeepEqual(want, got) {
		t.Fatalf("Wanted %v, got %v", want, got)
	}
}

func slowStubWebsiteChecker(_ string) bool {
    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"
    }

    for i := 0; i < b.N; i++ {
        CheckWebsites(slowStubWebsiteChecker, urls)
    }
}
```
```go
type WebsiteChecker func(string) bool
type result struct {
    string
    bool
}

func CheckWebsites(wc WebsiteChecker, urls []string) map[string]bool {
    results := make(map[string]bool)
    resultChannel := make(chan result)

    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
    }

    return results
}
```
## Select
```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)
		// defer 延遲動作，用於當需要執行完function後close物件
		defer slowServer.Close()
		defer fastServer.Close()
		
		slowURL := slowServer.URL
		fastURL := fastServer.URL
		
		want := fastURL
		got, err := Racer(slowURL, fastURL)
		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(25 * time.Millisecond)
		defer server.Close()
		_, err := ConfigurableRacer(server.URL, server.URL, 20*time.Millisecond)
		if err == nil {
			t.Error("expected an error but didn't get one")
		}
	})
}

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)
	}))
}
```
```go
var tenSecondTimeout = 10 * time.Second
// 測試 a b 網站速度，超過 10s timeout
func Racer(a, b string) (winner string, error error) {
	return ConfigurableRacer(a, b, tenSecondTimeout)
}
// 比較 a b 網站 並回傳速度較快的一個
func ConfigurableRacer(a, b string, timeout time.Duration) (winner string, error error) {
	// 使用 select 同時並發動作 
	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)
	}
}
func ping(url string) chan bool {
	ch := make(chan bool)
	go func() {
		http.Get(url)
		ch <- true
	}()
	return ch
}
```
## Pointer Panic
```go
type  Calc  interface {
	increment()
	decrement()
}

type  Math struct {
	count int
}

func (m *Math) increment() {
	println(m)
	m.count +=  1
	println("increment!", m.count)
}

func (m *Math) decrement() {
	println(&m)
	m.count -=  1
	println("decrement!", m.count)
}

func (m Math) decrement2() {
	m.count -=  1
	println("decrement!", m.count)
}

func  main() {
	// explodes -> math -> nil
	var  math  *math =  nil
	(*math).increment() // panic: value method main.math.decrement called using nil *math pointer
	var  explodes Explodes = math
	println(math, explodes) // '0x0 (0x10a7060,0x0)'
	if explodes !=  nil {
		println("Not nil!") // 'Not nil!'
		explodes.increment() // Normal
		explodes.decrement2() // panic: value method main.math.decrement called using nil *math pointer
	} else {
		println("nil!")
	}
	var  math1 math = math{}
	println(&math1)
	math1.increment()
	println(&math1)
	math1.increment()
	println(&math1)
	math1.increment()
	println(math1.count)
	
	var  math2  *math =  &math{}
	println(&math2)
	math2.increment()
	println(&math2)
	math2.increment()
	println(&math2)
	math2.increment()
	println(math2.count)
}
```
對於 golang 並無try catch 的解釋
https://www.ithome.com.tw/voice/103455

## Panic Recover
```go
package main

import (
"fmt"
"os"
)

func  check(err error) {
	if err !=  nil {
		panic(err)
	}
}

func  main() {
	f, err  := os.Open("/tmp/dat")
	defer  func() {
		if  err  :=  recover(); err !=  nil {
			fmt.Println(err) // 這已經是頂層的 UI 介面了，想以自己的方式呈現錯誤
		}
		if f !=  nil {
			if  err  := f.Close(); err !=  nil {
				panic(err) // 示範再拋出 panic
			}
		}
	}()
	check(err)
	b1  :=  make([]byte, 5)
	n1, err  := f.Read(b1)
	check(err)
	fmt.Printf("%d bytes: %s\n", n1, string(b1))
}
```

## Summary
|優點                          |
|-------------------------------|
|使用 goroutines、channels 快速開發並型程序|
|Go 性能明顯優越於近代語言(除了 Rust)|
|標準內建的測試框架|
|有 Defer功能不會忘記關閉清除物件|

|缺點                          |
|-------------------------------|
|沒有泛型|
|沒有 enum|
|數據結構只有 map,slice|
|擁有指標特性可能使開發者混亂|

## Reference
**TDD 開發模式**
**CI/CD 使用**
https://github.com/drone/drone
**ORM Library 測試**
https://github.com/mattn/go-oci8/tree/master/_example
https://www.jianshu.com/p/add47894c446
https://gocodecloud.com/blog/2016/08/09/accessing-an-oracle-db-in-go/
https://blog.csdn.net/notbaron/article/details/78168026
https://o7planning.org/en/10467/installing-c-cpp-compiler-mingw
https://github.com/wendal/go-oci8
https://www.oracle.com/technetwork/topics/winx64soft-089540.html
https://www.144d.com/post-490.html
https://www.cnblogs.com/ghj1976/p/3540257.html
https://medium.com/@kfoss/oracle-and-go-on-windows-64-bit-2b37496fe179
**gRPC 使用**
https://grpc.io/docs/quickstart/go.html#prerequisites
**MicroServices 架構開發**
https://yq.aliyun.com/articles/2764
https://lostechies.com/andrewsiemer/2016/01/11/testing-microservices/
https://blog.csdn.net/tengxing007/article/details/78289645
https://juejin.im/post/5a3257496fb9a044fc44c3d3
https://medium.com/seek-blog/microservices-in-go-2fc1570f6800
https://outcrawl.com/go-microservices-cqrs-docker
https://dzone.com/articles/go-microservices-blog-series-part-1
https://en.wikipedia.org/wiki/Software_architecture

**Go library** 
https://github.com/avelino/awesome-go